Compare commits

...

2253 Commits

Author SHA1 Message Date
80fabce99c fix: config override error 2023-11-01 20:49:58 +08:00
347e606853 chore: add optional provider path 2023-10-23 16:52:21 +08:00
7d58238e06 fix: find pkg not work 2023-10-23 16:52:21 +08:00
a883faae9d chore: Try to restore process rules 2023-10-23 16:52:21 +08:00
a7040af346 Android: patch 2023-10-23 16:52:19 +08:00
5cca3569e9 chore: cleanup unneed patch 2023-10-23 16:51:51 +08:00
f2857b3243 fix: inbound metadata 2023-10-23 16:51:48 +08:00
8e84210852 fix: missing config member in patch 2023-10-23 16:50:48 +08:00
8c2df79536 chore: Update default Geodata URL 2023-10-23 16:50:48 +08:00
ebe33cff18 fix: GeoIP side-load 2023-10-23 16:50:48 +08:00
a551d7ebd9 Android: patch 2023-10-23 16:50:43 +08:00
79a29826a4 Android: patch 2023-10-23 16:46:00 +08:00
3564e96a00 chore: share some code 2023-10-23 16:45:22 +08:00
f6f8f27668 action: update sync 2023-10-23 15:39:56 +08:00
dff54464c6 Add auto sync Alpha rebase android-open -> android-real (#817)
* chore: add android branch auto sync

* chore: fix

* chore: fix missing

* chore: fix actions

* chore: write branch auto sync
2023-10-23 15:39:56 +08:00
e987cdaaae chore: add CMFA auto update-dependencies trigger 2023-10-23 15:39:56 +08:00
6cd0e58fd0 fix: ssr panic 2023-10-23 15:39:56 +08:00
f794c090a5 chore: update sing-tun 2023-10-23 15:39:56 +08:00
0d3197e437 chore: fix sniffer log error 2023-10-20 22:36:29 +08:00
150bf7fc65 chore: decrease memory copy in sing listener 2023-10-20 08:39:04 +08:00
51004b14d9 docs: update readme.md 2023-10-20 00:34:10 +08:00
ea7e15b447 chore: decrease memory copy in quic sniffer 2023-10-19 23:51:37 +08:00
8e637a2ec7 chore: code cleanup 2023-10-19 20:44:49 +08:00
96d886380a Merge pull request #810 from 5aaee9/Alpha
feat: add quic sniffer
2023-10-19 19:34:45 +08:00
981c69040f docs: update about quic sniffer 2023-10-19 19:09:13 +08:00
de90c276af feat(sniffer): add quic sniffer 2023-10-19 18:30:20 +08:00
0129a8579f chore: merge some quic-go fix 2023-10-19 11:08:14 +08:00
11ed4a56bd chore: code cleanup 2023-10-17 12:46:41 +08:00
d75a0e69a0 chore: Update dependencies 2023-10-16 09:56:41 +08:00
1faad73381 fix: socks5 udp associate 2023-10-16 09:27:55 +08:00
d2499cd69d feature: add xdg base support (#2913) 2023-10-16 09:23:31 +08:00
98df77439c feature: add environs startup option support (#2909) 2023-10-16 09:22:16 +08:00
81bbbe4eec fix: DNS NCACHE TTL and OPT RRs (#2900)
* Fix: DNS NCACHE TTL and OPT RRs

1. DNS NCACHE was not correctly implemented.
2. OPT RRs must not be cached or forwarded.

Closes #2889.
2023-10-16 09:21:06 +08:00
9f530525d7 fix: method in vmess http-opts is not used 2023-10-16 09:16:36 +08:00
129283066f chore: code cleanup 2023-10-11 22:54:19 +08:00
0dc6a726c1 fix: unmap 4in6 ip 2023-10-11 18:17:39 +08:00
4636499439 chore: support reject proxy type 2023-10-11 13:01:14 +08:00
9a16eb2895 fix: BBR memory leak
from: 7c46e845a6
2023-10-11 11:01:17 +08:00
270a080b55 fix: sing listener panic 2023-10-11 10:55:12 +08:00
1cf9a55e3e chore: code cleanup 2023-10-10 21:29:12 +08:00
6bcd91a801 feat: add skip-auth-prefixes 2023-10-10 21:29:12 +08:00
7ed25ddc74 chore: better atomic using 2023-10-10 21:28:46 +08:00
ae557c30d3 fix: quic-go min MTU 2023-10-08 13:15:17 +08:00
5a1800d642 fix: BBR bandwidth estimation edge case
from 89429598bf
2023-10-08 07:26:28 +08:00
d8fe7a52d6 feat: add certificate and private-key to vmess listener 2023-10-08 07:26:28 +08:00
791ecfbb32 feat: add ws-path to vmess listener 2023-10-08 07:26:28 +08:00
5ff4473083 chore: migrate from gorilla/websocket to gobwas/ws 2023-10-06 17:44:36 +08:00
d1e88a30cb fix: gVisor UDP 6to4 check 2023-10-03 16:00:03 +08:00
7eae7756f5 chore: update gvisor 2023-10-01 19:15:26 +08:00
4e3cd01aad chore: merge some quic-go fix 2023-10-01 13:44:56 +08:00
dbaee284e4 fix: hy2/tuic inbound cert isn't path
Co-authored-by: wwqgtxx <wwqgtxx@gmail.com>
2023-10-01 12:04:34 +08:00
8253bfe2e0 add quic-go-disable-ecn to experimental 2023-10-01 09:10:11 +08:00
828b5ad8bb chore: add new bbr implementation 2023-10-01 00:01:32 +08:00
fedad26c13 chore: support relative path for hy2/tuic inbound cert 2023-10-01 00:01:32 +08:00
a526bb70ea chore: fix bbr bugs 2023-09-30 13:40:07 +08:00
5f6de610e1 Fix: should check all ips need to fallback (#2915) 2023-09-29 13:42:22 +08:00
02397868fc docs: support reload in service 2023-09-29 13:26:59 +08:00
265a6b9b68 chore: reduce string split immediately after string concat (#773) 2023-09-29 08:51:13 +08:00
10e7c533d7 feat: support clash premium's structured log stream (#735)
* feat: support clash premium's structured log stream

New version of Clash for Windows uses `ws://external-controller/logs?token=&level=info&format=structured` to get real time log. When Clash Premium Core reveices `format=structured`, it returns a different form of JSON log entry. Supporting this feature will allow better Clash for Windows integration

Signed-off-by: Misty <gyc990326@gmail.com>
2023-09-29 08:50:50 +08:00
0ed3c5a5ec chore: improve subscription userinfo parsing (#781)
do not use regex parsing for `Subscription-UserInfo` header field
2023-09-29 08:42:57 +08:00
c2b06a02bf feat: add reload signal support (#780)
Backport Clash feature by @septs, see Dreamacro/clash#2908
2023-09-29 08:36:25 +08:00
e0458a8fde chore: decrease goroutine used in core tunnel 2023-09-28 18:59:31 +08:00
21fb5f75b8 fix: gvisor panic 2023-09-26 09:06:00 +08:00
fb99412193 chore: update quic-go to 0.39.0 2023-09-26 08:51:25 +08:00
fdd327d58d fix: fail to set KeepAliveIntervall #715 2023-09-25 14:05:13 +08:00
0dfe696300 chore: ntp service support dialer-proxy 2023-09-25 09:11:35 +08:00
c0ba798708 chore: share N.dialer code 2023-09-25 09:11:35 +08:00
67d7e53f7a feat: recovering preHandleMetadata failure from sniffing (#769) 2023-09-24 19:27:55 +08:00
e6366f7442 chore: fix typo 2023-09-24 19:00:51 +08:00
89d9cb0539 Merge pull request #767 from PuerNya/fix-delay
chore: handle provider proxies  in proxies api
2023-09-24 15:55:33 +08:00
0d300a3540 chore: handle provider proxies in proxies api 2023-09-24 15:39:14 +08:00
7c59916c22 chore: update provider proxies api 2023-09-24 00:19:10 +08:00
8f515ecc05 chore: updateUI API return 501 when config incomplete 2023-09-23 18:00:07 +08:00
34f62a0919 feat: add provider proxies api 2023-09-23 17:54:20 +08:00
0207a7ac96 chore: resolver read system hosts file 2023-09-23 14:01:18 +08:00
bf619d8586 fix: socks5 udp not working on loopback 2023-09-22 23:33:24 +08:00
d48f9c2a6c chore: rebuild ca parsing 2023-09-22 14:45:34 +08:00
90a5aa609a fix: uot read failed 2023-09-22 00:11:57 +08:00
4fe7a463c5 chore: limit tuicv5's maxUdpRelayPacketSize up to 1200-PacketOverHead 2023-09-21 23:49:45 +08:00
7f49c91267 fix: hy2 udp not working 2023-09-21 23:36:40 +08:00
f6bf9c0857 feat: converter support hysteria2 2023-09-21 17:25:15 +08:00
da24810da2 chore: support set cwnd for hy2 too 2023-09-21 16:41:31 +08:00
ee3213c28f fix: tuicv5 panic in ReadFrom 2023-09-21 15:10:35 +08:00
233eeb0b38 feat: inbound support Hysteria2 2023-09-21 15:10:35 +08:00
6c3b973748 doc: add Hysteria2 doc 2023-09-21 10:43:45 +08:00
9b8e2d9343 feat: support Hysteria2 2023-09-21 10:28:28 +08:00
24fd577767 chore: Update dependencies 2023-09-21 08:57:38 +08:00
42b85de83e chore: Restore go1.20 support 2023-09-21 08:29:28 +08:00
62266010ac Revert "migration: go 1.21"
This reverts commit 33d41338ef.
2023-09-21 08:29:28 +08:00
0d7a57fa9d Chore: update github issue template 2023-09-21 03:40:46 +08:00
f909b3c0dc chore: Update android-ndk 2023-09-20 15:26:36 +08:00
8b518161a3 chore: update external-ui 2023-09-20 14:23:58 +08:00
20fafdca65 chore: cleanup code 2023-09-18 19:42:08 +08:00
fd96efd456 chore: ignore PR when Pre-releasing 2023-09-18 19:36:11 +08:00
7c21768e99 feat: update external-ui 2023-09-18 19:21:30 +08:00
6a5a94f48f chore: DNS cache policy follow upstream 2023-09-17 17:18:35 +08:00
33d41338ef migration: go 1.21 2023-09-17 17:05:13 +08:00
2d3b9364bf fix: caceh dns result 2023-09-16 12:30:11 +08:00
fa49fd7ba2 chore: use cmp in go 1.21
Co-authored-by: H1JK <hell0jack@protonmail.com>
2023-09-16 12:06:58 +08:00
c3d72f6883 feat: download/upgrade XD to external-ui 2023-09-16 11:44:15 +08:00
af99b52527 docs(README): update dashboard section 2023-09-09 13:06:49 +08:00
f241e1f81a chore: Update dependencies 2023-09-09 09:53:14 +08:00
90acce7fa1 feat: Add disable quic-go GSO to experimental 2023-09-08 22:58:59 +08:00
7286391883 feat: support users to customize download ua 2023-09-07 18:44:58 +08:00
a1eab125ee fix: ntp service panic 2023-09-04 18:35:06 +08:00
1d4af2d92b chore: TCPKeepAlive interval set to 15s by default 2023-09-03 20:42:54 +08:00
d6cf2a837f chore: ntp service dep with sing, optional synchronize system time 2023-09-03 17:49:56 +08:00
d6b80acfbc chore: Use xsync provided map size calculation 2023-09-02 20:17:43 +08:00
1cad615b25 chore: using xsync.MapOf replace sync.Map 2023-09-02 16:54:48 +08:00
73fa79bf3f feat: configurable TCPKeepAlive interval 2023-09-02 16:45:16 +08:00
d79c13064e chore: cleanup codes 2023-09-02 14:12:53 +08:00
427a377c2a refactor: Decouple .Cleanup from ReCreateTun
The listener.Cleanup method will be called during
executor.Shutdown and route.restart, so it should serve
all kinds of listeners rather than a single tun device.

Currently listener.ReCreateTun will call it to handle
some internal affairs, This should be decoupled.

In this way, the cleanup tasks for data outside the
process life cycle that other listeners will add here
in the future will not be accidentally triggered
by configuring tun.
2023-09-02 14:12:53 +08:00
9feb4d6668 fix: RESTful api missing TunConf.device
In commit 54fee7b, due to failure to take into account that
not all required parameters of `sing_tun.server.New` have
default values provided by `LC.Tun`, the name of the tun device
cannot be obtained when `TunConf.device` is not explicitly
configured. This commit fixed the issue.
2023-09-02 14:12:53 +08:00
a366e9a4b5 fix: ntp service panic 2023-09-02 12:37:43 +08:00
cbdf33c42c feat: ntp service 2023-09-02 02:15:46 +08:00
9ceaf20584 fix: concurrent map writes #707 2023-09-01 10:43:04 +08:00
54fee7bd3a Improve: nicer tun info for RESTful api
Let the restful api still get TunConf even when tun is off.
Otherwise the api will return the default values,
instead of the values that actually take effect after enable.

* Due to this problem, yacd changes the displayed value
back to gvisor immediately after the user selects tun stack.
2023-08-30 21:13:32 +08:00
414d8f2162 chore: use WaitGroup in dualStackDialContext 2023-08-30 17:28:36 +08:00
86cf1dd54b fix: dualStack confusing error on ipv4 failed connect 2023-08-30 17:28:36 +08:00
d099375200 chore: rename func name 2023-08-30 15:52:41 +08:00
9536372cfb fix: call shutdown before restart (#709) 2023-08-30 15:49:28 +08:00
630a17cf90 chore: cleanup codes 2023-08-26 21:20:20 +08:00
0a7b7894bd feat: proxies support direct type 2023-08-24 23:33:03 +08:00
3a9fc39cd9 chore: update quic-go to 0.38.0 2023-08-21 16:18:56 +08:00
1181fd4560 feat: add udp-over-stream for tuic
only work with meta tuic server or sing-box 1.4.0-beta.6
2023-08-21 12:37:39 +08:00
b8a60261ef chore: restore unselected
clear selected node in outboundgoup/URLtest when getGroupDelay triggered
2023-08-18 22:17:07 +08:00
db68d55a0e fix: sing-vmess panic 2023-08-17 22:33:07 +08:00
574efb4526 chore: Update dependencies 2023-08-16 21:30:12 +08:00
03b0252589 feat: bump restls to v0.1.6 (utls v1.4.3) (#692)
* feat: bump restls to v0.1.5 (utls v1.4.3)
* fix: rm dependency go-quic
2023-08-16 11:41:58 +08:00
ed09df4e13 fix: TLS ALPN support 2023-08-14 15:48:13 +08:00
f89ecd97d6 feat: Converter unofficial TUIC share link support 2023-08-14 15:11:33 +08:00
3093fc4f33 chore: update go1.21.0 release 2023-08-09 17:26:24 +08:00
984fca4726 feat: add inbound-mptcp for listeners 2023-08-09 17:09:03 +08:00
cc42d787d4 feat: add mptcp for all proxy 2023-08-09 16:57:39 +08:00
e2e0fd4eba chore: using uint16 for ports in Metadata 2023-08-09 13:51:02 +08:00
bad9f2e6dc fix geodata-mode 2023-08-07 01:43:23 +08:00
68bf6f16ac refactor: Geodata initialization 2023-08-06 23:34:10 +08:00
cca701c641 chore: Update dependencies 2023-08-06 18:38:50 +08:00
09ec7c8a62 chore: update quic-go to 0.37.3 2023-08-06 09:45:51 +08:00
68f312288d chore: update quic-go to 0.37.2 and go1.21rc4 2023-08-05 12:53:49 +08:00
191243a1d2 chore: better tuicV5 deFragger 2023-08-03 23:07:30 +08:00
b0fed73236 Fix: mapping dns should not stale (#675)
* Fix: mapping dns should not stale

* Update enhancer.go
2023-08-01 17:30:57 +08:00
f125e1ce9e chore: Update dependencies 2023-08-01 13:54:22 +08:00
e2216b7824 chore: update quic-go to 0.37.1 2023-08-01 09:55:55 +08:00
7632827177 chore: Use Meta-geoip for default 2023-07-20 23:24:48 +08:00
b0e76ec791 feat: Add Meta-geoip V0 database support 2023-07-17 10:33:20 +08:00
a82745f544 chore: Remove legacy XTLS support (#645)
* chore: Remove legacy XTLS support

* chore: Rename function
2023-07-16 23:26:07 +08:00
cbb8ef5dfe fix: discard http unsuccessful status 2023-07-16 11:43:55 +08:00
a181e35865 chore: structure support decode pointer 2023-07-16 11:11:30 +08:00
014537e1ea fix: discard http unsuccessful status 2023-07-16 11:10:07 +08:00
9b50f56e7c fix: tunnel's handleUDPToLocal panic 2023-07-16 10:35:10 +08:00
9cbca162a0 feat: tuic outbound allow set an empty ALPN array 2023-07-16 10:29:43 +08:00
f73f32e41c fix: parse nested sub-rules failed 2023-07-16 10:15:43 +08:00
cfc30753af chore: Update go1.21rc3 2023-07-15 16:52:44 +08:00
081e94c738 feat: Add sing-geoip database support 2023-07-14 22:28:24 +08:00
5dd57bab67 chore: Update dependencies 2023-07-14 11:37:15 +08:00
492a731ec1 fix: DNS cache 2023-07-14 09:55:43 +08:00
0b1aff5759 chore: Update dependencies 2023-07-02 10:41:02 +08:00
8f1475d5d0 chore: update to go1.21rc2, drop support for go1.19 2023-07-02 09:59:18 +08:00
c6b84b0f20 chore: update quic-go to 0.36.1 2023-07-02 09:05:16 +08:00
02ba78ab90 chore: change geodata download url to fastly.jsdelivr.net (#636) 2023-06-30 18:52:39 +08:00
57db8dfe23 Chore: Something update from clash (#639)
Chore: add alive for proxy api
Improve: alloc using make if alloc size > 65536
2023-06-30 17:36:43 +08:00
8e16738465 chore: better env parsing 2023-06-29 16:40:08 +08:00
db6b2b7702 chore: better resolv.conf parsing 2023-06-28 09:17:54 +08:00
603d0809b4 fix: panic when add 4in6 ipcidr 2023-06-26 21:04:54 +08:00
614cc93cac chore: better close single connection in restful api 2023-06-26 18:25:36 +08:00
1cb75350e2 chore: statistic's Snapshot only contains TrackerInfo 2023-06-26 18:13:17 +08:00
42ef4fedfa chore: avoid unneeded map copy when close connection in restful api 2023-06-26 17:46:14 +08:00
2284acce94 chore: update quic-go to 0.36.0 2023-06-26 12:08:38 +08:00
919daf0dbb fix: tuic server cwnd parsing 2023-06-21 14:00:49 +08:00
6d824c8745 chore: tuic server can handle V4 and V5 in same port 2023-06-21 13:53:37 +08:00
1d94546902 chore: fix TUIC cwnd parsing 2023-06-21 00:47:05 +08:00
ad7508f203 Revert "chore: Refine adapter type name"
This reverts commit 61734e5cac.
2023-06-19 14:28:06 +08:00
d391fda051 chore: function rename 2023-06-19 08:32:11 +08:00
fe0f2d9ef9 chore: Update dependencies 2023-06-19 08:23:48 +08:00
b9110c164d update docs 2023-06-18 01:50:32 +08:00
6c8631d5cc chore: adjustable cwnd for cc in quic 2023-06-18 00:47:26 +08:00
61734e5cac chore: Refine adapter type name 2023-06-17 00:05:03 +08:00
77fb9a9c01 feat: optional provider path (#624) 2023-06-15 22:45:02 +08:00
af28b99b2a Add REALITY ChaCha20-Poly1305 auth mode support 2023-06-14 17:17:46 +08:00
4f79bb7931 fix: singmux return wrong supportUDP value 2023-06-14 15:51:13 +08:00
644abcf071 fix: tuicV5's heartbeat should be a datagram packet 2023-06-13 17:50:10 +08:00
183f2d974c fix: dns concurrent not work 2023-06-12 18:42:46 +08:00
e914317bef feat: support tuicV5 2023-06-12 18:42:46 +08:00
5e20fedf5f chore: Update dependencies 2023-06-11 23:57:25 +08:00
54337ecdf3 chore: Disable cache for RCode client 2023-06-11 23:01:51 +08:00
c7de0e0253 feat: Add RCode DNS client 2023-06-11 23:01:45 +08:00
b72219c06a chore: allow unsafe path for provider by environment variable 2023-06-11 01:55:49 +00:00
64b23257db chore: Replace murmur3 with maphash 2023-06-10 17:35:19 +08:00
c57f17d094 chore: reduce process lookup attempts when process not exist #613 2023-06-08 18:07:56 +08:00
cd44901e90 fix: Disable XUDP global ID if source address invalid 2023-06-08 15:57:51 +08:00
766d08a8eb chore: init gopacket only when dial fake-tcp to decrease memory using 2023-06-08 11:58:51 +08:00
c3ef05b257 feat: Add XUDP migration support 2023-06-07 23:03:36 +08:00
093453582f fix: Resolve delay omission in the presence of nested proxy-groups 2023-06-07 13:20:45 +08:00
767aa182b9 When testing the delay through REST API, determine whether to store the delay data based on certain conditions instead of discarding it directly (#609) 2023-06-07 11:04:03 +08:00
ad11a2b813 fix: go1.19 compile 2023-06-06 10:47:50 +08:00
dafecebdc0 chore: Something update from clash :) (#606) 2023-06-06 09:45:05 +08:00
e7174866e5 fix: nil pointer in urltest (#603) 2023-06-05 12:40:46 +08:00
fdaa6a22a4 fix hysteria faketcp lookback in TUN mode (#601) 2023-06-04 23:43:54 +08:00
fd0c71a485 chore: Ignore PR in Docker build 2023-06-04 15:51:25 +08:00
3c1f9a9953 ProxyProvider health check also supports specifying expected status (#600)
Co-authored-by: wwqgtxx <wwqgtxx@gmail.com>
2023-06-04 14:00:24 +08:00
3ef81afc76 [Feature] Proxy stores delay data of different URLs. And supports specifying different test URLs and expected statue by group (#588)
Co-authored-by: Larvan2 <78135608+Larvan2@users.noreply.github.com>
Co-authored-by: wwqgtxx <wwqgtxx@gmail.com>
2023-06-04 11:51:30 +08:00
03d0c8620e fix: hysteria faketcp loopback in tun mode 2023-06-03 22:15:09 +08:00
63b5387164 chore: update proxy's udpConn when received a new packet 2023-06-03 21:40:09 +08:00
2af758e5f1 chore: Random only if the certificate and private-key are empty 2023-06-03 17:45:47 +08:00
2c44b4e170 chore: update quic-go to 0.35.1 2023-06-03 16:45:35 +08:00
7906fbfee6 chore: Update dependencies 2023-06-03 00:24:51 +08:00
17565ec93b chore: Reject packet conn implement wait read 2023-06-02 22:58:33 +08:00
26acaee424 fix: handle manually select in url-test 2023-06-02 18:26:51 +08:00
9b6e56a65e chore: update quic-go to 0.34.0 2023-06-01 16:25:02 +08:00
7fa3d3aa0b chore: cleanup system dns code 2023-06-01 12:36:53 +08:00
1120c8185d chore: Use API to create windows firewall rule 2023-05-31 15:54:36 +08:00
41af94ea66 fix: deadline reader cause panic 2023-05-30 20:21:51 +08:00
36539bb670 fix: sing-ss2's Reader not set buffer end 2023-05-30 07:59:55 +08:00
8e88e0b9f5 chore: add WaitReadFrom support in ssr 2023-05-28 22:51:44 +08:00
097f3e250c chore: slightly improve quic-bbr performance 2023-05-28 20:15:11 +08:00
9c2972afb0 chore: add IN-USER and IN-NAME rules 2023-05-28 17:19:57 +08:00
7aae781569 chore: add WaitReadFrom support in quicStreamPacketConn 2023-05-28 15:22:08 +08:00
92f71fd25f chore: add WaitReadFrom support in hyPacketConn 2023-05-28 09:33:42 +08:00
f44ba26f0c chore: switch ss uot default back to version 1 2023-05-28 08:50:02 +08:00
73140ab826 fix: udp panic when server return a domain name 2023-05-27 13:43:41 +08:00
4971b9d804 chore: Add vision splice support 2023-05-27 11:26:13 +08:00
654e76d91e refactor: Move vision implementation to a new package 2023-05-26 20:11:06 +08:00
984bf27d9b chore: using internal socks5.ReadAddr0 in trojan 2023-05-20 18:35:04 +08:00
546b2bc24b chore: Decrease UoT read memory 2023-05-20 17:01:52 +08:00
d4e4f6d2d7 chore: rebuild ref and threadSafe packetConn 2023-05-20 16:57:42 +08:00
b047ca0294 chore: packet deadline support CreateReadWaiter interface 2023-05-20 11:44:11 +08:00
2b1e69153b chore: better packet deadline 2023-05-19 23:29:59 +08:00
ae8d42fb82 Fix: update action to support Node 16 (#565) 2023-05-19 21:00:00 +08:00
89ae640487 fix: ensure group not empty 2023-05-19 19:57:55 +08:00
6e0c3a368f chore: upgrade dependencies 2023-05-19 11:08:14 +08:00
033f902ace chore: more context passing in outbounds 2023-05-18 13:15:08 +08:00
6b1a4385b2 chore: better updater 2023-05-17 00:33:59 +08:00
e552b5475f fix: tfoConn panic 2023-05-16 14:55:50 +08:00
8b631f11b8 chore: better sing's udp api support 2023-05-15 22:45:08 +08:00
1a9104c003 fix: UDP packet should not return io.EOF 2023-05-15 19:06:58 +08:00
872a28a5eb Fix: deprecated action commands (#556)
Co-authored-by: 8Mi_Yile <admin@8mi.tech>
2023-05-14 13:43:25 +08:00
c7557b8e48 feat: Updater detect and download AMD64v3 artifact
Co-authored-by: Larvan2 <78135608+larvan2@users.noreply.github.com>
2023-05-14 12:34:47 +08:00
c6fed3e97f fix: TLS certificate pool initialize
Co-authored-by: Skyxim <noreply@skyxim.dev>
2023-05-14 00:21:59 +08:00
ed17478961 feat: Support insecure gRPC 2023-05-13 09:38:14 +08:00
b674983034 chore: improve read waiter interface 2023-05-12 12:12:22 +08:00
a22b1cd69e fix: sing-based listener panic 2023-05-12 09:14:27 +08:00
f1be9b3f4a fix: tuic server return error udp address 2023-05-11 22:45:27 +08:00
534282839c chore: better tproxy error logging 2023-05-11 21:31:29 +08:00
8dd7632d0a chore: update docs 2023-05-11 21:24:38 +08:00
51e9f3598e fix: shadowsocks rc4-md5 not working 2023-05-11 20:42:36 +08:00
76caab19bf fix: Deadline not apply on EnhancePacketConn 2023-05-11 19:58:50 +08:00
234f7dbd3b chore: decrease shadowsocks udp read memory used for no-windows platform 2023-05-11 19:01:41 +08:00
e404695a0d fix: mux's udp should add write lock 2023-05-11 15:34:28 +08:00
75cd72385a chore: decrease direct udp read memory used for no-windows platform 2023-05-11 13:47:51 +08:00
d9fa051dd8 chore: drop bufio.Reader in BufferedConn to let gc can clean up its internal buf 2023-05-11 11:30:20 +08:00
98394095e4 fix: udp can't auto close 2023-05-11 00:03:40 +08:00
c58400572c chore: sing inbound support WaitReadPacket 2023-05-10 22:35:50 +08:00
3b291d3fbf fix: sing inbound should check needAdditionReadDeadline on udp too 2023-05-10 16:03:28 +08:00
15a8d7c473 chore: better tuic earlyConn impl 2023-05-10 09:36:06 +08:00
67b9314693 fix: tuic can't work with proxy-dialer 2023-05-10 09:03:49 +08:00
99f7c4f821 fix: ss aead udp problem 2023-05-10 08:31:16 +08:00
0cb594dd5d chore: upgrade dependencies 2023-05-10 07:23:49 +08:00
bd431fbf49 fix: Update unsafe pointer add usage 2023-05-06 15:49:10 +08:00
8c0168d3a8 chore: upgrade dependencies 2023-05-05 07:27:36 +00:00
7ae3e78b15 Feat: rewrite http outbound 2023-05-03 22:00:06 +08:00
d61a5af335 chore: update release note 2023-05-02 22:06:07 +08:00
f90066f286 chore: Update dependencies 2023-05-01 23:38:02 +08:00
463da578dd fixes #512: geo download failed when startup (#538)
* fixes #512: geo download failed when startup

- 启动阶段,executor还未初始化tunnel,tcpIn==nil导致geo下载失败,阻塞在
  tcpIn <- context

* chore: handled by the upper layer

* chore: remove useless parameters

---------

Co-authored-by: Skyxim <noreply@skyxim.dev>
2023-05-01 21:27:55 +08:00
1eefa71e1f chore: Make slash optional for system resolver 2023-05-01 12:58:02 +08:00
969c235490 chore: Remove default DNS in system resolver 2023-05-01 12:41:36 +08:00
f35ff24d0c docs: update config.yaml 2023-04-30 17:10:57 +00:00
94f990da31 feat: support system dns for windows 2023-05-01 00:46:57 +08:00
d6931ec491 feat: support system dns 2023-04-30 23:59:54 +08:00
19b403da86 refactor: Switch to sing-shadowsocks2 client 2023-04-30 18:57:16 +08:00
6ecd1c31e5 fix: tuic connection error using fast_open 2023-04-29 15:44:07 +00:00
a7233f6036 fix: wildcard matching problem 2023-04-28 16:55:35 +00:00
898f10ca96 Fix concurrent close on h2mux server conn 2023-04-27 22:18:09 +08:00
87d2d08a8f chore: clash filter link local 2023-04-27 07:06:53 +00:00
928dcf9af9 chore: better memory fetching time 2023-04-27 06:55:53 +00:00
e4f762822a chore: better parse remote dst 2023-04-27 02:32:25 +00:00
573216befb fix: tracker remote addr check 2023-04-27 01:39:29 +00:00
30f93debc4 chore: better workflow 2023-04-26 14:09:17 +00:00
a1b6d6050f chore: remove debug_api patch 2023-04-26 14:02:21 +00:00
eceea72a56 fix: tunnel udp panic 2023-04-26 15:57:25 +08:00
888c233e9c fix: sing-mux udp 2023-04-26 13:33:35 +08:00
8db030d36e fix: wireguard tcp close need long time 2023-04-26 11:05:59 +08:00
d8db25ee89 fix: domain-set wildcard match 2023-04-26 02:49:16 +00:00
e1af1abcc2 fix: wireguard auto close not working 2023-04-26 09:34:36 +08:00
787e5ea28d chore: version print error 2023-04-26 01:13:52 +00:00
7bb5da3005 chore: support splice for direct outbound 2023-04-25 23:01:05 +08:00
efcb278f61 chore: safe sing-mux close 2023-04-24 10:30:12 +08:00
72a67ac534 chore: force set SelectAble when start load cache 2023-04-24 08:07:17 +08:00
7f1b7e7521 fix: smux should show its support udp and uot 2023-04-23 21:50:42 +08:00
0c61057551 feat: add statistic and only-tcp options for smux 2023-04-23 20:55:42 +08:00
0c146bd04c doc: update smux 2023-04-23 20:10:58 +08:00
7ca4b64a2b feat: add proxy and sing-based listener support sing-mux 2023-04-23 19:57:54 +08:00
aa6fa7f1e3 chore: cleanup unneeded deadline 2023-04-22 19:17:45 +08:00
40da1911d9 chore: using sync/atomic replace uber/atomic 2023-04-22 15:37:57 +08:00
c6ecbb25dc chore: update doc 2023-04-21 08:49:03 +00:00
00939da40f chore: update wireguard-go 2023-04-20 13:46:21 +08:00
7513761540 fix: not match top domain 2023-04-20 05:45:22 +00:00
ec234ac0a8 chore: clear windows bind error 2023-04-20 10:22:51 +08:00
31095e4a8c fix: vless udp not working 2023-04-20 09:38:08 +08:00
dd60baf9d4 fix: revert LRU cache changes 2023-04-19 23:50:57 +08:00
4ef99294e7 chore: rewrite verifyIP6 2023-04-19 19:25:21 +08:00
47df97322d fix: h2 close panic 2023-04-17 23:42:15 +08:00
f100ce6a04 chore: Adopt sing-tun's update 2023-04-17 20:38:37 +08:00
cae9cf44cf chore: Update dependencies 2023-04-17 19:46:57 +08:00
495033270c chore: using new chan based deadline reader 2023-04-17 19:29:07 +08:00
4a0d097fe9 fix: ensure StreamWebsocketConn call N.NewDeadlineConn 2023-04-17 00:23:12 +08:00
8e5dbc7382 chore: Update dependencies 2023-04-15 16:56:43 +08:00
c2d1f71305 fix: ruleProvider panic 2023-04-14 19:08:41 +08:00
81bef30ae0 chore: clean up docs 2023-04-14 10:04:50 +00:00
e4926c8364 feat: ruleset support text format 2023-04-14 13:51:26 +08:00
b3794ffdd8 fix: deadline pipeReadBuffer, pipeReadFrom and panic when alloc empty buffer 2023-04-13 19:37:33 +08:00
394889258c fix: wireguard reconnect failed 2023-04-13 17:01:01 +08:00
38a85272c2 fix: vless tcp not working 2023-04-13 11:10:35 +08:00
dbffbca6f5 doc: update config.yaml 2023-04-12 23:55:51 +08:00
cd42e9832c chore: resolver priority return TypeA in ResolveIP (not effected LookupIP) 2023-04-12 22:06:21 +08:00
cb0c9e5caf chore: udp always direct pass ip to remote without domain 2023-04-12 21:49:22 +08:00
aaf534427e chore: close all connections after proxySet initial 2023-04-12 18:50:51 +08:00
5d7fd47cf9 fix: CGO build failed on darwin-10.16 2023-04-12 18:27:22 +08:00
42721f3b75 fix: proxyDialer has a non-nil interface containing nil pointer judgment 2023-04-12 18:19:59 +08:00
836615aac9 fix: Converter panic on bad VMess share links 2023-04-12 14:40:38 +08:00
e745755a46 fix: direct outbound not ensure ip was resolved 2023-04-12 12:57:59 +08:00
20eb168315 fix: proxyDialer panic when domain name was not resolved 2023-04-12 12:49:53 +08:00
17922dc857 chore: proxyDialer first using old function to let mux work 2023-04-12 11:09:31 +08:00
fda8857ec8 feat: proxy-provider can set dialer-proxy too
it will apply `dialer-proxy` to all proxy in this provider
2023-04-12 10:39:24 +08:00
bad7340a4e chore: proxyDialer don't push flow to manager in statistic 2023-04-11 23:58:56 +08:00
7beb09153e chore: proxyDialer can add inner conn to statistic 2023-04-11 21:42:16 +08:00
90f95d7c78 chore: wireguard dns can work with domain-based server 2023-04-11 14:10:57 +08:00
92cc268209 chore: proxyDialer can limited support old dial function 2023-04-11 12:51:24 +08:00
ab3fce29ab feat: wireguard add remote-dns-resolve and dns settings 2023-04-11 10:29:55 +08:00
ecdde647b1 chore: cleanup listener before restart 2023-04-10 21:13:23 +08:00
304b4d9bcb chore: download geoX use inner 2023-04-10 21:03:31 +08:00
4d12ed491c fix: tuic pool client should only cache the system's UDPConn 2023-04-10 12:22:12 +08:00
9afcb7071f feat: support dialer-proxy config for all outbound 2023-04-10 11:20:28 +08:00
2c48d2da3f chore: clarify the wireguard logging 2023-04-10 10:13:36 +08:00
87b9e3d977 feat: wireguard add dialer-proxy config to support chain forwarding 2023-04-10 08:54:10 +08:00
1dbefc40c8 chore: better error ignore 2023-04-09 23:06:56 +08:00
6c76312e5c chore: Add read deadline implementation 2023-04-09 22:58:05 +08:00
20b0af9a03 chore: fix build 2023-04-09 19:17:44 +08:00
9e6b4dca97 Merge pull request #493 from rookisbusy/Alpha
fix: tun warn timeout
2023-04-09 19:03:10 +08:00
99dfa4c73a fix: tun warn timeout 2023-04-09 19:00:45 +08:00
8e8cddf462 chore: Update dependencies 2023-04-09 15:40:17 +08:00
e9c9d17a9b chore: init gopsutil's Process direct from struct 2023-04-08 09:56:02 +08:00
2c09ce44f6 feat: urltest can be select by user 2023-04-08 02:04:16 +08:00
89d5695b7f Merge pull request #492 from rookisbusy/Alpha
fix: chat.js not begin with zero
2023-04-08 01:42:04 +08:00
8fb2c68722 fix: chat.js not begin with zero 2023-04-08 01:39:48 +08:00
c4c7c56684 Merge pull request #491 from rookisbusy/Alpha
feat: core support memory chat
2023-04-08 01:07:25 +08:00
76340cc99c feat: core support memory chat 2023-04-08 00:55:25 +08:00
eecd8fff71 feat: add memory status for snapshot 2023-04-07 23:53:46 +08:00
36b3581389 Alpha (#490)
* feat: add memory status for snapshot

* feat: add memory status for snapshot
2023-04-07 22:55:01 +08:00
eb07aafce8 doc: update config.yaml 2023-04-07 22:37:01 +08:00
8ab70d76e7 Fix: should always drop packet when handle UDP packet (#2659) 2023-04-06 09:38:59 +08:00
f92f34bb20 Fix: return pooled buffer when simple-obfs tls read error (#2643) 2023-04-06 09:32:27 +08:00
ae722bb1a0 chore: Add early bounds checks 2023-04-05 13:51:50 +08:00
ee5c4cb83b fix: tuic fast-open not work 2023-04-03 21:08:12 +08:00
bf31abee22 chore: clean up code 2023-04-03 12:03:18 +00:00
2fc37501f5 chore: update demo 2023-04-03 16:14:07 +08:00
7308c6c2a9 feat: Add multi-peer support for wireguard outbound 2023-04-03 16:14:07 +08:00
99f84b8a66 chore: make all net.Conn wrapper can pass through N.ExtendedConn 2023-04-02 22:24:46 +08:00
2ff0f94262 chore: sync sing-wireguard's update 2023-04-02 17:29:30 +08:00
affc453b6e chore: better upgrade 2023-04-02 15:16:42 +08:00
526ac3906d chore: Update dependencies 2023-04-02 13:15:41 +08:00
cd95cf4849 fix: firstWriteCallBackConn can pass N.ExtendedConn too 2023-04-01 20:56:49 +08:00
4af4935e7e fix: Vision slice out of bounds error 2023-04-01 20:08:49 +08:00
9442880a5a chore: rule-provider direct using IndexByte in bytes for find new line 2023-04-01 16:55:16 +08:00
9d7a78e1ef chore: update use compatible version for windows/linux amd64 2023-04-01 15:16:45 +08:00
54c2fa98b4 chore: rule-provider now read yaml line-by-line 2023-04-01 14:11:09 +08:00
54cad53f5f chore: DomainSet now build from a DomainTrie 2023-04-01 12:15:03 +08:00
cfd03a99c2 feat: nameserver-policy support use rule-providers and reduce domain-set memory 2023-04-01 11:53:39 +08:00
991de009be chore: update readme 2023-03-30 15:57:52 +00:00
2fef329319 fix: upgrade backup 2023-03-29 13:59:36 +00:00
db7623968d fix: inner http use host of address 2023-03-29 20:50:46 +08:00
7c80c88feb chore: push latest alpha core to MetaCubeX/AlphaBinary 2023-03-29 12:28:16 +00:00
2c7153cd7a chore: clean up code 2023-03-29 16:19:26 +08:00
d730feecb4 chore: use inner for upgrade core 2023-03-29 06:03:13 +00:00
1fdd1f702e chore: better rename 2023-03-28 17:00:21 +00:00
34c91e5fe0 chore: add release branch 2023-03-28 16:40:45 +00:00
6d40de2179 chore: adjust trust cert 2023-03-27 22:27:59 +08:00
6ca14c814e fix: tproxy listener cannot listen udp 2023-03-27 22:18:54 +08:00
545cbeeec0 chore: skip restart when update error 2023-03-27 00:49:47 +08:00
431dcfa914 fix: Converter REALITY security type 2023-03-26 11:03:32 +08:00
4d30788738 chore: clean up code 2023-03-25 22:56:24 +08:00
e4364cc985 chore: update for testing the updater 2023-03-23 21:04:04 +08:00
99ede63a9a feat: add upgrade api
example: curl -X POST -H "Authorization: Bearer 123456" http://ip:port/upgrade
2023-03-23 20:48:20 +08:00
291b5be986 chore: move sing-tun's udpTimeout fix to there lib 2023-03-23 19:53:28 +08:00
a7944f1369 chore: better geodata shared 2023-03-23 18:58:24 +08:00
7e10d78d53 chore: share the same geodata in different rule 2023-03-23 18:35:37 +08:00
fd0580bfdd fix: sing_tun apply udpTimeout when using gvisor stack 2023-03-23 14:05:31 +08:00
5737fbc23c chore: proxy-server-nameserver does not follow the nameserver-policy 2023-03-23 12:58:59 +08:00
e026ac6a2a chore: update xray-core version 2023-03-22 23:45:26 +08:00
e7bb1f42b1 chore: update quic-go to release unused buffer when error 2023-03-22 13:01:58 +08:00
a22000c41b Update README.md 2023-03-21 23:56:40 +08:00
0336435ebc chore: shadowsocks listener support the "udp" setting 2023-03-21 12:40:36 +08:00
154fbb34ea fix: log typo 2023-03-21 00:45:25 +08:00
3e47bfacf0 feat: Converter support REALITY share standard 2023-03-19 17:31:52 +08:00
9316c1293e fix: geosite of nameserver-policy cannot be loaded correctly 2023-03-18 22:33:39 +08:00
f6f02bb5eb fix: ToLower first 2023-03-18 22:20:31 +08:00
84967ace53 Update flake.nix (#452) 2023-03-18 19:55:29 +08:00
c7362fce9c chore: do not modify ALPN in utls 2023-03-17 14:49:42 +08:00
8cb67b6480 Update UoT protocol 2023-03-17 13:23:45 +08:00
3ae4285702 fix: tuic udp native mode can't relay packetSize>1200 2023-03-16 21:09:44 +08:00
998d407d44 Feat: support set tun file-descriptor in config file
Co-authored-by: DuFoxit <DuFoxit@users.noreply.github.com>
2023-03-15 23:43:58 +08:00
520cc807d6 chore: Update dependencies 2023-03-15 18:58:59 +08:00
7404bfdc44 chore: Improve REALITY handshake 2023-03-15 15:55:18 +08:00
e8d4f8ae7b Update UoT protocol 2023-03-15 14:46:35 +08:00
f7610ce2ad chore: better uuid using 2023-03-15 10:10:03 +08:00
516c219580 fix: let quic-go works on outbound's packetConn 2023-03-15 09:56:00 +08:00
5d0efb5472 chore: keep existing connections 2023-03-15 09:18:03 +08:00
9d9bd245f3 fix: Adjust the timing of subscription information acquisition 2023-03-15 09:05:16 +08:00
53928eb895 chore: better TunnelStatus define 2023-03-15 00:11:31 +08:00
8dda9fdb70 fix: The default interface is actually configured incorrectly 2023-03-14 23:52:27 +08:00
cf7520ec22 chore: disconnect when suspended 2023-03-14 22:57:43 +08:00
68d7a6da7f fix: ensure restart api return ok 2023-03-14 22:38:59 +08:00
09c53e7cb7 chore: Chore: adjust the loading order, and then load the resource at last 2023-03-14 22:37:07 +08:00
0f24c2f849 chore: add /restart to restful api 2023-03-14 22:19:12 +08:00
88116d9032 fix: optimize health check 2023-03-14 20:10:52 +08:00
6ba82c6d85 chore: cleanup code 2023-03-14 17:45:37 +08:00
5816dc2955 chore: better restls 2023-03-14 16:50:27 +08:00
f4251e58a5 chore: clean up code 2023-03-14 14:23:10 +08:00
2fef00d2a8 Support Restls-V1 in Clash.Meta (#441)
* feat: impl restls

* fix: don't break shadowtls' working

* chores: correct restls-client-go version

* feat: bump restls-client-go version

* fix: consistent naming `client-fingerprint`

* docs: update example config

* chore: adjust client-fingerprint's snippet

---------

Co-authored-by: wwqgtxx <wwqgtxx@gmail.com>
Co-authored-by: Larvan2 <78135608+Larvan2@users.noreply.github.com>
2023-03-14 13:33:24 +08:00
2f992e9863 chore: fix issues #440 2023-03-13 21:19:39 +08:00
13111081be fix: SA4001 for net.UDPAddr copy 2023-03-12 23:37:45 +08:00
5de043acc6 fix: tuic relay tuic 2023-03-12 19:03:18 +08:00
7d230139a0 fix: rand ip error and clash remove loopback ip 2023-03-12 18:44:30 +08:00
0a6c848c9e feat: nameserver-policy support multiple keys
e.g.,
  nameserver-policy: #   'www.baidu.com': '114.114.114.114'
    #   '+.internal.crop.com': '10.0.0.1'
    "geosite:cn,private,apple":
      - https://doh.pub/dns-query
      - https://dns.alidns.com/dns-query
    "www.baidu.com,+.google.cn":
      - 223.5.5.5
      - 1.1.1.1
2023-03-12 16:56:29 +08:00
074fee2b48 chore: add comment 2023-03-12 15:05:28 +08:00
7f588935ea feta: add hosts support domain and mulitple ip (#439)
* feat: host support domain and multiple ips

* chore: append local address via `clash`

* chore: update hosts demo

* chore: unified parse mixed string and array

* fix: flatten cname

* chore: adjust logic

* chore: reuse code

* chore: use cname in tunnel

* chore: try use domain mapping when normal dns

* chore: format code
2023-03-12 15:00:59 +08:00
4b72ae7aab fix: global-client-fingerprint is now work 2023-03-12 13:35:59 +08:00
09b4a7ff15 chore: Remove useless mutex in Vision 2023-03-12 10:13:23 +08:00
7944522188 chore: update quic-go 2023-03-12 09:39:13 +08:00
ae4d114802 chore: Cleanup REALITY code 2023-03-11 12:23:27 +08:00
07f3cd2ae5 chore: exposure ipv6 wait time 2023-03-10 23:38:16 +08:00
035d878a9f fix: dial panic 2023-03-10 22:08:01 +08:00
913ed62095 fix: ALPN not applied in uTLS/REALITY 2023-03-10 20:53:39 +08:00
8c135e4a91 chore: adjust log 2023-03-10 20:48:18 +08:00
5bcfe1a6c6 fix: dialer dual stack panic 2023-03-10 20:16:14 +08:00
2ccef31f75 fix: ensure wireguard inner use dialer with DefaultResolver 2023-03-10 17:00:39 +08:00
2c4783ff8b fix: SA4001 for netDialer copy 2023-03-10 16:17:54 +08:00
7cc1c1b561 chore: adjust error log 2023-03-10 14:12:18 +08:00
d309c6311d chore: add reality-grpc 2023-03-10 14:10:28 +08:00
e7f9072911 fix: add xtls-rprx-vision server version warning to user 2023-03-10 12:54:43 +08:00
fe298bd53f fix: strategyRoundRobin not begin with zero 2023-03-10 12:47:01 +08:00
6fe7f4641e fix: tuic server set authentication timeout after quic handshake complete 2023-03-10 12:26:17 +08:00
9ae0bd9c2b fix: don't return a non-nil interface containing nil pointer 2023-03-10 12:06:53 +08:00
dca98b7aa1 fix: REALITY with gRPC transport 2023-03-10 10:01:05 +08:00
c0fc5d142f fix: unmap 4in6 address in dialer and wireguard 2023-03-10 00:25:22 +08:00
a973a6c7d2 chore: update utls library 2023-03-09 12:33:29 +08:00
a454a7f736 fix: load-balance's touch not effected 2023-03-09 11:09:36 +08:00
bae61a8152 fix: tuic server close with error message 2023-03-09 10:41:24 +08:00
0c984644b4 chore: parse the allowInsecure field for the trojan uri scheme 2023-03-09 01:31:43 +08:00
921b2c3aa4 feat: REALITY use proxy servername 2023-03-08 20:28:12 +08:00
8ba7ce73d8 Update config.yaml 2023-03-08 19:12:51 +08:00
76a8fe3839 feat: Support REALITY protocol 2023-03-08 17:18:46 +08:00
1e6f0f28f6 chore: change default geo* url 2023-03-08 00:19:20 +08:00
6040803b60 chore: do not apply padding for nonTLS packet with contentLen over 900 2023-03-07 16:35:19 +08:00
04ae812a11 chore: try to fix slice out of bound. 2023-03-07 15:52:50 +08:00
a0ad12c45b fix: sing-vmess listener‘s "cipher: message authentication failed" 2023-03-07 14:11:38 +08:00
9cc7fdaca9 chore: wireguard using internal dialer 2023-03-07 09:30:51 +08:00
545a79d406 chore: cleanup dialer's code 2023-03-06 23:23:05 +08:00
7c34964f87 fix: dns resolver 2023-03-06 19:15:12 +08:00
6a97ab9ecb chore: use fastrand to replace math/rand 2023-03-06 18:10:14 +08:00
ad6336231c doc: update config.yaml 2023-03-06 12:59:53 +08:00
2f36c9d66d chore: better workflow 2023-03-06 00:49:34 +08:00
ae966833a4 chore: Generate UUID from fastrand 2023-03-05 11:00:14 +08:00
3b037acb01 chore: Update dependencies 2023-03-04 23:41:56 +08:00
8771fa5c17 chore: Vision padding upgrade 2023-03-04 21:47:42 +08:00
bccc6aa8d1 chore: Better REJECT conn 2023-03-04 18:33:05 +08:00
da27be6de5 chore: add sni of tuic in demo 2023-03-04 09:44:36 +08:00
838c5c7770 ci: set prerelease notes timezone of release create time to Asia/Shanghai 2023-03-02 15:09:40 +08:00
02395413ba chore: better release notes 2023-03-02 14:39:57 +08:00
4c1682b365 chore: better release notes 2023-03-02 00:24:39 +08:00
e7613e4f8b fix: loadbalance panic 2023-03-01 14:04:42 +08:00
685fd49dd7 chore: better workflow 2023-03-01 13:41:25 +08:00
6061f3d4fa chore: add more utls fingerprints 2023-02-28 21:17:52 +08:00
d55025ecae fix: udp loopback show "The requested address is not valid in its context." 2023-02-28 15:53:34 +08:00
e3e0e9796b chore: better workflow 2023-02-27 23:02:45 +08:00
ecb2a5f3c6 adjust: Simplify VLESS handshake lock 2023-02-27 12:02:44 +08:00
76ccebf099 chore: better REJECT process 2023-02-27 09:46:16 +08:00
78100aa963 fix: vless NeedHandshake mistake 2023-02-27 09:46:00 +08:00
0b56fc7598 fix: Vision filter TLS 1.2
Add magic from sing-box. 5ce3ddee9b/transport/vless/vision.go (L199)
2023-02-27 01:24:36 +08:00
c1199f1a8a chore: add early conn interface to decrease unneeded write 2023-02-27 00:26:49 +08:00
c8c078e78a fix: golang1.19 can't compile 2023-02-26 22:20:25 +08:00
d36f9c2ac8 fix: handle no IP address 2023-02-26 21:01:44 +08:00
e1dd4ac9e7 chore: format code 2023-02-26 20:38:32 +08:00
e6a35199e0 fix: dual stack serial dial 2023-02-26 20:15:28 +08:00
3cd1c92162 fix: uot client's WriteTo mistake 2023-02-26 15:59:34 +08:00
be5ce6249f fix: dns resolve in dialer 2023-02-26 13:52:10 +08:00
0321fe93cf fix: replace self define "connect timeout" to os.ErrDeadlineExceeded 2023-02-26 13:06:10 +08:00
2cbfac2c89 fix: Filter slice index out of bounds 2023-02-26 13:04:12 +08:00
0a6705f43e fix: ip version prefer not working 2023-02-26 12:39:53 +08:00
97e14337e3 refactor: tcp dial (#412)
Non-concurrent support to try to connect in turn

fix: serial dual stack dial
2023-02-26 12:12:25 +08:00
5e7d644efd fix: ensure peekMutex is locked before handleSocket 2023-02-26 11:18:01 +08:00
40ae019e1d fix: Vision filter TLS 1.2 2023-02-26 11:13:47 +08:00
efbde4a179 fix: reject's dial warning 2023-02-26 11:11:54 +08:00
f565edd76d chore: add custom ca trust 2023-02-25 22:01:20 +08:00
a3b8c9c233 fix: peek not work with some inbound 2023-02-25 19:41:14 +08:00
de92bc0234 fix: Vision filter Client Hello 2023-02-25 19:11:23 +08:00
e6377eac9b chore: adjust config.yaml. 2023-02-25 17:26:13 +08:00
22726c1de8 fix: add version of shadow-tls plugin in docs/config.yaml 2023-02-25 15:05:36 +08:00
bce3aeb218 fix: Vision disable filter for non-TLS connections 2023-02-25 15:00:21 +08:00
81722610d5 feat: Support VLESS XTLS Vision (#406) 2023-02-25 14:01:59 +08:00
5bfad04b41 fix: checkTunName mistake 2023-02-24 14:58:01 +08:00
880664c6ab fix: tunnel's inboundTFO missing 2023-02-24 14:19:50 +08:00
8f0c61ed14 fix: tuic missing routing mark 2023-02-24 14:02:20 +08:00
7d524668e0 chore: support TFO for outbounds 2023-02-24 13:53:44 +08:00
75680c5866 chore: use early conn to support real ws 0-rtt 2023-02-24 09:54:54 +08:00
a1d008e6f0 chore: add pprof api, when log-level is debug 2023-02-23 23:30:53 +08:00
d5d62a4ffd chore: change internal tcp traffic type 2023-02-23 20:26:25 +08:00
b72bd5bb37 chore: adjust the configuration loading order 2023-02-23 14:13:27 +08:00
7fecd20a1d chore: adjust the configuration loading order 2023-02-22 23:45:18 +08:00
f586f22ce3 fix: incorrect time to set interface name 2023-02-22 21:08:08 +08:00
21848d6bf1 chore: code cleanup 2023-02-22 19:43:32 +08:00
28c57c4144 chore: Update dependencies 2023-02-22 19:35:43 +08:00
4a6ebff473 fix: add "dns resolve failed" error in dialer 2023-02-22 19:14:11 +08:00
5c8d955f61 chore: better windows bind error handle 2023-02-22 13:41:33 +08:00
baaf509637 chore: using sing-shadowtls to support shadowtls v1/2/3 2023-02-21 21:58:37 +08:00
db3e1b9ed5 feat: add sni field for tuic 2023-02-19 16:20:30 +08:00
1a1e3345f4 chore: reset tunName in macos when it isn't startWith "utun" 2023-02-19 10:10:27 +08:00
527fc2790b chore: combine workflows 2023-02-19 01:23:06 +08:00
cd7d9fc4f5 fix: socks5 serialize error #376 2023-02-18 17:18:58 +08:00
a61685ce01 fix: disable header protection in vmess server 2023-02-18 16:42:54 +08:00
b9e63d3f7d fix: ensure return a nil interface not an interface with nil value 2023-02-18 14:16:03 +08:00
cc3a9dd553 fix: websocket headroom 2023-02-18 13:58:08 +08:00
6a89cc15c3 chore: Considering remove GOAMD64=v2 of linux-amd64-compatible
close https://github.com/MetaCubeX/Clash.Meta/issues/391
2023-02-18 13:32:26 +08:00
fc50392ec7 chore: cleanup natTable's api 2023-02-18 13:16:07 +08:00
59cd89a9c9 fix: parsing ipv6 doh error 2023-02-17 23:30:38 +08:00
d6ff5f7d96 style: run go fmt on every .go file (#392) 2023-02-17 16:31:37 +08:00
8e4dfbd10d feat: introduce a new robust approach to handle tproxy udp. (#389) 2023-02-17 16:31:15 +08:00
b2d1cea759 fix: RoundRobin strategy of load balance when called multiple times (#390) 2023-02-17 16:31:00 +08:00
6fe1766c83 chore: add log 2023-02-17 13:48:29 +08:00
e59c35a308 fix issue #357.
Copy from upstream.
2023-02-16 21:14:27 +08:00
b50071ed37 chore: better log time 2023-02-15 22:39:28 +08:00
28c7de6185 fix: avoid modifying the request message id 2023-02-14 21:09:37 +08:00
6fb4ebba15 chore: Allow 0-RTT in Tuic server
refers to: https://github.com/quic-go/quic-go/pull/3635
2023-02-13 23:52:15 +08:00
d00d83abd4 fix: tun udp with 4in6 ip 2023-02-13 22:06:09 +08:00
e6d16e458f chore: update gvisor 2023-02-13 20:50:11 +08:00
ae42d35184 chore: support golang1.20's dialer.ControlContext 2023-02-13 11:14:19 +08:00
ce8929d153 chore: better bind in windows 2023-02-13 10:14:59 +08:00
cc2a775271 feat: Converter support uTLS fingerprint field 2023-02-11 16:40:01 +08:00
83d719cf79 fix: VLESS handshake write 2023-02-11 15:13:17 +08:00
4643b5835e chore: setting sniffHost value 2023-02-10 13:01:53 +08:00
a991bf9045 fix: missing sniffhost field in RESTful API 2023-02-10 12:48:02 +08:00
3fd3d83029 feat: Attempts to send request with first payload on VLESS 2023-02-10 10:03:37 +08:00
24419551a9 chore: update tfo-go for golang1.20 2023-02-08 13:10:44 +08:00
c83eb2e0c9 chore: adjust log 2023-02-07 21:29:40 +08:00
929b1675e3 chore: avoid repeated wrapper 2023-02-07 21:29:40 +08:00
db54b438e6 chore: do not use extra pointer in UClient 2023-02-07 17:51:37 +08:00
967254d9ca chore: move global-utls-client snippets to components\tls 2023-02-07 17:24:14 +08:00
2d806df9b9 fix: sniff domain don't match geosite when override-destination value is false 2023-02-07 15:59:44 +08:00
4fe798ec3b chore: update sing-vmess 2023-02-07 15:10:36 +08:00
3555ff5f4e chore: update docs/config.yml 2023-02-07 13:19:19 +08:00
05ca819823 feat: add global-client-fingerprint.
* Available: "chrome","firefox","safari","ios","random","none".
* global-client-fingerprint will NOT overwrite the proxy's client-fingerprint setting when "client-fingerprint: none".
2023-02-07 01:26:56 +08:00
c8b8b60b93 chore: override-destination default value is true 2023-02-06 17:48:49 +08:00
287986d524 Update README.md 2023-02-05 23:36:08 +08:00
4c25f5e73b feat: Update utls support.
* client-fingerprint is used to apply Utls for modifying ClientHello, it accepts "chrome","firefox","safari","ios","random" options.
* Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan.
2023-02-05 17:34:37 +08:00
cbc217e80a fix: Converter Shadowsocks password parse 2023-02-04 16:58:17 +08:00
fe348e89c5 chore: add nameserver-policy demo 2023-02-03 21:41:26 +08:00
e1e1984d3e feat: nameserver policy support multiple server 2023-02-03 21:40:05 +08:00
99662b616f fix: tuic listener config name 2023-02-02 21:48:20 +08:00
857d6e419f fix: Parse CC fail in tuic. 2023-02-02 21:29:12 +08:00
a298b9ea01 chore: fix mips atomic panic 2023-02-02 21:03:24 +08:00
61097d0826 chore: update to golang1.20 2023-02-02 15:39:57 +08:00
2ee0f634e6 feat: Add utls for modifying client's fingerprint.
Currently only support TLS transport in TCP/GRPC/WS/HTTP for VLESS/Vmess and trojan-grpc.
2023-02-01 22:36:05 +08:00
61b3b4f775 fix: Handle error earlier in DialContextWithDialer.
chore: Fix typo.
2023-01-31 18:05:46 +08:00
dc4b9753d3 Merge pull request #360 from tgNotHouse/Alpha
fix: get tlsconfig err not handle, return nil pointer #358
2023-01-31 15:40:44 +08:00
f1ef6c2096 fix: get tlsconfig err not handle, return nil pointer 2023-01-31 15:26:18 +08:00
872c915cf7 Chore: Add images for wiki 2023-01-30 21:19:46 +08:00
fb9f09c97f Update README.md 2023-01-30 20:39:13 +08:00
884db8a8b5 chore: add patch for debug api,better workflow. 2023-01-30 20:19:44 +08:00
ee21b7bc37 chore: update gvisor 2023-01-29 22:30:40 +08:00
32c53b9584 chore: dns log error 2023-01-29 11:03:39 +08:00
4e5f3fbe84 Merge pull request #356 from kunish/Alpha
Chore: Remove missing image link, mention Yacd-meta in README.md
2023-01-29 00:24:37 +08:00
2ce193877b docs(README.md): remove missing image link, mention Yacd-meta 2023-01-29 00:17:33 +08:00
e52d599326 chore: better dns log 2023-01-28 22:33:03 +08:00
2cf66f41cb fix: parse error 2023-01-28 16:09:14 +08:00
a06b387acc adjust: VLESS enable XUDP by default 2023-01-28 14:58:52 +08:00
03520e0d6f Fix: dns api panic on disable dns section (#2498) 2023-01-28 00:55:30 +08:00
a6a72a5b54 Feature: add dns query json api 2023-01-28 00:55:24 +08:00
85db58aeb5 chore: update config.yaml 2023-01-28 00:32:17 +08:00
596bf32caa chore: adjust keyword for geosite-based nameserver policy 2023-01-28 00:19:58 +08:00
2b2644a76f chore: restful api display xudp for VLESS and VMess 2023-01-28 00:07:20 +08:00
02684a868f feature: geosite-based nameserver policy 2023-01-27 23:40:53 +08:00
1924b308fd chore: clear code 2023-01-27 17:10:15 +08:00
0d62e42c50 chore: better parsing pure UDP DNS 2023-01-27 17:02:58 +08:00
d3193cf8b7 Chore: Better parsing pure IPv6 UDP DNS 2023-01-27 15:08:05 +08:00
f7538568c0 Chore: Change default latency test url to HTTPS. 2023-01-27 13:41:23 +08:00
4629ecb8ee Chore: Add GEO data url configuration. 2023-01-27 13:27:39 +08:00
5bcea37d59 chore: better parse udp dns 2023-01-27 13:07:52 +08:00
6decaef050 fix: sub-rule condition don't work 2023-01-27 12:38:15 +08:00
248578086f feat: Converter support WS early data parameters 2023-01-27 11:31:58 +08:00
87553c6aa0 Update config.yaml 2023-01-26 23:19:33 +08:00
a2aa267e43 chore: update workflows docker 2023-01-25 20:53:39 +08:00
a563e9375e chore: better source address 2023-01-25 13:00:18 +08:00
9a4be1fbec Chore: Action ignore docs/**,README.md when push. 2023-01-24 21:56:17 +08:00
80f48518ca Chore: Update config.yaml 2023-01-24 21:50:21 +08:00
16c4b55e31 Chore: Decrease the default MaxUdpRelayPacketSize to 1252 to avoid the relay UDP exceeding the size of the QUIC's datagram.
ClientMaxOpenStreams now follows the config.yaml option.
2023-01-24 21:48:31 +08:00
023a96a6d3 make ConvertsV2Ray more robust (#349)
* make ConvertsV2Ray more  robust
* add log
* fix
2023-01-24 16:34:52 +08:00
39394e49ae chore: update config.yaml 2023-01-23 14:51:25 +08:00
b54ddc3aa9 chore: update config.yaml 2023-01-23 14:19:13 +08:00
97537bd185 chore: update config.yaml 2023-01-23 14:14:18 +08:00
1225173a43 chore: update config.yaml 2023-01-23 14:12:53 +08:00
096bb8d439 feat: add override-destination for sniffer 2023-01-23 14:08:11 +08:00
df1f6e2b99 feat: better config for sniffer 2023-01-23 13:16:25 +08:00
d1f5bef25d chore: better log 2023-01-23 11:17:30 +08:00
d426db43ec chore: adjust log 2023-01-23 11:14:45 +08:00
3bace07948 fix: ipv6 logic 2023-01-21 22:31:07 +08:00
24e31d0a40 Chore: Update paths-ignore 2023-01-21 14:42:48 +08:00
fb623c0929 chore: Correct the decision of enabling find process 2023-01-21 14:27:09 +08:00
4f641ce12d fix: ShadowTLS header use array instead 2023-01-20 17:35:49 +08:00
8cd1e40fb3 Update README.md 2023-01-20 17:13:32 +08:00
8a7027e8d6 Fix: Remove EnableProcess from config.go and enable-process from config.yaml.
Fix: FindProcess is now enabled by default when the rule set contains process-name rules.
2023-01-20 16:29:08 +08:00
5bbf73e3b5 chore: new Random TLS KeyPair when empty input 2023-01-18 12:06:36 +08:00
106a58779d chore: update quic-go 2023-01-17 22:06:21 +08:00
fa5b5ca02d fix: tcpTracker's upload 2023-01-17 21:36:16 +08:00
ba6163574e chore: better parseAddr 2023-01-17 15:41:51 +08:00
37eca8af24 fix: tuic server's MaxIncomingStreams 2023-01-17 14:25:19 +08:00
421c91a58c chore: update docker.yaml and Makefile docker 2023-01-17 12:43:51 +08:00
c90bf1c6e2 chore: Update const type 2023-01-17 12:33:15 +08:00
5b1de296af chore: Update dependencies 2023-01-17 12:26:31 +08:00
f4414566d3 fix: tuic server's SetCongestionController 2023-01-17 10:41:51 +08:00
db4f3eda55 fix: Add CC for TUIC server 2023-01-17 01:08:30 +08:00
f3b76df13b chore: Update BBR config
chore: Adjust workflow
2023-01-16 21:50:02 +08:00
bb79272020 chore: better workflow 2023-01-16 16:44:47 +08:00
926ef9e33d feat: gRPC gun implement extended writer 2023-01-16 15:54:20 +08:00
ead21f37d7 chore: better workflow 2023-01-16 15:09:25 +08:00
49a2602329 fix: add Upstream to refconn 2023-01-16 13:26:30 +08:00
e88bddc24f fix: addr panic 2023-01-16 12:47:22 +08:00
a5821e5785 fix: add ReaderReplaceable to BufferedConn, avoid buffered data lost 2023-01-16 12:28:30 +08:00
4e4d741075 chore: code cleanup 2023-01-16 12:11:34 +08:00
bec66e9e69 adjust: Improve WebSocket mask 2023-01-16 11:42:10 +08:00
50832aab47 chore: decrease direct depend on the sing package 2023-01-16 10:50:31 +08:00
643fdd0bce chore: tuic decrease unneeded copy 2023-01-16 09:55:06 +08:00
d1565bb46f refactor: Implement extended IO 2023-01-16 09:42:03 +08:00
8fa66c13a9 chore: better workflow 2023-01-15 21:51:33 +08:00
c0ffa06b95 chore: Update dependencies 2023-01-15 15:04:58 +08:00
3b53f5bca3 chore: better workflow 2023-01-15 15:04:27 +08:00
2c80155c6f Update Makefile
add CGO support for release build
add release.sh
2023-01-15 02:08:46 +08:00
8a9b3b3d59 fix: config parse error 2023-01-14 22:34:54 +08:00
5dd691aa95 fix: ss converter cipher missing 2023-01-14 21:37:10 +08:00
27ceae580a chore: update config.yaml 2023-01-14 21:34:26 +08:00
b6b6413d04 refactor: replace experimental.fingerprints with custom-certificates and Change the fingerprint verification logic to SSL pinning 2023-01-14 21:08:06 +08:00
2095f4f670 chore: update gitignore 2023-01-14 18:10:22 +08:00
606e8948c0 Fix: TLS defaults to true for h2/grpc networks 2023-01-14 16:20:58 +08:00
3b6fc1c496 chore: adjust the case of Program names and HttpRequest UA 2023-01-14 16:17:10 +08:00
f96bf65557 chore: Refine process code 2023-01-14 16:16:59 +08:00
804cff8c55 fix: skip-cert-verify is true by default (#333)
* fix: skip-cert-verify is true by default

* fix: format

* fix: typo

Co-authored-by: 3andero <3andero@github.com>
Co-authored-by: Hellojack <106379370+H1JK@users.noreply.github.com>
2023-01-13 09:55:01 +08:00
633b9c0426 chore: adjust Dockerfile 2023-01-12 02:13:22 +08:00
7d6991dd65 chore: adjust makefile 2023-01-12 01:31:38 +08:00
95247154d6 Fix: Deprecate TCPMSS (#336)
* 修改 DefaultTCPMSS 为 MaxDatagramSize
修改 MaxDatagramSize 的值提高 TUIC 的上传速度
2023-01-12 00:53:42 +08:00
be6142aa43 feat: VLESS support packet encodings (#334)
* adjust: Do not use XTLS on H2 connections

* feat: VLESS support XUDP fullcone NAT

* fix: VLESS with PacketAddr does not work

* fix: VLESS XUDP crash
2023-01-11 22:01:15 +08:00
0069513780 chore: shadowtls don't depend on trojan's code 2023-01-11 10:19:30 +08:00
0c9a23a53c fix: dns cache index out of range 2023-01-11 09:54:07 +08:00
0035fc2313 Update prerelease.yml 2023-01-11 00:50:04 +08:00
6f62d4d5c1 chore: update config.yaml 2023-01-11 00:28:21 +08:00
51f9b34a7c feat: Support ShadowTLS v2 as Shadowsocks plugin (#330) 2023-01-11 00:13:48 +08:00
337be9124f chore: clean code 2023-01-11 00:01:28 +08:00
dd4e4d7559 chore: ss2022 converter method verify 2023-01-10 21:55:36 +08:00
0f29c267be fix: Converter VMess XUDP not enabled by default when using v2rayN style share link 2023-01-10 20:47:58 +08:00
d38ceb78c9 chore: Refine converter packet encoding parse 2023-01-10 18:25:05 +08:00
0c354c748a fix: ss2022 converter password decode error 2023-01-10 18:13:18 +08:00
3a8e7c8899 chore: vemss converter xudp is true by default 2023-01-10 18:10:21 +08:00
261b8a1d06 fix: vmess udp 2023-01-10 13:21:32 +08:00
01d8b224db fix: vless RoutingMark bind 2023-01-09 23:15:17 +08:00
e9a7e104c0 fix: geoip mmdb/geodata init 2023-01-09 21:12:13 +08:00
b4503908df fix #322: add option general.find-process-mode, user can turn off findProcess feature in router
findProcess slow down connection due to repeat call to FindProcessName in router environment
this option has 3 values: always, strict, off
- always, equal to enable-process: true. Just try to merge all process related option into one
- strict, as default value, behavior remains unchanged
- off, turn off findProcess, useful in router environment
2023-01-09 19:48:39 +08:00
fd48c6df8a chore: Fix fmt in #321
Replace all double spaces to tabs due to Go fmt proposal.
2023-01-07 12:24:28 +08:00
cd7134e309 Merge pull request #321 from ag2s20150909/Alpha
proxy-provider and proxy-groups support exclude node by node type
2023-01-06 11:58:12 +08:00
5fa6777239 fix: Process rule is not work in classical rule-set 2023-01-04 21:18:07 +08:00
908d0b0007 Merge pull request #1 from ag2s20150909/fixConverter
fix converter error
2023-01-03 22:36:38 +08:00
8e6989758e fix converter error 2023-01-03 22:33:29 +08:00
29b72df14c proxy-groups support exclude node by node type 2023-01-03 21:47:57 +08:00
f100a33d98 proxy-provider support exclude node by node type 2023-01-03 21:27:07 +08:00
7a64c432b1 Merge branch 'dev' of https://github.com/Dreamacro/clash into Alpha 2023-01-01 13:03:30 +08:00
2301b909d2 Fix: immediately update provider when modtime too old 2022-12-31 16:32:30 +08:00
89680de12b fix: only wrap with SyscallConn() when currentConn implements syscall.Conn 2022-12-25 13:29:25 +08:00
a03af85a6b fix: trying to let hysteria's port hopping work 2022-12-23 11:00:55 +08:00
fbca37c42b Feature: REDIRECT support IPv6 (#2473) 2022-12-22 19:25:30 +08:00
4a57917783 Chore: skip cache acme challenge dns msg (#2469) 2022-12-22 13:30:23 +08:00
cdc7d449a6 Fix: safeConnClose not working (#2463) 2022-12-22 12:42:38 +08:00
daf0b23805 fix: some safeConnClose forget using original 2022-12-22 12:31:45 +08:00
d8ac82be36 Fix: broken build badge (#2470) 2022-12-22 12:09:24 +08:00
a6c144038b Chore: improve redir getorigdst 2022-12-22 12:00:56 +08:00
980454beb2 chore: cleanup code 2022-12-22 09:53:25 +08:00
63922f86a2 chore: linux ipv6 REDIRECT (#311) 2022-12-22 08:45:11 +08:00
22414ce399 chore: relay support tuic 2022-12-20 00:11:02 +08:00
7496d9c114 chore: rebuild relay 2022-12-19 21:34:07 +08:00
c63dd62ed2 chore: support relay native udp when using ss and ssr protocol 2022-12-19 17:02:04 +08:00
ff01d845b4 fix: try to fix gvisor panic 2022-12-17 11:03:37 +08:00
57592ee840 chore: better safeConnClose 2022-12-16 22:16:02 +08:00
432c4c2cf1 Merge pull request #302 from oluceps/fix-nix
Fix nix build fail
2022-12-15 13:54:45 +08:00
98b7377643 fix: spec Go 119 for nix build 2022-12-15 13:25:56 +08:00
287ec7e851 chore: update flake lock 2022-12-15 13:25:18 +08:00
8a2d1ec5a7 chore: better structure decoder 2022-12-13 21:13:31 +08:00
afb2364ca2 chore: wireguard's reserved support base64 input 2022-12-13 20:40:07 +08:00
9711390c18 fix: check conn is nil in safeConnClose 2022-12-13 18:09:19 +08:00
bffb0573a6 fix: safeConnClose not working 2022-12-13 13:20:40 +08:00
17cbbb5bf0 chore: split dns's dialContext and listenPacket 2022-12-13 12:38:46 +08:00
b3b5f17e03 chore: cleanup doh/doq's code 2022-12-13 11:23:34 +08:00
88acf8e098 fix: fix bindIfaceToListenConfig() in windows force bind to an ipv4 address 2022-12-13 11:18:32 +08:00
f87144f84b chore: add persistent-keepalive for wireguard 2022-12-13 08:35:01 +08:00
1333f1fd47 fix: fix wireguard outbound not work with ipv6 server 2022-12-13 08:23:17 +08:00
8fa6bd1743 fix: fix wireguard outbound not work with the sniffer 2022-12-11 23:53:53 +08:00
02d3468516 chore: Android version supports child processes following the main process rules 2022-12-11 18:24:55 +08:00
f657ac97f6 fix: add an unmap before is6 2022-12-11 15:48:37 +08:00
57dfaf135d fix: hysteria ipv6 outbound 2022-12-11 13:41:44 +08:00
9df42d7b98 fix: issue #292 2022-12-11 09:25:46 +08:00
b5928c36a3 fix: tunnel panic 2022-12-11 08:59:57 +08:00
910e7fed97 fix: parse DoH url 2022-12-08 12:46:46 +08:00
a9839abd4c fix: tun create panic 2022-12-08 11:29:39 +08:00
78c7b6259c chore: update config.yaml 2022-12-08 09:59:04 +08:00
a6f7e1472b fix: let doh/dot server follow hosts and can remotely resolve itself ip 2022-12-07 20:01:44 +08:00
e03fcd24dd fix: let any type of dns server can use remote proxy 2022-12-07 13:00:45 +08:00
cd99b2e795 fix: config code merge 2022-12-06 10:13:05 +08:00
b5b06ea49c fix: config crash 2022-12-06 09:04:30 +08:00
f7fb5840cf fix: reorder metadata 2022-12-06 08:23:30 +08:00
3b96d54369 chore: cleanup rules/logic code 2022-12-05 23:51:38 +08:00
f390b9cf2f fix: inbound nil pointer 2022-12-05 23:10:47 +08:00
1c65a2c1b4 chore: cleanup rule parse code 2022-12-05 22:29:52 +08:00
2d2b75a4bf doc: update tor listeners' tun 2022-12-05 21:32:08 +08:00
dcbe25c3ae chore: add log 2022-12-05 19:48:54 +08:00
46d23d9b86 chore: all listeners support specialProxy 2022-12-05 17:53:54 +08:00
fd9c4cbfa5 chore: listeners support tun 2022-12-05 17:43:50 +08:00
5c410b8df4 chore: listeners support tunnel 2022-12-05 17:03:12 +08:00
8c58d8a8ad fix: remove unneed listener's start 2022-12-05 16:29:50 +08:00
a0a2eb2106 chore: update dependencies 2022-12-05 11:03:28 +08:00
b7d976796a chore: listeners support shadowsocks/vmess 2022-12-05 10:12:53 +08:00
2e22c712af chore: rebuild add adapter/inbound.Addition to simply Listener.New apis 2022-12-05 00:20:50 +08:00
c7f83d3ff1 chore: listeners support tuic 2022-12-04 23:05:13 +08:00
62474e0ed6 fix: correct C.Metadata 2022-12-04 22:08:20 +08:00
62226e8b3d chore: rebuild InboundListener 2022-12-04 21:53:13 +08:00
8144373725 chore: support skip the same config Listeners' Close and Listen 2022-12-04 17:20:24 +08:00
e9d8dd09ac fix: close linstener 2022-12-04 15:15:23 +08:00
6fc62da7ae chore: change C.PacketAdapter from a struct to an interface 2022-12-04 14:37:52 +08:00
4f75201a98 feat: add linsters 2022-12-04 13:37:14 +08:00
ba884c29bd refactor: adjust config 2022-12-03 14:14:15 +08:00
2fe271f19f feat: add tls port for RESTful api and external controller 2022-12-03 12:25:10 +08:00
cf5709aab1 fix: a temporary solution for error reporting when enabling tun for devices that do not have an ipv6 environment. 2022-12-03 00:10:50 +08:00
654cdf3d5b chore: adjust fakeip test 2022-12-02 20:21:31 +08:00
6c79d9e63b chore: fixed v2fly v4 version for test 2022-12-02 20:21:15 +08:00
0aefa3be85 fix: remove cyclic dependent to make tuic's Finalizer work 2022-12-02 16:56:17 +08:00
bc5ab3120f fix: use same strClone function 2022-11-30 20:38:03 +08:00
df8e129fc6 chore: optimize DomainTrie for only one child 2022-11-30 19:42:05 +08:00
84caee94af chore: decrease DomainTrie's memory use 2022-11-30 18:50:46 +08:00
1d9e320087 test: cleanup the test code 2022-11-30 08:58:53 +08:00
2a3c4c1a33 fix: ensure pool's dial pass to client 2022-11-29 09:23:28 +08:00
8c0fbb3665 chore: restful api display fast-open for tuic and hysteria 2022-11-29 00:56:27 +08:00
9ea09b2b94 fix: tuic protocol error 2022-11-29 00:42:26 +08:00
db81db5363 Chore: allow tunnels don't set special proxy 2022-11-28 20:31:32 +08:00
e715ccbdd5 chore: update docs 2022-11-28 20:12:09 +08:00
bc94c50783 chore: update dependencies 2022-11-28 20:09:24 +08:00
b4b9ef2362 Merge branch 'dev' of https://github.com/Dreamacro/clash into Alpha 2022-11-28 20:04:56 +08:00
dd6f7e3701 fix: structure decode first do strict match 2022-11-28 19:11:55 +08:00
01e382285d chore: tuic-server support restful api patch 2022-11-28 18:53:09 +08:00
4b1d4a3e20 chore: tuic-server support heartbeat command 2022-11-28 18:28:22 +08:00
562819e3ca chore: tuic-server support disassociate command 2022-11-28 18:18:51 +08:00
551283c16e chore: add tuic-server listener 2022-11-28 17:09:25 +08:00
cd53e2d4a7 fix: set RequestTimeout in earlyConn 2022-11-27 16:38:41 +08:00
a58234f0cd chore: support KeyReplacer in Structure Decoder 2022-11-27 13:44:38 +08:00
c8d7243b5b fix: hop-interval of tysteria is optional too 2022-11-27 13:07:27 +08:00
6b1ca7b07c fix: ports of hysteria is optional 2022-11-27 12:52:14 +08:00
b80e7c3c92 fix: tuic client use pool-based bytes.Buffer 2022-11-27 12:06:04 +08:00
0da09c5ddd fix: quicStreamPacketConn's close 2022-11-27 11:42:43 +08:00
17c081a40c add support for hysteria udp port hopping (#269)
* add support for hysteria udp port hopping

* add ports field for hysteria

* change method for udp connection

Co-authored-by: geoleonsh <geoleonsh@live.com>
2022-11-27 11:09:56 +08:00
0647cee02a chore: update quic-go 2022-11-27 10:37:10 +08:00
423850a7aa chore: allow to set max-open-streams for tuic 2022-11-27 09:38:20 +08:00
896d30b151 chore: rebuild tuic client's code 2022-11-26 23:53:59 +08:00
495fd191f2 chore: clear config field name (be compatible with old field name) 2022-11-26 21:35:47 +08:00
ae76daf393 chore: tuic add fast-open support 2022-11-26 21:14:56 +08:00
8056b5573b chore: update github action 2022-11-26 20:11:43 +08:00
516623cbbb chore: update demo 2022-11-26 19:48:16 +08:00
a5ae2e891c feat: support fast_open for hysteria, and unified parameter naming 2022-11-26 19:37:32 +08:00
90b40a8e5a Fix: drop UDP packet which mismatched destination for VMess (#2410)
Co-authored-by: SUN Sizhe <sunsizhe@cmi.chinamobile.com>
2022-11-26 11:27:24 +08:00
7f40645934 chore: tuic use a udp pool too and auto close when outbound is garbage collected 2022-11-25 22:42:28 +08:00
6c204d2b77 chroe: wrong variable 2022-11-25 20:53:08 +08:00
ed988dcdc5 Chore: update dependencies 2022-11-25 20:42:28 +08:00
7b44cde4bd chore: tuic use a simple client pool 2022-11-25 20:14:05 +08:00
c7bad89af3 fix: tuic better stream close 2022-11-25 19:14:09 +08:00
21a91e88a1 fix: tuic set MaxOpenStreams 2022-11-25 18:32:30 +08:00
76d2838721 chore: split tuic's tcp and udp client 2022-11-25 17:15:45 +08:00
9b1fe9f466 fix: tuic stream close 2022-11-25 16:06:56 +08:00
9976800a35 fix: tuic err handle 2022-11-25 13:03:36 +08:00
f542351404 chore: tuic add max_udp_relay_packet_size 2022-11-25 12:43:23 +08:00
a13dedb6e4 fix: tuic panic 2022-11-25 12:10:33 +08:00
d47ce79a24 chore: better tuic conn close 2022-11-25 11:32:52 +08:00
cce42b4b83 fix: prefer ipv6 not working 2022-11-25 11:12:22 +08:00
142d17ebad fix: don't close tuic when read timeout 2022-11-25 11:04:28 +08:00
30ca59dab7 fix: tuic typo 2022-11-25 10:45:06 +08:00
c89b1f0e96 chore: tuic add cubic,new_reno,bbr congestion_controller 2022-11-25 10:33:37 +08:00
59bd11a3a7 chore: add tuic outbound
close #133
2022-11-25 08:08:14 +08:00
3880c3c1be chore: add retry in tunnel dial 2022-11-24 12:32:35 +08:00
efa4b9e0b8 Fix: lint warning 2022-11-22 21:01:51 +08:00
8c6e205c5a Fix: tunnel proxy match 2022-11-22 19:16:08 +08:00
d478728cb7 fix: geosite match 2022-11-21 10:33:42 +08:00
5b07d7b776 Feature: add tunnels 2022-11-20 21:30:55 +08:00
18d62c4a17 fix: catch context.DeadlineExceeded too 2022-11-19 23:16:20 +08:00
02830e0ad6 fix: adjust log 2022-11-19 23:07:49 +08:00
6d89bddf29 fix: better error return 2022-11-19 23:06:27 +08:00
dbbd499349 fix: better error check 2022-11-19 23:03:14 +08:00
d3562ce394 fix: DoH recreate the connection multiple times 2022-11-19 22:48:04 +08:00
d5973cf8a6 chore: Adjust error of dialer 2022-11-19 10:57:33 +08:00
1d3cc36eef chore: return context error 2022-11-19 10:50:13 +08:00
8fcfecbed1 chore: Adjust the return 2022-11-19 10:47:03 +08:00
7c1b878c3f fix: resolver's ctx 2022-11-19 10:45:44 +08:00
4ea4221380 fix: rollback batchExchange's code 2022-11-19 10:35:45 +08:00
b8b3c9ef9f fix: DoH/DoQ doesn't use context 2022-11-19 10:31:50 +08:00
f00dc69bb6 fix: doh use NewRequestWithContext and batchExchange don't wait cancel finish 2022-11-19 09:43:31 +08:00
23f286f24e fix: auto retry in exchangeWithoutCache 2022-11-19 08:51:00 +08:00
16f8f77f5d fix: better wireguard error handle 2022-11-18 19:40:39 +08:00
dfc0ec995c fix: wireguard handle conn is nil 2022-11-18 19:32:12 +08:00
8b848b62bb fix: reset timeout in exchangeWithoutCache's singleflight 2022-11-18 18:02:46 +08:00
2dc62024fe chore: support old chacha20 2022-11-16 18:37:14 +08:00
994e85425f fix: resolver's defer 2022-11-16 17:53:52 +08:00
1880a485f8 chore: better tfo inbound code 2022-11-16 10:43:16 +08:00
03645fb235 fix: correct the go.mod 2022-11-16 10:18:42 +08:00
eb8431255d fix: sing-shadowsocks serverConn.Write return (0,nil) when p isn't empty 2022-11-16 10:18:42 +08:00
e5a81b6c35 fix: don't use ReadOnceFrom in sing-shadowsocks 2022-11-16 10:18:42 +08:00
0eecd11fdc ss and vmess inbound add tfo 2022-11-16 10:18:10 +08:00
9c8e39827f update tfo to v2 2022-11-16 10:18:10 +08:00
586dec5ba3 Merge commit '2a8e1778ad1d7b507e432f659407d76dffcaacb8' into Alpha 2022-11-14 20:17:52 +08:00
6db7c800d5 fix: DoQ and HTTP/3 over proxy 2022-11-14 20:17:12 +08:00
2a8e1778ad chore: sync tunnel code 2022-11-12 21:42:45 +08:00
a3425c0e78 chore: sync dns code 2022-11-12 21:31:07 +08:00
7300c917dc fix: build error 2022-11-12 20:59:29 +08:00
dc3e144b6a Merge branch 'dev' of https://github.com/Dreamacro/clash into Alpha 2022-11-12 20:43:48 +08:00
75d339392b chore: better dns background fetch retrying 2022-11-12 18:29:19 +08:00
901a47318d chore: always pass context when resolve dns 2022-11-12 13:18:36 +08:00
dbadf37823 chore: update listeners config 2022-11-12 12:36:59 +08:00
3321ac95ca fix: cleanup import 2022-11-12 12:31:44 +08:00
c0bd4af120 chore: update dns config 2022-11-12 12:12:31 +08:00
d78b2b1cfb Merge pull request #256 from Skimmle/Alpha
featrue: DoH and DoQ are implemented using AdGuardTeam/dnsProxy
2022-11-12 11:19:04 +08:00
3e20912339 featrue: DoH and DoQ are implemented using AdGuardTeam/dnsProxy, DoH support perfer and force http3 2022-11-12 11:14:51 +08:00
b2d7149a95 chore: support IN-PORT rule 2022-11-11 23:36:06 +08:00
64be213b66 code cleanup 2022-11-11 22:48:44 +08:00
68b28ed530 chore: shadowsocks listener support old cipher 2022-11-11 22:44:44 +08:00
3eacce9a66 chore: add vmess, shadowsocks, tcptun and udptun listener 2022-11-11 20:56:08 +08:00
6dadc2357a chore: remove AddrType on Metadata 2022-11-11 09:19:50 +08:00
698d8ca701 Update README.md 2022-11-11 04:05:13 +08:00
1a4b00c70e fix: update sing-vmess 2022-11-10 21:23:52 +08:00
64552fbd00 fix: when host's ip in fakeip's range, don't send to remote server 2022-11-10 21:08:06 +08:00
7c8d8f56e1 Update docs/config.yaml 2022-11-09 22:55:32 +08:00
93ada8989f Update README.md 2022-11-09 19:55:30 +08:00
4b4c3dc41e fix: small-case import name 2022-11-09 19:42:56 +08:00
b699fb046b fix: wireguard's dns resolve 2022-11-09 19:35:03 +08:00
ae08d13de4 chore: support wireguard outbound 2022-11-09 18:44:06 +08:00
1d784231b0 fix: exclude-filter not work when filter is empty 2022-11-09 08:41:30 +08:00
5fd79890e7 chore: add exclude-filter to ProxyGroup 2022-11-09 08:06:37 +08:00
53b2a480ef fix: subscriptionInfo api 2022-11-08 22:30:50 +08:00
943137de3b Merge pull request #250 from Skimmle/Alpha
chore: upgrade dependencies
2022-11-08 15:58:21 +08:00
2d3aad573e chore: upgrade dependencies 2022-11-08 15:50:01 +08:00
409cd4f6a1 fix: subscriptionInfo api
fix: subscriptionInfo api

fix: subscriptionInfo api
2022-11-08 07:59:08 +08:00
bd526ad0a1 chore: adjust tun config
Update config.yaml

chore: adjust tun demo
2022-11-07 18:33:27 +08:00
4673d2093a fix: context import 2022-11-06 08:43:39 +08:00
94a765ee31 fix: avoid choose ZeroTier's tap to defaultInterface 2022-11-05 20:51:28 +08:00
dcd2417fce feat: subscriptionInfo 2022-11-05 19:39:17 +08:00
4c5853e5e7 feat: Converter Shadowsocks UoT support 2022-11-05 06:41:07 +00:00
52f4cb599a fix: pool_test.go 2022-11-05 13:08:50 +08:00
90f6cc233c fix: correct yaml config name 2022-11-04 17:38:24 +08:00
de264c42a8 Chore: update test dependencies 2022-11-04 13:31:20 +08:00
c2469162fb Chore: update dependencies 2022-11-04 13:28:51 +08:00
19b7c7f52a Fix: a shared fastSingle.Do() may cause providers untouched (#2378) 2022-11-04 13:11:01 +08:00
e20d01a679 chore: try to let tun's restful patch api work 2022-11-04 08:52:30 +08:00
9a5c0a4b6d chore: better tun config passing 2022-11-03 18:56:03 +08:00
1b0d09068b fix: RESTful API empty tun device name 2022-11-03 18:04:37 +08:00
3373b62b02 fix: try to support android hotspot when using tun 2022-11-03 12:58:21 +08:00
508e257543 fix: RESTful API sniffingEnable 2022-11-03 00:31:31 +08:00
7b0cd14b00 chore: netlink duplicate contains 2022-11-02 23:58:51 +08:00
22fb219ad8 chore: trie.DomainTrie will not depend on zero value 2022-11-02 22:28:18 +08:00
c34c5ff1f9 build: fix golang build cache 2022-11-02 22:13:54 +08:00
4e5bdec13a Fix: amd64 macOS Ventura process name match 2022-11-02 11:43:43 +08:00
fbd43d9947 Fix: macOS Ventura process name match 2022-11-02 11:38:31 +08:00
5dab89c9ec fix: group filter add not matched proxies at the end 2022-10-31 21:50:30 +08:00
2a24effac0 chore: better UrlTest's torch 2022-10-31 16:58:29 +08:00
972d3f1d39 fix: UrlTest's torch not work
close #232
2022-10-31 16:45:14 +08:00
a7aa5fd523 adjust: add some log for healthcheck debug 2022-10-31 16:04:50 +08:00
b9d8b69889 fix: lazy check 2022-10-30 23:08:18 +08:00
dedb9122df chore: support multi filter in GroupBase too 2022-10-30 22:30:54 +08:00
0e5bf0c27e chore: support multi filter like subconverter in ProxyProvider and add exclude-filter to ProxyProvider 2022-10-30 21:04:33 +08:00
a46436f61a chore: parse user's hosts before remoteDial 2022-10-29 09:03:00 +08:00
6106adc6a5 fix: hysteria converter 2022-10-27 18:37:27 +08:00
c8bc11d61d Fix: amd64 macOS Ventura process name match 2022-10-27 15:36:09 +08:00
f29b54898f Fix: macOS Ventura process name match 2022-10-27 11:25:18 +08:00
fc693bc257 chore: SUB-RULE 2022-10-23 16:54:50 +08:00
bba5c2cc8a Update config.yaml 2022-10-21 01:21:15 +08:00
0fb0e490f8 fix: when connection refused active health test 2022-10-16 13:12:49 +08:00
023e3d0c41 chore: add parse-pure-ip in sniffer 2022-10-14 08:42:28 +08:00
c11a359761 chore: retrying for "Cannot create a file when that file already exists." 2022-10-14 08:27:34 +08:00
0da49bd92b chore: add force-dns-mapping in sniffer 2022-10-14 07:46:33 +08:00
b9ef713dd7 chore: add sing-tun's custom route support 2022-10-13 19:26:14 +08:00
4948f3f213 chore: Cache and skip multiple failed addresses 2022-10-11 21:35:26 +08:00
7b1427b843 fix: set default tun udp timeout to 5 minutes 2022-10-10 22:10:36 +08:00
77a3c1c3ae fix: tun stack shown 2022-10-10 19:02:57 +08:00
2c236387b7 fix: flush default interface when tun config hasn't change 2022-10-10 09:32:42 +08:00
66e5136ba0 fix: correct sing-tun's rAddr 2022-10-09 13:16:13 +08:00
f748e3632d fix: fakeip pool test 2022-10-09 11:07:24 +08:00
90688b238a fix: try let fakeip mode get real destination ip 2022-10-09 10:48:26 +08:00
986c91b5c8 Merge pull request #207 from oluceps/add-tags
add: with_gvisor tag for nix build
2022-10-08 15:47:48 +08:00
8c13426492 add: with_gvisor tag for nix build 2022-10-08 15:40:41 +08:00
fefd9b7427 Merge pull request #206 from oluceps/update-sha256
Update vendorSha256
2022-10-08 15:06:29 +08:00
8a55208c62 chore: update vendorSha256 due to dependencies change 2022-10-08 13:11:25 +08:00
1f8b54a92d fix: don't set auto detect interface with tun name 2022-10-07 16:54:08 +08:00
8d74a86bf1 fix: macos's tunName 2022-10-07 06:57:03 +08:00
6c82e98bbc chore: fix sing-tun's BuildAndroidRules 2022-10-06 22:18:49 +08:00
94246104b8 chore: use sing-tun to replace old tun_adapter 2022-10-06 19:23:38 +08:00
347e5e9606 fix: dns tcp hijack not working 2022-10-05 13:29:10 +08:00
e4138c3e1e chore: add description 2022-10-04 22:16:03 +08:00
f2b5ae6894 Merge remote-tracking branch 'meta/Alpha' into Alpha 2022-10-03 22:41:40 +08:00
0a89107b8b fix: global fingerprints load failed 2022-10-03 22:41:24 +08:00
2a323f77ce Merge pull request #203 from oluceps/Alpha
add: current version and BuildTime for nix build
2022-10-03 13:19:04 +08:00
59edcf33bd feat: Add VMess global padding support 2022-10-02 22:42:33 +08:00
35506e179a chore: Unify config field name style 2022-10-02 21:46:01 +08:00
6ce3805719 feat: Converter support packet encodings for VMess 2022-10-02 21:10:29 +08:00
c1a82f2fae chore: fix doc 2022-10-02 20:54:51 +08:00
4f9478a336 chore: adjust doc 2022-10-02 20:53:52 +08:00
e1dc2681f1 chore: adjust doc 2022-10-02 20:48:50 +08:00
ce77c3fd8b chore: add SUB-RULE demo 2022-10-02 20:47:43 +08:00
9f1194056a Merge branch 'udp' into Alpha 2022-10-02 20:28:44 +08:00
b82c9ba190 chore: remove buffer for relay 2022-10-02 20:28:31 +08:00
6857b05039 chore: pure udp metadata 2022-10-02 20:08:41 +08:00
abbbcb02c0 Merge branch 'Alpha' into dev 2022-10-02 20:02:39 +08:00
ec6144250d add: current version and BuildTime for nix build 2022-10-02 18:29:20 +08:00
89d1222b8f rm: default.nix 2022-10-02 18:20:26 +08:00
de4985a9b6 Merge pull request #202 from oluceps/refactor_flake
Refactor flake
2022-10-02 02:23:57 +08:00
6dd8cf6c0a refactor: multi-platform support for nix build 2022-10-02 02:15:37 +08:00
6b2eae36f2 adjust: gvisor version 2022-10-02 01:00:07 +08:00
13445d815b add: flake auto track latest commit 2022-10-02 00:18:35 +08:00
e2d71abecd Fix: handle parse socks5 udp address properly (#2220)
(cherry picked from commit bec4df7b12)
2022-10-01 23:45:06 +08:00
ddf1c74091 adjust: routes for windows 2022-10-01 23:30:41 +08:00
1bcc916807 chore: upgrade dependencies 2022-10-01 23:09:23 +08:00
c06b48af0e Merge pull request #201 from oluceps/add_flake
add: flake.nix and other required files for nix build
2022-10-01 22:44:20 +08:00
7e9549c05a add: flake.nix and other required files for nix build 2022-10-01 22:34:39 +08:00
Pan
3e2b08f9d0 Chore: upgrade go.mod go version to 1.19 (#2331) 2022-09-29 11:47:30 +08:00
fb85691fb9 Fix: uncorrect README link (#2325) 2022-09-27 14:22:21 +08:00
1684756b79 Merge pull request #191 from StashNetworks/patch-1
Chore: compatible with Stash hysteria config
2022-09-21 23:54:40 +08:00
88e4a9a755 Chore: compatible with Stash hysteria config 2022-09-21 23:42:33 +08:00
d411394482 Chore: rename linux-armv8 to linux-arm64, windows-arm32v7 to windows-armv7 2022-09-21 21:18:24 +08:00
827d5289bc Refactor: improve Dockerfile (#2246) 2022-09-21 21:09:11 +08:00
b133bc58f0 fix: upgrade sing-vmess to let xudp work 2022-09-21 19:06:13 +08:00
82c9a1a2bb chore: add xudp support for vmess 2022-09-19 18:26:43 +08:00
e1ec4a2502 fix: wrong host shown when using uot 2022-09-19 17:37:16 +08:00
b6dc539105 Merge pull request #189 from sjtuross/Alpha
Add iptables package to docker
2022-09-19 00:07:30 +08:00
6e8d8befb8 docker: add iptables package 2022-09-18 23:19:25 +08:00
6995e98181 Refactor: linux process resolving (#2305) 2022-09-18 12:53:51 +08:00
4597ed49cf fix: adjust sub_rule to logic package, and fix not rule failed 2022-09-11 16:19:42 +08:00
ef2f8317c7 Fix: wechat protocol is not working if no obfs string is configured 2022-09-11 15:24:56 +08:00
9b89ff9f2d feat: support sub-rule, eg.
rules:
  - SUB-RULE,(AND,((NETWORK,TCP),(DOMAIN-KEYWORD,google))),TEST2
  - SUB-RULE,(GEOIP,!CN),TEST1
  - MATCH,DIRECT

sub-rules:
  TEST2:
    - MATCH,Proxy
  TEST1:
    - RULE-SET,Local,DIRECT,no-resolve
    - GEOSITE,CN,Domestic
    - GEOIP,CN,Domestic
    - MATCH,Proxy
2022-09-06 17:30:35 +08:00
4f291fa513 Chore: show the source ip in log (#2284)
Co-authored-by: Li Feng <fengli@smartx.com>
2022-09-02 16:59:00 +08:00
22b9befbda Fix: fake ip pool offset calculate (#2281) 2022-09-01 11:33:47 +08:00
a9694fcdc0 chore: update doc 2022-08-30 15:59:52 +08:00
d823dde43c chore: update doc 2022-08-29 13:07:38 +08:00
d69e0bce4a fix: resolve ip of udp proxy error 2022-08-29 13:04:48 +08:00
7f197ede51 fix: hysteria udp crash 2022-08-29 12:10:46 +08:00
af97922e94 fix: no main result conn, will fail 2022-08-28 20:26:13 +08:00
db94dc76b4 fix: udp default resolve ip 2022-08-28 15:57:10 +08:00
99effb051b feat: add ip-version param 2022-08-28 13:41:43 +08:00
425b6e0dc0 Chore: update README (#2276) 2022-08-27 12:16:25 +08:00
2516169f61 Chore: update dependencies 2022-08-26 21:18:16 +08:00
a3281712e2 Chore: reduce dhcp dns client cost 2022-08-24 21:36:19 +08:00
bf079742cb Clean: use go 1.19 Appendf 2022-08-24 20:21:06 +08:00
98f4f4d6c4 chore: log error 2022-08-23 20:12:28 +08:00
42e489e199 Merge remote-tracking branch 'origin/Alpha' into Alpha
# Conflicts:
#	component/sniffer/http_sniffer.go
2022-08-22 23:22:26 +08:00
d3b88d1b4f fix: ebpf support 2022-08-22 23:17:41 +08:00
ec318f1cc5 Chore: the default sniffing is changed to a standard port, and the sniffing result is only used for this connection. 2022-08-21 08:43:57 +08:00
6cfae6919f Merge pull request #167 from H1JK/Alpha
feat: Update Converter
2022-08-20 02:32:44 +08:00
b23a071050 feat: Converter VMessAEAD share link standard support 2022-08-19 22:00:22 +08:00
732e82e3d0 fix: Converter VMess security field typo 2022-08-19 21:17:44 +08:00
fecbc7a091 chore: Clean converter code and add doc 2022-08-19 19:46:50 +08:00
4b39362039 chore: Skip initial "lan" rules that load geoip 2022-08-17 12:42:33 +08:00
835cab58cf fix: http sniffer skip ip 2022-08-17 12:41:36 +08:00
6e058f8581 Chore: remove old cache implementation 2022-08-17 11:43:20 +08:00
9317dd610b chore: Skip initial "lan" rules that load geoip 2022-08-17 00:33:03 +08:00
8dc56b56ad fix: http sniffer skip ip 2022-08-16 22:59:53 +08:00
4611fbfe0c chore: disable tcp_test 2022-08-15 15:52:03 +08:00
b725c91b05 chore: clean code 2022-08-15 15:46:07 +08:00
482062376e fix: Temporarily delete marking node alive as false when error occurs 2022-08-13 18:25:28 +08:00
32fc990c68 fix: Unhandled dns resolve failure error 2022-08-13 16:47:24 +08:00
3946d771e5 Feature: sync missing resolver logic from premium, but still net.IP on opensource 2022-08-13 13:07:35 +08:00
5940f62794 Chore: http2 should use DialTLSContext and some tls handshake should with context 2022-08-13 12:35:39 +08:00
71cad51e8f Fix: satisfy RFC4343 - DNS case insensitivity (#2260) 2022-08-12 13:47:51 +08:00
dfeb901417 Fix: no_gviosr tags 2022-08-12 12:53:11 +08:00
02933ae568 Fix: nil pointer 2022-08-12 12:49:35 +08:00
4ca2d4146b Merge remote-tracking branch 'origin/Alpha' into Alpha 2022-08-12 03:36:15 +08:00
5d97a7f9ca Chore: clean code 2022-08-12 03:35:49 +08:00
6eab1f158a Fixed: gViosr func 2022-08-12 03:34:59 +08:00
65a289e16f Chore: clean code 2022-08-12 03:04:58 +08:00
637707e58f Chore: Migration 1.19 2022-08-12 00:07:13 +08:00
95e602bf3b Chore: gVisor use bufferv2 2022-08-11 23:47:45 +08:00
4ac192f520 Chore: update badges 2022-08-11 23:45:50 +08:00
473d0f74bd fix: remove extra and the actual original IDNA domain name is no longer stored, for reduce memory 2022-08-11 21:50:16 +08:00
93ea1248e3 Merge remote-tracking branch 'origin/Alpha' into Alpha 2022-08-08 10:28:18 +08:00
97270dcbe0 rm EBpf tun && disable android ebpf 2022-08-08 10:21:16 +08:00
50105f0559 Migration: go1.19 2022-08-07 21:45:50 +08:00
50cc274b2e Merge pull request #151 from H1JK/update-converter2
fix: Converter error when VMess `aid` field not exists
2022-08-07 20:47:32 +08:00
3867329ef3 fix: Converter error when VMess aid field not exists 2022-08-07 20:43:11 +08:00
6648793e40 Chore: reenable latest golangci-lint 2022-08-05 10:52:36 +08:00
2899a126fc fix filepath undefined 2022-08-02 17:13:10 +08:00
5391425123 Merge branch 'dev' into Alpha 2022-08-01 22:13:46 +08:00
1e7af0bbc7 fix: repeat set http status 2022-08-01 22:12:36 +08:00
dd67a8c8ba Merge remote-tracking branch 'origin/Alpha' into Alpha 2022-08-01 18:07:09 +08:00
28ba9c5efa revert 9be70f67ca 2022-08-01 18:06:09 +08:00
bb413ece49 Merge pull request #144 from zhudan/ebpf
support ebpf
2022-08-01 17:20:14 +08:00
31f4d20477 support ebpf 2022-07-29 09:08:35 +08:00
95e3a88608 Chore: update bug_report.yml (#2240) 2022-07-28 20:27:53 +08:00
bec4df7b12 Fix: handle parse socks5 udp address properly (#2220) 2022-07-25 12:44:00 +08:00
93400cf44d Fix: ALPN should on DoH instead of DoT (#2232) 2022-07-25 12:41:22 +08:00
57a15088c2 update config demo 2022-07-25 09:27:31 +08:00
be6d55e5c4 Merge remote-tracking branch 'Meta/Alpha' into Alpha 2022-07-24 01:50:16 +08:00
09419d88af fix process code 2022-07-24 01:50:10 +08:00
9c4bae6f23 fix process code 2022-07-24 01:38:00 +08:00
b74320008a Merge remote-tracking branch 'Meta/Alpha' into Alpha 2022-07-24 01:37:06 +08:00
35b87e79a7 fix process code 2022-07-24 01:37:01 +08:00
9be70f67ca fix process code 2022-07-24 01:34:22 +08:00
bb86098235 fix process code 2022-07-24 01:32:22 +08:00
dec32da262 clean code 2022-07-24 01:07:30 +08:00
a33e511c12 Fix: macOS udp find process should use unspecified fallback 2022-07-24 00:10:01 +08:00
d71a2ce61e Fix: fakeip udp should not replace with another ip 2022-07-24 00:02:45 +08:00
133bb2319f Chore: load balance hash need to have fallback strategy 2022-07-23 23:59:13 +08:00
7d84a47683 Chore: load balance hash need to have fallback strategy 2022-07-23 23:51:42 +08:00
38e6b81d07 Merge pull request #129 from zhudan/Alpha
入站增加TFO支持(默认不开启)
2022-07-22 06:32:34 -04:00
143c5de51d inbound tfo 2022-07-22 15:16:09 +08:00
3e424dea7b refactor: DoH use fragment setting params 2022-07-21 21:40:28 +08:00
a794819869 Chore: upgrade actions and fixed golangci-lint version 2022-07-21 15:15:14 +08:00
b0fd50453a fix: DoT-ALPN error 2022-07-21 13:57:06 +08:00
fe3ad3724c fix: resolver error handling exception 2022-07-21 09:02:58 +08:00
e1c6142851 fix: pure ip resolve 2022-07-20 22:59:04 +08:00
6e7002dbd3 chore: clean code 2022-07-20 17:15:19 +08:00
6a4063af0d refactor: optimize nodes caching 2022-07-20 08:53:54 +08:00
6b636c051a chore: Adjust the falling logic 2022-07-16 19:52:51 +08:00
9a035d3c51 fix: no_gvisor compile failed for target linux 2022-07-16 19:35:52 +08:00
850c52d07c chore: log level should be setting after launched 2022-07-16 13:33:27 +08:00
10f8f5dd7b chore: Increase idle timeout and add keep alive period 2022-07-15 21:57:50 +08:00
4e272ff066 fix: DoH retry HTTP/3 2022-07-15 21:54:57 +08:00
a73e690172 fix: DoQ closes udp immediately. 2022-07-15 21:54:02 +08:00
be8d63ba8f Fix: macOS udp find process should use unspecified fallback 2022-07-15 17:00:41 +08:00
3b90e18047 Chore: update test dependencies 2022-07-15 16:07:18 +08:00
947d9d4560 chore: clean up code 2022-07-13 22:27:49 +08:00
179bc6ecdf chore: clean up code 2022-07-13 22:20:14 +08:00
fbabcfce94 fix: CA params convert to fingerprint 2022-07-12 14:32:34 +08:00
3a92ad47e7 fix: default nameserver cannot use doh of pure IP 2022-07-12 13:05:59 +08:00
92a20a5362 chore: tcp conn error text 2022-07-11 22:29:35 +08:00
9565b5194c chore: remove log 2022-07-11 22:18:24 +08:00
23b2f3b971 Merge branch 'provider' into Alpha 2022-07-11 22:17:56 +08:00
f93dd6052e fix: default nameserver cannot use non-standard port of doh 2022-07-11 22:05:37 +08:00
80df572b18 refactor: Unified provider loading resources 2022-07-11 21:30:34 +08:00
0c64d7e56a chore: fingerprint style 2022-07-11 13:44:27 +08:00
a8ce283727 feat: add fingerprint param 2022-07-11 13:42:28 +08:00
ab8e9e7d7a fix: skip-cert-verify not work 2022-07-11 12:37:27 +08:00
dbce268692 feat: Prepare to specify the fingerprint function 2022-07-10 21:56:33 +08:00
fef9f95e65 feat: add fingerprint for tls verify 2022-07-10 20:44:24 +08:00
f0952b55d0 Fix: query string parse on ws-opts (#2213) 2022-07-10 14:56:34 +08:00
8c7c8f4374 Chore: update dependencies 2022-07-07 22:15:50 +08:00
60e1947ed2 chore: upgrade dependencies for hysteria 2022-07-07 12:49:52 +08:00
5b35822945 Merge branch 'Alpha' into dev 2022-07-07 12:23:09 +08:00
0a76876764 fix: h3 of doh fall back logic 2022-07-06 21:25:25 +08:00
e382496e4c Merge branch 'h3' into Alpha 2022-07-06 20:54:10 +08:00
0c91a4e0f3 refactor: h3 for doh 2022-07-06 20:53:34 +08:00
65a8e8f59c Fix: process rule type (#2206) 2022-07-06 13:44:04 +08:00
5497adaba1 Fix: fakeip udp should not replace with another ip 2022-07-05 21:09:29 +08:00
56fae0b1f5 chore: reduce wrapper 2022-07-05 21:00:41 +08:00
aaf08dadff Change: remove AddrType on Metadata (#2199) 2022-07-05 20:26:43 +08:00
557297ac9a Chore: load balance hash need to have fallback strategy 2022-07-04 21:36:33 +08:00
baee951657 fix: close idle connections 2022-07-04 20:38:07 +08:00
253dc24e40 chore: clash.mini hack. 2022-07-04 18:53:24 +08:00
503b1efd8a fix: close transport with doh of h3 2022-07-03 23:01:49 +08:00
50f2ecbcb0 chore: upgrade dependencies 2022-07-03 22:58:03 +08:00
97e158989f Merge branch 'h3' into Alpha
# Conflicts:
#	go.mod
2022-07-03 22:55:26 +08:00
e732fbb414 chore: add prefer-h3 into config.yaml 2022-07-03 22:53:49 +08:00
f8a168e64d Merge branch 'hy' into Alpha 2022-07-03 22:51:33 +08:00
e599621a32 fix: resolve ipv6 error in hysteria 2022-07-03 22:51:20 +08:00
59ab2083aa feat: try h3 connect DOH, failed will fall back h2; turn on with dns.prefer-h3: true 2022-07-03 21:59:47 +08:00
2d7c4eda66 Merge branch 'hy' into Alpha 2022-07-03 18:27:14 +08:00
3cc1870aee chore: embed hysteria, clean irrelevant codes, code from https://github.com/HyNetwork/hysteria 2022-07-03 18:26:46 +08:00
8eec86232c chore: add config.yaml demo 2022-07-02 13:44:04 +08:00
77a1e3a653 Chore: cleanup bind mark code 2022-06-30 17:27:57 +08:00
27e1d6cdae Chore: cleanup code 2022-06-30 17:12:06 +08:00
91c22b16bf Fix: proxy provider filter validation (#2198) 2022-06-30 17:08:53 +08:00
fc5c9b931b Fix: try to unmap lAddr on tproxy udp listener 2022-06-29 23:36:45 +08:00
8ce9737f3d Update dependencies 2022-06-28 08:15:03 +08:00
6b44178108 Fix concurrency vmess udp write 2022-06-28 08:12:56 +08:00
6664547f43 chore: upgrade dependencies 2022-06-26 22:37:59 +08:00
10383e2701 Merge branch 'dev' into Alpha 2022-06-26 21:53:03 +08:00
f4b9f2965f fix: hysteria dial use external context 2022-06-26 21:52:22 +08:00
2ba933d16a chore: hysteria params verify 2022-06-25 12:43:47 +08:00
669961e496 fix: proxy provider force update on init 2022-06-25 12:42:52 +08:00
f979491013 fix: tcp concurrent force close when context done 2022-06-25 09:16:53 +08:00
0d55b28805 chore: dns interface name 2022-06-25 09:16:51 +08:00
9c70e649ca fix: disable doq skip verify cert 2022-06-25 09:16:49 +08:00
8c079bf5bc fix: tcp concurrent force close when context done 2022-06-25 09:16:28 +08:00
2cdf4a0532 chore: RESTful test group use request context 2022-06-25 08:53:11 +08:00
4ba34ce672 chore: healthcheck only once check at same time 2022-06-25 08:53:04 +08:00
637f1b5aed ClashX hack. (#102) 2022-06-24 20:08:33 +08:00
56a87125e0 Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	common/convert/converter.go
2022-06-23 00:55:34 +08:00
6fedc8d942 fix: Converter for password of ss2022
fix: Converter for password of ss2022

fixup! fix: Converter for password of ss2022 and ws

fix: Converter for password of ss2022 and ws
2022-06-23 00:54:58 +08:00
dbb834d964 fix: Converter for password of ss2022 2022-06-23 00:40:08 +08:00
449946cc15 fixup! fix: Converter for password of ss2022 and ws 2022-06-23 00:18:30 +08:00
c3671a154d fix: Converter for password of ss2022 and ws 2022-06-22 22:18:13 +08:00
6874fb785b Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	common/convert/converter.go
2022-06-21 00:29:57 +08:00
5141ddc96e fix: Converter for vless/vmess/ss URI Scheme 2022-06-21 00:28:33 +08:00
6a03371731 fix: Converter for vless/vmess/ss URI Scheme 2022-06-21 00:18:34 +08:00
b658bb415b chore: remove unused 2022-06-20 22:25:59 +08:00
85405a54c7 Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	go.mod
#	go.sum
2022-06-19 22:30:02 +08:00
30a0834e72 chore: update shadowsocks 2022-06-19 22:26:17 +08:00
109a76e1fc fix: url test http response not closed 2022-06-19 17:29:46 +08:00
c231fd1466 Chore: update dependencies 2022-06-19 13:01:43 +08:00
c1a99b9be4 fix: IDNA domain match 2022-06-18 18:13:54 +08:00
bf55428954 style: rule provider strategy 2022-06-18 17:53:40 +08:00
5e55d6b08f Merge branch 'Alpha' into dev 2022-06-18 17:30:49 +08:00
21098d2627 feat: RESTful api add interface-name field on patch config 2022-06-18 17:29:19 +08:00
8da67ba61c Add shadowsocks uot in relay 2022-06-18 16:38:44 +08:00
54a0947bb4 fix: force update provider happen loopback 2022-06-18 16:05:09 +08:00
a562b249a2 Add shadowsocks uot and test 2022-06-18 10:50:33 +08:00
5af17f70b4 Fix buffered shadowsocks aead tcp request 2022-06-18 10:50:33 +08:00
ca5bb91977 Merge branch 'dev' into Alpha 2022-06-17 21:44:54 +08:00
bbac54433e fix: resolve ipv4 of 4 in 6 2022-06-17 21:44:06 +08:00
b6a5ec6490 fix: fix async conn usage 2022-06-16 10:21:20 +08:00
aaf700f0b5 chore: Allow VLESS protocol TLS to be FALSE 2022-06-16 01:20:33 +08:00
2ce89aca1e Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	go.mod
#	go.sum
2022-06-16 01:13:33 +08:00
efdf69022a fix: fix async conn usage 2022-06-16 01:04:27 +08:00
d4d1d4cc2a Merge remote-tracking branch 'Meta/Alpha' into Alpha 2022-06-16 01:01:03 +08:00
a8c4900891 fix: fix async conn usage maybe 2022-06-16 00:49:30 +08:00
930a7af8e7 chore: hy URI Scheme 解析 2022-06-15 23:18:06 +08:00
77acd4ba8d Update README.md
add permissions for systemctl services
clash-dashboard change to updated one
2022-06-15 19:22:18 +08:00
625c4a1079 Update util.go 2022-06-15 08:44:16 +08:00
341ef19099 fix: ss/ssr URI Scheme 解析问题 2022-06-15 03:20:58 +08:00
2563b20019 fix: ss/ssr URI Scheme 解析问题 2022-06-15 03:03:26 +08:00
1b3b5b4dfe fix: find process error 2022-06-14 23:14:43 +08:00
2e6bdc5636 feat: add param general.enable-process, it will always find process or uid, default value is false 2022-06-14 23:08:07 +08:00
be298cfa16 refactor: finding process and uid should to find with match process or uid rule, reduce memory allocation 2022-06-14 22:52:56 +08:00
277e71b26a chore: hysteria test 2022-06-14 21:05:52 +08:00
f7c903a586 Merge branch 'dev' into Alpha 2022-06-14 20:23:51 +08:00
ff4a5bef9b fix: up/down of hysteria must be a valid value 2022-06-14 20:23:36 +08:00
d8dc44e786 Refactor: vmess
Add support for vmess length masking/packetaddr/authenticated length

Add support for zero/aes-128-cfb protcol
2022-06-14 13:21:22 +08:00
c968104a19 fix: udp listen use udp4 when general.ipv6 is false; general.ipv6 default value is true 2022-06-14 12:36:05 +08:00
fbb27b84d1 Chore: add redir-host deprecated warnning 2022-06-14 11:26:04 +08:00
f7481ecadf chore: delete DOQ meaningless ALPN 2022-06-12 23:17:26 +08:00
85c37b473a fix: DOQ blocked dns return result because DOQ goroutine leak 2022-06-12 21:41:01 +08:00
e0c5a85314 Fix: missing import 2022-06-12 21:22:02 +08:00
2fa1a5c4b9 Chore: update tproxy udp packet read logic 2022-06-12 19:37:51 +08:00
23bc231df3 chore: doq default port change to 853, ALPN use doq 2022-06-12 17:53:11 +08:00
2146b605f7 refactor: deprecated params(up_mbps,down_mpbs,auth) in hysteria; up/down no use append unit equivalent up_mbps/down_mbps, default unit is Mbps; up/down become a required option. 2022-06-12 11:52:15 +08:00
8853e97b40 fix: sni invalid on hysteria 2022-06-12 00:00:42 +08:00
06d75da257 Chore: adjust Relay copy memory alloc logic 2022-06-11 20:38:16 +08:00
099aa1e3c2 fix: disable unsafe buffer in windows by default
ref: f49cd6f979
2022-06-10 15:51:34 +08:00
63fdb348db fix: leak dns when domain in hosts list 2022-06-10 14:29:19 +08:00
81ee44f6c0 Merge branch 'rule' into A 2022-06-10 13:38:43 +08:00
130a3a261d refactor: clear linkname,reduce cycle dependencies,transport init geosite function 2022-06-10 13:38:19 +08:00
94368f43eb fix: Vmess URI Scheme 解析问题 2022-06-10 03:15:30 +08:00
9a55213ddc chore: add more shadowsocks tests 2022-06-09 18:10:31 +08:00
5055542d61 chore: update dependencies 2022-06-09 18:05:45 +08:00
23063ae0b9 fix: make CodeQL happy Dreamacro 2022-06-09 17:59:17 +08:00
a7f9aa909a fix: upgrade to yaml v3 2022-06-09 17:59:17 +08:00
15ecc451f3 fix: benchmark read bytes 2022-06-09 17:58:15 +08:00
186a4cfdf3 fix: test broken on opensource repo 2022-06-09 17:58:10 +08:00
220ef9e2e2 chore: add benchmark r/w 2022-06-09 17:58:05 +08:00
7079116aa8 chore: cleanup test code 2022-06-09 17:57:59 +08:00
b9e6de45e6 chore: make linter happy 2022-06-09 17:57:53 +08:00
d3503ff940 fix: fix upgrade header detect (#2134) 2022-06-09 17:57:48 +08:00
c3f4e1ba2e fix: add length check for ssr auth_aes128_sha1 (#2129) 2022-06-09 17:57:41 +08:00
695fb64fa8 fix: vmess ws 2022-06-09 16:23:15 +08:00
d32ab9ce74 fix: 规则匹配默认策略组返回错误 2022-06-09 13:52:02 +08:00
94648989b8 fix: hysteria URI Scheme 解析问题 2022-06-09 00:22:47 +08:00
07522d3c2e chore: 修改test文件 2022-06-08 02:03:34 +08:00
c14c07d2e3 feat: 代理集支持 Hysteria 分享格式订阅解析 2022-06-08 01:50:14 +08:00
9511ccfe47 chore: refine code 2022-06-08 01:47:50 +08:00
ed17a1bf23 fix: group filter touch provider 2022-06-07 17:19:25 +08:00
2a4f2f3942 fix: hysteria dialer 2022-06-07 15:49:10 +08:00
3254eaf51c fix: hysteria parse auth 2022-06-07 15:24:46 +08:00
7941bae141 fix: hysteria parse 2022-06-07 14:53:00 +08:00
35a6666a84 feat: add hysteria 2022-06-07 13:46:54 +08:00
73d5042774 fix: some test 2022-06-07 10:45:32 +08:00
9126cbab91 fix: shadowsocks-2022 on 32-bit systems 2022-06-07 10:44:22 +08:00
f8366f6e42 fix: 代理集转换ws类型
feat: 新增grpc h2 http 等支持
2022-06-07 03:17:33 +08:00
100c9b94ba Merge branch 'dev' into Alpha 2022-06-06 23:15:33 +08:00
8343c3597e fix: doq maybe crash when use adapter 2022-06-06 21:45:08 +08:00
d31adafa11 Merge pull request #76 from nekohasekai/Alpha
feat: add support for shadowsocks 2022 ciphers
2022-06-06 20:21:01 +08:00
1acc6759e9 feat: add support for shadowsocks 2022 ciphers 2022-06-06 19:56:36 +08:00
e995003c27 Merge branch 'dev' into Alpha 2022-06-05 21:23:14 +08:00
9edee2435c chore: amd64v3 改名为amd64,amd64v2改为amd64-compatible, darwin删除v2 2022-06-05 21:23:06 +08:00
5d337b75f9 feat: proxy provider 支持V2ray格式订阅链接 2022-06-05 17:14:24 +08:00
2188fad9c1 Feature: add V2Ray subscription support to proxy provider 2022-06-05 15:59:47 +08:00
edc73f3f33 Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	dns/client.go
#	dns/doh.go
#	dns/doq.go
2022-06-05 14:10:18 +08:00
ade424cbb4 chore: 调整dns interface与adapter部分 2022-06-05 13:37:00 +08:00
6e03773134 chore: 调整dns interface与adapter部分 2022-06-05 12:52:29 +08:00
4ad0294655 chore: adjust makefile 2022-06-04 21:21:42 +08:00
43d3a0c8ea chore: mix the proxy adapter and interface to dns client 2022-06-04 21:18:49 +08:00
c045a4f2a7 Chore: make hadowsocks2 lib embed 2022-06-04 20:29:33 +08:00
20611eb8dc Merge branch 'dev' into Alpha 2022-06-04 19:16:56 +08:00
8ff7e180a4 fix: 当初始化失败时,定时更新失效 2022-06-04 19:15:30 +08:00
3827e00b54 refactor: 抽离http请求方法 2022-06-04 19:14:39 +08:00
c52e689d0d fix: classical rule-set 更新未清理 2022-06-04 19:12:50 +08:00
cb517cb529 chore: 调整目录与包名一致 2022-06-04 03:25:33 +08:00
50bb620aa1 chore: 调整parseRule代码 2022-06-04 03:22:41 +08:00
c745ea63b2 chore: 优化GeoSite初始化代码 2022-06-04 02:58:14 +08:00
8e959bd245 chore: 当无tag时不输出无效日志 2022-06-03 21:00:45 +08:00
298ca42369 chore: 启动参数v,查看版本同时打印使用的tags 2022-06-03 20:23:53 +08:00
ed9b9ce3c5 refactor: 添加no_gvisor 编译tag, 剔除gvisor stack支持, 方便在arm设备上debug 2022-06-03 20:07:30 +08:00
1298d2f8b6 chore: 添加tag no_doq 编译不含doq版本, 仅减少1.5MB(macOS-arm64) 2022-06-03 18:12:06 +08:00
6e84f685ce chore: 更新geox时通过内存存储 2022-06-03 16:50:49 +08:00
1ad87cfec9 chore: 选择fallback时,当节点不可用时触发urltest 2022-06-03 13:32:11 +08:00
9e9f459c0e refactor: 优化proxy server nameserver, 当节点专用dns全部查询失败会回落到正常逻辑 2022-06-02 20:58:25 +08:00
fa3e0c726e chore: 调整解析逻辑 2022-06-02 17:03:08 +08:00
3b038310ab fix: 类型转换错误导致规则解析错误 2022-06-02 15:43:27 +08:00
6709936a8f refactor: 归类规则解析代码 2022-06-02 13:42:18 +08:00
04e5d02ab9 feat: IP-SUFFIX
eg. IP-SUFFIX,0.0.0.124/6,匹配ip二进制后四位(IP-CIDR的倒序),支持ipv6
2022-06-02 12:53:19 +08:00
09d49bac95 Chore: embed shadowsocks2 2022-06-01 21:43:20 +08:00
3360839fe3 Chore: make CodeQL happy 2022-06-01 21:38:05 +08:00
1af39cb228 fix: OpenClash 回环 2022-06-01 12:32:45 +08:00
c95735f083 chore: 调整内置winTun.dll部分 2022-06-01 12:01:08 +08:00
c1285adbf8 Feature: can set custom interface for dns nameserver (#2126) 2022-06-01 10:50:54 +08:00
fa2e6be05d fix: TUN file exists 2022-05-31 10:34:13 +08:00
4092a7c84b feat: proxies group URLTest api 2022-05-30 22:07:09 +08:00
58d299c737 chore: 调整geosite初始化位置 2022-05-30 21:26:41 +08:00
11ddac2b5f refactor: 逻辑规则显示效果 2022-05-30 13:58:37 +08:00
1f95c74f1e chore: 识别线程数 2022-05-29 21:04:47 +08:00
a197fbd4b5 Merge branch 'Alpha' into dev 2022-05-29 20:43:39 +08:00
708b8beadf fix: compile 2022-05-29 20:02:30 +08:00
0e1601e5b6 fix: 调整not规则判断子规则数量,逻辑规则返回payload采用解析后结果 2022-05-29 19:54:11 +08:00
c7355510a2 chore: 调整uid系统判断位置 2022-05-29 18:55:08 +08:00
1faa172944 chore: 调整uid系统判断位置 2022-05-29 18:12:43 +08:00
7a8c98cd90 refactor: 使用 netlink 获取默认网卡 2022-05-29 15:35:08 +08:00
13e907bbd0 refactor: 使用 netlink 配置 ip rule 2022-05-29 15:03:27 +08:00
39e7832676 fix: route on android 2022-05-28 23:29:22 +08:00
9b999e72ce fix: npe 2022-05-28 23:29:03 +08:00
e1a61503e4 Merge branch 'netlink' into Alpha 2022-05-28 21:59:04 +08:00
9272d02149 refactor: 合并部分android代码入linux && ip 使用netlink配置路由 2022-05-28 21:58:29 +08:00
067c02aba1 fix: 调整获取远程目的的位置 2022-05-28 20:01:27 +08:00
d0268bb9a2 chore: 降低并发查询时IPv6等待 2022-05-28 09:58:45 +08:00
fb4872ff7f fix: 关闭并发时双栈使用错误 2022-05-27 20:43:39 +08:00
2044458df9 fix: npe 2022-05-27 20:33:27 +08:00
d6df026550 chore: 更换GeoData下载地址
Signed-off-by: Meta <maze.y2b@gmail.com>
2022-05-27 18:32:23 +08:00
7858ca6cc5 fix: geox url setting 2022-05-27 12:32:59 +08:00
ac36473d13 refactor: 获取远程目的从tunnel中剔除,移至tracker 2022-05-27 09:00:48 +08:00
72fb153fe0 refactor: 优化UDP远程目标获取 2022-05-26 23:41:09 +08:00
527a602eba fix: 更新错误时未停止后续流程,日志修改 2022-05-26 23:13:36 +08:00
a71fd3b4df fix: 启动时检测provider文件是否过期,强制更新 2022-05-26 21:05:00 +08:00
2ebc0383b5 feat: RESTful API support set tcp-concurrent 2022-05-26 19:49:12 +08:00
9d2fc976e2 Chore: upgrade to yaml v3 2022-05-26 17:47:05 +08:00
7431001ed6 feat: RESTful API support update Geo file
and can set update url by user, eg.
geox-url:
  geoip: "http://xxxx/gepip.dat"
  mmdb: "http://xxxx/country.mmdb"
  geosite: "http://xxxx/geosite.dat"
2022-05-24 15:04:13 +08:00
149b4b5b43 feat: RESTful API support disable sniffer 2022-05-24 13:44:52 +08:00
c0eb9aac1c feat: fallback can be select by user 2022-05-24 10:17:44 +08:00
7f41f94fff Fix: benchmark read bytes 2022-05-23 12:58:18 +08:00
d1f0dac302 Fix: test broken on opensource repo 2022-05-23 12:30:54 +08:00
afb3e00067 Chore: add benchmark r/w 2022-05-23 12:27:52 +08:00
79469fc8d6 feat: uid rule support for logic and rule-set 2022-05-22 13:07:20 +08:00
9a31ad6151 Chore: cleanup test go.mod 2022-05-21 17:46:34 +08:00
09cc6b69e3 Chore: cleanup test code 2022-05-21 17:38:17 +08:00
948700eed6 fix: 并发dns查询,由于ipv6阻塞导致某些情况下的网络不通 2022-05-21 00:34:15 +08:00
3ab82849d4 feat: IN-TYPE rule support
eg. IN-TYPE,SOCKS/REDIR/INNER,Proxy
support list: HTTP HTTPS SOCKS SOCKS4 SOCKS5 REDIR TPROXY TUN INNER
2022-05-20 23:17:16 +08:00
0f43a19fdb refactor: new way to get interface change even for linux 2022-05-20 21:44:19 +08:00
cc1c1340a3 feat: 安卓恢复进程规则,可通过enable-process开关,默认true 2022-05-19 20:44:09 +08:00
fe25ae83df refactor: 修改sticky-session尝试逻辑 2022-05-19 20:27:26 +08:00
c787bbe0e5 fix: 热重载Tun配置 2022-05-19 19:19:19 +08:00
7aff9aac82 fix: sticky-sessions异常 2022-05-18 22:29:27 +08:00
8b09db5f7f fix: Rule-Set中不解析DNS
feat: RULE-SET支持no-resolve
2022-05-18 18:43:44 +08:00
b5623602f5 chore: Android auto-detect-interface plus 2022-05-18 12:00:57 +08:00
16b27b3a1f fix: doq过代理错误 2022-05-17 21:30:54 +08:00
8b00be9039 fix: 删除udp触发的错误逻辑 2022-05-17 21:23:28 +08:00
fa9e27c5e4 refactor: 重构失败主动健康检测 2022-05-17 21:15:14 +08:00
8603ac40a1 Chore: make linter happy 2022-05-17 19:58:33 +08:00
f4d9384603 chore: debug log print dns result 2022-05-17 18:21:18 +08:00
c4408612b3 chore: 暴露数据给前端 2022-05-17 16:47:21 +08:00
0742f7db26 refactor: 重构StickySessions 2022-05-17 13:28:54 +08:00
891c2fe899 fix: 当dns被禁用时,dns将根据general ipv6设置解析dns 2022-05-17 09:01:41 +08:00
b831eb178b chore: remove noisy log 2022-05-16 18:20:13 +08:00
962ceaa89e refactor: strategyStickySessions 2022-05-16 17:46:28 +08:00
d52b00bd34 refactor: remove useless code 2022-05-16 17:29:08 +08:00
aa0d174ccb fix: strategyStickySessions nil pointer 2022-05-16 17:06:44 +08:00
b8e9c3d55a fix: geoip ReverseMatch 2022-05-16 17:06:44 +08:00
0b4c498c93 refactor: new way to get interface for android 2022-05-16 17:06:44 +08:00
efc7c82cac feat: "!"(not) support for geosite
eg. GEOSITE,!CN,Proxy & dns.fallback-filter.geosite: ['!CN']
2022-05-15 13:16:45 +08:00
b384449717 Fix: fix upgrade header detect (#2134) 2022-05-15 09:12:53 +08:00
63917aa020 fix: uuid-map return failed error 2022-05-14 23:45:10 +08:00
5016f529af revert: yaml v2 2022-05-14 23:36:19 +08:00
5bd5f1bfda chore: remove Script mode residual code. 2022-05-14 13:00:33 +08:00
d4dcbce9cb chore: log show all ips when all ips shake hands failed 2022-05-13 21:43:42 +08:00
da7ffc0da9 Fix: add length check for ssr auth_aes128_sha1 (#2129) 2022-05-13 11:21:39 +08:00
df8196a68c fix: print process path logic 2022-05-12 18:57:30 +08:00
c1631759fc feat: add strategy:sticky-sessions for LoadBalance
Signed-off-by: Meta <maze.y2b@gmail.com>
2022-05-09 18:56:36 +08:00
9e9c3c810f fixed: make log api unblocked 2022-05-09 18:54:00 +08:00
463101aec1 fix: limit load provider concurrent size 2022-05-08 22:52:46 +08:00
2072964701 revert: tls handshake timeout recovery 10s 2022-05-08 21:56:59 +08:00
aded1b78b5 chore: sniffer give the err to the caller 2022-05-08 09:09:39 +08:00
ca9c859059 Merge remote-tracking branch 'meta/Alpha' into Alpha 2022-05-08 07:59:17 +08:00
55811dae32 fix: Adjust the timing of loading proxy selection 2022-05-08 07:58:26 +08:00
7136d145f8 chore: update dependencies 2022-05-08 00:47:01 +08:00
2fbbf7519f fix: provider auto update 2022-05-08 00:04:16 +08:00
5dd94c8298 Chore: update dependencies 2022-05-07 21:08:15 +08:00
663bf4fbb0 fix: remove misjudgment 2022-05-07 12:53:13 +08:00
f0a22a4a4c chore: modify sniff error log 2022-05-07 12:44:28 +08:00
4ab91520bd refactor: reuse uuid namespace 2022-05-07 12:35:14 +08:00
412b44a981 Fix: decode nil value in slice decoder (#2102) 2022-05-07 11:00:58 +08:00
980d8a2641 refactor: string map to uuidv5 2022-05-06 14:02:34 +08:00
a95d439852 chore: the uuid-map is transferred to the protocol 2022-05-06 13:28:09 +08:00
a08e39faec fix uuid match 2022-05-06 13:08:27 +08:00
b3295262c1 chore: Initialize provider ahead of time 2022-05-05 21:14:46 +08:00
27aa026568 fix: use actual metadata 2022-05-04 20:13:12 +08:00
9969e1706e fix: loadbalance group npe 2022-05-04 19:52:48 +08:00
fb58595d44 feat: Expose remote destination (udp proxy maybe domain of node) 2022-05-04 16:57:08 +08:00
bdfa16ca6f fix: wrong parameters 2022-05-04 01:04:43 +08:00
b307bcb4a9 Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	listener/tun/ipstack/commons/router_linux.go
2022-05-04 01:04:11 +08:00
f26941091b fix: default router with fakeIP when tun enable 2022-05-04 00:59:04 +08:00
6cd5769ed7 fix: default router with fakeIP when tun enable 2022-05-04 00:36:44 +08:00
41adfa65b3 Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	listener/tun/ipstack/commons/router_linux.go
2022-05-03 23:59:41 +08:00
3fbb7c7a2d chore: add default router when tun enable 2022-05-03 23:58:11 +08:00
0aa82c04ea chore: add default router 198.18.0.0/16 when tun enable 2022-05-03 23:28:02 +08:00
5c6f2694c7 chore: sniffer param skip-sni renamed to ship-domain, old param will be removed in the release version 2022-05-03 23:10:59 +08:00
eca7615f08 fix: patch update support tun 2022-05-03 19:31:00 +08:00
52d559bb38 feat: rule-provider support NetWork rule 2022-05-03 01:36:03 +08:00
259736390a feat: rule-provider support rules field 2022-05-03 00:53:22 +08:00
7db07630a7 fix: DNS mapping error when sniffing result is ip, Discard sniffs that result in ip 2022-05-02 22:24:14 +08:00
d617b0f447 style: uid log tidy 2022-05-02 19:52:34 +08:00
80ff5917f7 fix: The sniffer does not clean up the original address 2022-05-02 17:09:24 +08:00
b401da5eba refactor: provider init order 2022-05-02 16:47:48 +08:00
05b25c334f Merge branch 'makefile' into Alpha 2022-05-02 14:43:51 +08:00
2c5a47a275 fix: Failed to get version tag 2022-05-02 14:43:01 +08:00
b2605a9012 fix: tun dns 2022-05-02 14:21:37 +08:00
b929a19f48 refactor: Unified active health detection, supported by load balancing policy group 2022-05-02 13:50:10 +08:00
4b04faa88b fix: http sniffer return host that was handled correctly 2022-05-02 09:51:26 +08:00
5fee0b5bf1 chore: adjust pass to reject.go 2022-05-02 09:16:47 +08:00
27120fb0f5 Merge remote-tracking branch 'Meta/Alpha' into Alpha 2022-05-02 08:49:23 +08:00
0cf539fb82 chore: adjust sniffer constant 2022-05-02 08:49:18 +08:00
26a38bd8de chore: adjust sniffer constant 2022-05-02 08:46:24 +08:00
ebbce4d061 Merge remote-tracking branch 'meta/Alpha' into Alpha 2022-05-02 08:28:00 +08:00
5acd2f6c3a chore: workflow 2022-05-02 08:27:17 +08:00
fe2bc903b8 fix trojan and snell's normal udp 2022-05-02 06:28:27 +08:00
658f1f5cda fix code mistake 2022-05-02 05:34:20 +08:00
5ccc047fe4 chore: adjust sniffer err info 2022-05-02 05:17:13 +08:00
6d704b9cd1 feat: sniffer support http 2022-05-02 05:10:18 +08:00
5f957b5cf9 Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	.github/workflows/prerelease.yml
#	.github/workflows/release.yaml
2022-05-02 05:01:33 +08:00
8681728b8d chore: doq parameters 2022-05-02 05:01:07 +08:00
bb1eb5979b chore: Merge alpha and beta 2022-05-02 01:14:30 +08:00
032b6a2cc5 chore: workflow 2022-05-02 00:59:41 +08:00
45a02e3439 chore: workflow 2022-05-02 00:19:28 +08:00
9d8cd036ff refactor: remove dns and tun relationship, the enabled of dns module should be decided by user 2022-05-01 09:41:27 +08:00
2aad9818e8 fix trojan and snell's udp over tcp 2022-04-30 22:26:38 +08:00
4d5c0d2bf4 fix: auto-route priority wlan0 in Android 2022-04-30 17:43:37 +08:00
861205dbbe support udp in relay if last proxy could udp-over-tcp 2022-04-30 11:36:42 +08:00
9dbe20f2c5 fix: npe when with resolver is nil 2022-04-29 13:03:55 +08:00
8dea62cd63 refactor: del useless file 2022-04-28 23:49:24 +08:00
2d99f86780 fix: dhcp ifacename type 2022-04-28 23:44:37 +08:00
fac63625fc fix: replace with sync.map for GroupBase 2022-04-28 23:43:10 +08:00
b9f270162a refactor: field name 2022-04-28 23:10:08 +08:00
bbbe371ea9 fix: dns specified interface does not change 2022-04-28 22:40:06 +08:00
e2409f9639 Merge branch 'doq' into Alpha 2022-04-28 22:24:02 +08:00
4f634edaa9 refactor: doq dialer 2022-04-28 22:21:48 +08:00
f1dab9e9ce refactor: optimize the performance of filter in proxy-group 2022-04-28 19:01:13 +08:00
8217db82ce Merge remote-tracking branch 'origin/Alpha' into Alpha 2022-04-28 17:47:29 +08:00
f9dd0a6bfc [Skip CI] chore: add docker workflow 2022-04-28 14:24:38 +08:00
b9b25be68d Merge remote-tracking branch 'Meta/Alpha' into Alpha
# Conflicts:
#	.github/workflows/docker.yaml
2022-04-28 14:19:25 +08:00
179e5aad58 chore: add docker workflow 2022-04-28 14:18:54 +08:00
fc1eaf9a9d chore: add docker workflow 2022-04-28 14:11:35 +08:00
1756730693 Merge remote-tracking branch 'meta/Alpha' into Alpha 2022-04-28 12:44:51 +08:00
c7bc67e44c fix: handle metadata when dst is ip:port 2022-04-28 12:44:27 +08:00
949c1551f8 fixup! chore: system err log 2022-04-28 12:37:53 +08:00
b23a333acf Merge remote-tracking branch 'origin/Alpha' into Alpha 2022-04-28 11:51:57 +08:00
47568051bf fix: problems caused when uid is 0 2022-04-28 11:51:40 +08:00
22c1e05e1c fix: rule provider http api crash 2022-04-28 09:44:29 +08:00
c161d5e6be fix: inner request error 2022-04-28 09:24:40 +08:00
573be855f7 fix: file not change modify time when updated rule 2022-04-28 09:07:58 +08:00
a38b2bcb6d Merge remote-tracking branch 'meta/Alpha' into Alpha 2022-04-28 08:56:00 +08:00
2e74986fe4 refactor: adjust provider loading order, remove meaningless pointers 2022-04-28 08:55:45 +08:00
30eaa8add6 fix: count error 2022-04-28 08:54:33 +08:00
b498d2dda2 chore: system err log 2022-04-27 22:54:12 +08:00
96a32f5038 refactor: tcp concurrent 2022-04-27 21:37:20 +08:00
e11f6f84df chore: adjust workflows 2022-04-27 18:05:03 +08:00
5a1e1050b7 chore: adjust sniffer log 2022-04-27 18:04:02 +08:00
73aa8c7be7 feat: support uuid with custom string 2022-04-27 18:02:29 +08:00
183973e823 chore: Adjust the tcp-concurrent and sniffer log 2022-04-27 15:22:42 +08:00
2e08a4b4e1 fix: undefined parameter 2022-04-27 14:42:58 +08:00
564a6fdf35 Chore: http 2022-04-27 12:52:11 +08:00
cca3a1a934 Fix: http proxy Upgrade behavior (#2097) 2022-04-27 12:38:31 +08:00
aef4dd3fe7 Fix: make log api unblocked 2022-04-26 22:36:10 +08:00
6a92c6af4e Fix: http proxy Upgrade behavior (#2097) 2022-04-25 19:50:20 +08:00
ffb49ba4c5 fix: gvisor panic 2022-04-25 18:39:45 +08:00
e4fb10faf9 Chore: update dependencies 2022-04-25 13:23:56 +08:00
16a35527b7 Chore: increase nattable capacity 2022-04-25 13:18:41 +08:00
4fd7d0f707 Chore: use generics as possible 2022-04-25 13:18:30 +08:00
dee1aeb6c3 fix: logic of auto-detect-interface 2022-04-23 23:42:42 +08:00
2f95d56a12 pref: uid style in log 2022-04-23 17:37:50 +08:00
c77993eed3 refactor: tidy auto-route code 2022-04-23 17:31:16 +08:00
ce663a7b96 fix: ipv6 enable logic 2022-04-23 14:21:58 +08:00
9c2ca0feb0 fix: uid match 2022-04-23 13:37:37 +08:00
b8d5321615 feat: cache uid 2022-04-23 12:11:26 +08:00
eb6f7e3158 fix: relay conn error when addr is domain 2022-04-23 10:26:22 +08:00
0947cb4a5a fix: whitelist 2022-04-23 09:52:23 +08:00
0368bb4180 fix: sniffer port whitelist error 2022-04-23 09:36:11 +08:00
4aeac0e227 chore: Adjust the connection IP log 2022-04-23 08:53:51 +08:00
bd3c493c9f fix: ipv6 enable logic 2022-04-23 02:03:10 +08:00
0a99fc4d74 fix: wrong parameter name 2022-04-23 00:45:43 +08:00
19fc70b2c4 fix: general ipv6 is false should be broke ipv6 conn 2022-04-23 00:30:25 +08:00
81b5543b0d feat: support tcp concurrent, Separate dialing and dns resolver ipv6
tcp-concurrent:true
2022-04-23 00:27:22 +08:00
2e1d9a4f2e fix: hotspot for android 2022-04-22 22:25:45 +08:00
de4341c8cd Revert: "fix: proxy-groups filter logic"
This reverts commit 8a85c63b08.
2022-04-22 18:56:35 +08:00
8a85c63b08 fix: proxy-groups filter logic 2022-04-22 17:27:55 +08:00
b0dd74e74e fix: sniffer 2022-04-22 17:00:39 +08:00
4dd9e199b7 fix: uid rule only support linux and android 2022-04-22 16:51:01 +08:00
3d6aea4c1e feat: support uid rule
eg. UID,1000/5000-6000,Proxy
2022-04-22 16:27:51 +08:00
0cb5270452 Merge remote-tracking branch 'origin/Alpha' into Alpha 2022-04-22 15:58:57 +08:00
3f6d2e5f91 feat: dnsHijack support "any"
chore: adjust process debug display logic
2022-04-22 13:30:04 +08:00
3b16fcef92 Chore: wait for system stack to close 2022-04-22 12:44:51 +08:00
f91d106cdf Chore: fix typos 2022-04-22 12:42:20 +08:00
9e6ba64940 fix: add wait timeout, and log 2022-04-21 08:08:37 -07:00
bee1bddceb feat: add sniffer port whitelist, when empty will add all ports 2022-04-21 07:06:08 -07:00
e98dcc4267 [fix] logic 2022-04-21 18:56:33 +08:00
4b79f8de93 [fix] auto-route for android 2022-04-21 17:47:04 +08:00
f40c2eb71d Chore: update dependencies 2022-04-20 22:55:30 +08:00
7ca1a03d73 Refactor: metadata use netip.Addr 2022-04-20 22:52:05 +08:00
6c4791480e Chore: IpToAddr 2022-04-20 22:09:16 +08:00
42d853a7e6 chore: upgrade dependencies 2022-04-20 01:31:33 +08:00
5d36d8b139 Improve: replace bootstrap dns (#2080) 2022-04-19 22:49:39 +08:00
5a4441c47f Chore: add none alias to dummy on ShadowsocksR (#2056) 2022-04-19 22:49:22 +08:00
0ca10798ea Chore: fix typo 2022-04-19 22:38:20 +08:00
3ea3653d7a Chore: persistence fakeip pool state 2022-04-19 22:37:47 +08:00
58cd8f9ac1 fix:force-domain invalid 2022-04-17 21:17:21 +08:00
ea0d236259 chore: change comments 2022-04-17 20:03:53 +08:00
48a01adb7a refactor: sniffer param force and reverses deprecated, will be removed when release version, replace force-domain and skip-sni,
force-domain add '+' equivalent to force is true
sniffer:
  enable: true
  force-domain:
    - "google.com"
  skip-sni:
    - www.baidu.com
  sniffing:
    - tls
2022-04-17 20:02:13 +08:00
f8d7f29856 fix: PASS policy inconsistent names 2022-04-17 14:11:58 +08:00
e010940b61 Improve: replace bootstrap dns (#2080) 2022-04-16 15:31:26 +08:00
1cf9321aa0 fix: domain tree match failed 2022-04-16 11:55:49 +08:00
71a1f5dfbd fix: domain type fix Mapping 2022-04-16 09:51:31 +08:00
25426cba33 chore: log style 2022-04-16 09:04:43 +08:00
9d364f66e9 fix: reverse error when force is false 2022-04-16 08:53:31 +08:00
7c23fa2bd4 fix: sniffer npe 2022-04-16 08:45:18 +08:00
0658ecadd3 fix: adjust loading timing 2022-04-16 08:29:38 +08:00
efbc334b3b Merge branch 'logic-rule' into Alpha 2022-04-16 08:22:08 +08:00
80764217c2 feat: add domain list for sniffer, reverse force logic
when force is false, if domain in the list, will force replace
when force is true, if sniff domain in the list, will skip it
2022-04-16 08:21:31 +08:00
45fe6e996b fix: npe when parse rule 2022-04-16 00:21:08 +08:00
2c9a4d276a Chore: add more github action cache 2022-04-14 23:37:41 +08:00
4dfba73e5c Fix: SyscallN should not use nargs 2022-04-14 23:37:19 +08:00
36a719e2f8 feat: support http headers 2022-04-14 13:07:39 +08:00
c282d662ca Fix: make golangci lint support multi GOOS 2022-04-13 17:51:21 +08:00
b3d7594813 Chore: add none alias to dummy on ShadowsocksR (#2056) 2022-04-13 10:06:06 +08:00
1b6b0052c2 chore:adjust sniffer debuglog info 2022-04-13 08:38:55 +08:00
c981ef0f28 chore: update dependencies 2022-04-13 02:32:55 +08:00
1bf291d240 chore: update dependencies 2022-04-13 02:24:21 +08:00
b179d09efb Chore: adjust ipstack 2022-04-13 02:20:53 +08:00
4be17653e0 Fix: fakeip pool cycle used 2022-04-13 02:19:42 +08:00
21446ba5d4 chore: adjust code 2022-04-12 21:39:31 +08:00
75ce6b59bf Refactor: fakeip pool use netip.Prefix, supports ipv6 range 2022-04-12 20:32:08 +08:00
ce96ac35fb chore:merge & adjust code 2022-04-12 20:20:04 +08:00
173e10abe6 Chore: fix typos 2022-04-12 19:08:13 +08:00
a6eb11ce18 Refactor: DomainTrie use generics 2022-04-12 18:45:47 +08:00
0c65f6962a Refactor: queue use generics 2022-04-12 18:44:13 +08:00
baa9e02af6 Refactor: cache use generics 2022-04-12 18:44:10 +08:00
673541e2a8 Refactor: lrucache use generics 2022-04-12 18:44:07 +08:00
14878b37f6 fix: trojan fail may panic 2022-04-12 18:43:55 +08:00
83e0abaa8c chore: adjust code 2022-04-11 13:23:59 +08:00
7166db2ac9 fix: code logic error 2022-04-10 20:01:35 +08:00
815a060309 Update metadata.go
revet commit 13012a9
2022-04-10 00:47:22 +08:00
544e0f137d feat: sniffer support
sniffer:
  enable: true
  force: false # Overwrite domain
  sniffing:
    - tls
2022-04-09 22:30:36 +08:00
07906c0aa5 fix: parse logic rule error 2022-04-09 22:25:39 +08:00
b2981f921c chore: reduce a little memory 2022-04-09 22:24:48 +08:00
7be3e617ab disable process name on android 2022-04-09 17:54:01 +08:00
9a3bc8ef9e fix: auto detect interface add param[auto-detect-interface], default is true, only use it when tun is enabled 2022-04-07 21:36:19 +08:00
e083d1c57d Revert "Add docker workflow"
This reverts commit ce4902e5e6.
2022-04-07 10:24:23 +08:00
ce4902e5e6 Add docker workflow 2022-04-06 08:31:34 +08:00
4b9edc3b66 revert:the name of tun device on mac 2022-04-05 23:04:59 +08:00
91e48b707b Merge remote-tracking branch 'yaling888/with-tun' into Alpha 2022-04-05 14:44:40 +08:00
7a8af90b86 feat: add SMTPS/POP3S/IMAPS port to sni detect 2022-04-05 03:26:23 +08:00
93d2cfa091 fix: when ssh connect to a ip, if this ip map to a domain in clash, change ip to host may redirect to a diffrent ip 2022-04-05 03:26:23 +08:00
5719b9d22f fix: npe panic 2022-04-04 22:28:47 +08:00
b553dd749b refactor: Some adjustments 2022-04-03 19:15:16 +08:00
9461bcd44e fix: default-nameserver allow DOT and DOH with host is ip 2022-04-03 19:14:21 +08:00
6548dc90fa Merge remote-tracking branch 'Plus/with-tun' into Alpha 2022-04-02 20:48:11 +08:00
908ca20afa fix: dns over proxy may due to cancel request, but proxy live status is fine 2022-04-02 18:24:11 +08:00
13012a9f89 fix: dns over proxy may due to cancel request, but proxy live status is fine 2022-04-02 17:32:37 +08:00
afdcb6cfc7 fix: log level ajust and lint fix 2022-03-31 21:27:25 +08:00
c495d314d4 feat: 添加tls sni 嗅探
# Conflicts:
#	tunnel/statistic/tracker.go
#	tunnel/tunnel.go
2022-03-31 21:27:25 +08:00
e877b68179 Chore: revert "Feature: add tls SNI sniffing (#68)"
This reverts commit 24ce6622a2.
2022-03-31 21:20:46 +08:00
24ce6622a2 Feature: add tls SNI sniffing (#68) 2022-03-31 19:34:40 +08:00
88e4b3575e [Chore] fallback dependency 2022-03-31 00:26:01 +08:00
559b3ff9f3 [Fix] VLESS http conn with tls false
[Chore] Upgrade Dependencies
2022-03-31 00:08:43 +08:00
127634028d Merge remote-tracking branch 'Meta/Alpha' into Alpha 2022-03-30 13:19:05 +08:00
81c5a65f23 Merge remote-tracking branch 'Pro-Plus/with-tun' into Alpha
# Conflicts:
#	README.md
#	adapter/outbound/trojan.go
#	adapter/outbound/vless.go
#	transport/trojan/trojan.go
2022-03-30 13:15:45 +08:00
591ee119c2 docs: warning 2022-03-30 13:05:46 +08:00
5b03cc56e7 Merge remote-tracking branch 'Clash-dev/dev' into Alpha 2022-03-30 12:41:16 +08:00
9ff1f5530e Feature: Trojan XTLS 2022-03-30 00:15:39 +08:00
b3ea2ff8b6 Chore: adjust VLESS 2022-03-29 23:50:41 +08:00
c4216218c8 Merge pull request #24 from MarksonHon/patch-2
Fix systemd service
2022-03-29 14:53:02 +08:00
63840b3358 Fix systemd service 2022-03-29 14:50:12 +08:00
131e9d38b6 Fix: Vless UDP 2022-03-29 07:24:11 +08:00
56e2c172e1 Chore: adjust tun_wireguard cache buffer 2022-03-29 07:24:11 +08:00
b3b7a393f8 Chore: merge branch 'ogn-dev' into with-tun 2022-03-29 07:22:52 +08:00
045dd0589b fix: classical missing count 2022-03-28 21:04:50 +08:00
705311b70e [Chore]修改workflows 2022-03-28 20:52:09 +08:00
55ce40fbd1 [Chore]升级项目依赖
[Chore]隐藏TUN模式在system堆栈启动时弹窗
2022-03-28 20:44:52 +08:00
07fda93111 [Chore]升级项目依赖
[Chore]隐藏TUN模式在system堆栈启动时弹窗
2022-03-28 19:48:32 +08:00
012e044c54 [Chore]完成调试workflows 2022-03-28 19:02:51 +08:00
b323315583 [Chore]调试workflows 2022-03-28 18:58:23 +08:00
4c10d6e212 [Chore]调试workflows 2022-03-28 18:54:00 +08:00
ece3bb360a [Chore]调试workflows 2022-03-28 18:52:19 +08:00
5a7b9bdf45 [Chore]调试workflows 2022-03-28 18:49:24 +08:00
028ecb70c5 [Chore]调整workflows流程2 2022-03-28 18:44:27 +08:00
4e0b22f42d [Chore]调整workflows流程2 2022-03-28 18:41:30 +08:00
dbd27ef910 [Chore]调整workflows流程 2022-03-28 17:07:11 +08:00
ffff1418f2 [Fixed]尝试修复PASS空指针问题
[Chore]调整workflows测试
2022-03-28 16:36:34 +08:00
dd9bdf4e2f Fix: convert size to unit32 in getoridst to solve some mips64 devices cannot get redirect origin dst (#2041)
Change-Id: I40aa73dcea692132e38db980320a8a07ed427fe6

Co-authored-by: Zhao Guowei <zhaoguowei@bytedance.com>
2022-03-28 14:48:51 +08:00
64a5fd02da Merge remote-tracking branch 'tun/with-tun' into Alpha 2022-03-28 10:51:59 +08:00
8df8f8cb08 Chore: adjust gVisor stack 2022-03-28 03:25:55 +08:00
fe76cbf31c Chore: code style 2022-03-28 03:18:51 +08:00
7e2c6e5188 Chore: adjust HealthCheck at first check 2022-03-28 00:46:44 +08:00
4502776513 Refactor: MainResolver 2022-03-28 00:44:13 +08:00
611ce5f5f1 [commit]
[Feat] add Pass type for support temporary skip rule set
2022-03-27 23:44:51 +08:00
9bab2c504e Chore: regenerate protoc file 2022-03-27 07:12:12 +08:00
94b3c7e99a Chore: merge branch 'ogn-dev' into with-tun 2022-03-27 00:27:05 +08:00
0a0b8074f4 refactor: rule-set and its provider 2022-03-26 20:27:41 +08:00
f66c3b6f86 [Bilud]
正常编译
2022-03-26 16:39:50 +08:00
a3d49d1ed4 Merge remote-tracking branch 'dev/dev' into Alpha 2022-03-26 16:27:17 +08:00
0d068e7b5f [Fixed]
弃用过期函数,修复Process Name获取问题
2022-03-26 16:17:44 +08:00
275cc7edf3 Chore: structure support weakly type from float to int (#2042) 2022-03-25 15:22:31 +08:00
24583009c4 Merge remote-tracking branch 'tun/with-tun' into Alpha 2022-03-25 14:20:05 +08:00
4a4b1bdb83 Chore: adjust tun RelayDnsPacket 2022-03-25 04:09:11 +08:00
c6efa74a6b Fix: udp 4In6 of tun system stack 2022-03-25 03:42:46 +08:00
a593d68c42 build test 2022-03-24 23:42:49 +08:00
520657e953 [Fix] use direct to update http providers when proxy 寄 2022-03-24 12:34:45 +08:00
6c64164bee [skip ci] [Fix] ban auto set iptables when tun is enabled 2022-03-23 20:37:46 +08:00
9b4ddbed2c [skip ci] [Pre] avoid npe 2022-03-23 13:48:21 +08:00
79d984ee8e [Fix] url-test npe 2022-03-23 13:29:51 +08:00
7a54d616c4 [SKIP CI]
Merge remote-tracking branch 'Pro-Plus/with-tun' into Alpha

# Conflicts:
#	README.md
#	hub/route/server.go
2022-03-23 13:23:34 +08:00
f19b67fe9d bypass support for auto-iptables 2022-03-23 11:36:13 +08:00
91e83ea955 delete useless field 2022-03-23 10:18:26 +08:00
a375b85fa0 [skip ci]
# Conflicts:
#	.github/workflows/linter.yml
#	.github/workflows/release.yml
#	config/config.go
#	go.mod
#	go.sum
#	hub/executor/executor.go
2022-03-23 01:41:42 +08:00
ef915c94dc Feature: flush fakeip pool 2022-03-23 01:05:43 +08:00
4cc661920e [Fix] redir-host use host not ip 2022-03-22 23:31:23 +08:00
f4312cfa5a Chore: adjust the signal 2022-03-22 18:40:33 +08:00
ac4cde1411 Refactor: iptables auto config, disabled by default 2022-03-22 05:38:42 +08:00
b5f6f26de4 Update version.go
[BUILD TEST]
2022-03-22 01:39:00 +08:00
e068563b58 Merge pull request #22 from Adlyq/Alpha-pr
[skip ci]
[Fix] skip when country code not found in GeoIP.dat
2022-03-22 00:33:02 +08:00
bf6839e5f3 Merge pull request #23 from Adlyq/Alpha-pr-iptabls
[skip ci] auto change interface for tproxy
2022-03-22 00:32:47 +08:00
e0040b7e5d [Fix] do not monitor when auto-iptables is false 2022-03-21 20:29:07 +08:00
3beb71b6e1 auto change interface for tproxy 2022-03-21 19:51:27 +08:00
668d29d91f init sequence adjustment 2022-03-21 19:47:21 +08:00
5386c3903d delete useless code 2022-03-21 18:09:36 +08:00
6a4d2b3368 Change type conversion method 2022-03-21 12:34:32 +08:00
d9d8507c8f [Fix] skip when country code not found in GeoIP.dat 2022-03-21 12:24:39 +08:00
2c0890854e Fix: retry create TUN on Windows 2022-03-20 21:27:33 +08:00
bac04ab54b Merge branch 'ogn-dev' into with-tun 2022-03-20 21:26:25 +08:00
8c9e0b3884 Chore: use GOAMD64 v1 on build docker image 2022-03-20 11:32:18 +08:00
fc8092f7cc Fix: wintun dns address 2022-03-20 04:19:48 +08:00
5b7f46bc97 [skip ci][内容]
1.调整部分代码
2022-03-20 02:39:48 +08:00
d1838f663e Merge remote-tracking branch 'yaling888/with-tun' into Alpha
# Conflicts:
#	listener/tun/tun_adapter.go
2022-03-19 22:37:51 +08:00
2d1c031ce0 [skip ci][内容]
1.修复部分空指针问题
2.修改go.mod
2022-03-19 22:28:28 +08:00
e67f94b87a [内容]
同步至最新v1.10.0
2022-03-19 15:01:49 +08:00
2df890c4ee Merge remote-tracking branch 'clash/dev' into Alpha
# Conflicts:
#	Makefile
2022-03-19 14:53:47 +08:00
30d4668008 Chore: fix typo (#2033) 2022-03-19 13:58:51 +08:00
02333a859a Chore: split amd64 v3 to special release 2022-03-19 13:42:06 +08:00
f9cc1cc363 Fix: routing-mark option doesn't work on proxies (#2028) 2022-03-19 13:29:30 +08:00
520256365e [内容]
1.wintun.dll 0.14.1
2022-03-19 01:54:21 +08:00
9270d3c475 [内容]
1.autoIptables 开关
2.go.mod 调整
3.processName 调整
4.makefile 调整
5.Tun模块 部分代码调整
2022-03-19 01:11:27 +08:00
c8b1050c15 Merge pull request #19 from Adlyq/Alpha-pr
[skip ci]Only prompt when interface cannot be found
2022-03-18 21:45:50 +08:00
39de5d58c8 Only prompt when interface cannot be found 2022-03-18 17:41:06 +08:00
7b7abf6973 Feature: auto detect interface if switch network 2022-03-18 17:03:50 +08:00
a38f30ec3b Merge pull request #18 from Adlyq/Alpha
[Fix] Process name display for Android
2022-03-18 13:20:35 +08:00
2ea92d70f9 Merge remote-tracking branch 'upstream/Alpha' into Alpha 2022-03-18 12:38:16 +08:00
8e5f01597e Fix: build 2022-03-18 05:21:28 +08:00
546f2fa739 Chore: make fake ip pool start with the third ip 2022-03-18 05:17:47 +08:00
ea2e715da9 Merge remote-tracking branch 'origin/Alpha' into Alpha
# Conflicts:
#	go.mod
#	go.sum
2022-03-18 02:36:09 +08:00
1350330fe0 1.fix module package
2.fix govet error
2022-03-18 02:35:15 +08:00
317797acc8 1.fix module package
2.fix govet error
2022-03-18 01:25:59 +08:00
8766764d49 fix 2022-03-18 00:40:39 +08:00
b8d48e1618 Merge remote-tracking branch 'upstream/Alpha' into Alpha 2022-03-18 00:33:27 +08:00
f972d1fa58 update 2022-03-18 00:27:48 +08:00
df78ba8fa6 update 2022-03-18 00:24:38 +08:00
0c83575302 Merge remote-tracking branch 'upstream/Alpha' into Alpha 2022-03-18 00:10:37 +08:00
e9151bc43f update 2022-03-17 23:57:58 +08:00
68345b6a19 Merge remote-tracking branch 'upstream/Alpha' into Alpha 2022-03-17 23:40:51 +08:00
435bee0ca2 update 2022-03-17 23:24:07 +08:00
92d169ca81 [Fix] Process name display for Android 2022-03-17 20:31:16 +08:00
30f1b29257 Merge remote-tracking branch 'yaling888/with-tun' into Alpha
# Conflicts:
#	.github/workflows/codeql-analysis.yml
#	.github/workflows/linter.yml
#	.github/workflows/release.yml
#	Makefile
#	README.md
#	adapter/outbound/vless.go
#	component/geodata/memconservative/cache.go
#	component/geodata/router/condition.go
#	component/geodata/router/condition_geoip.go
#	component/geodata/standard/standard.go
#	component/geodata/utils.go
#	config/config.go
#	config/initial.go
#	constant/metadata.go
#	constant/path.go
#	constant/rule.go
#	constant/rule_extra.go
#	dns/client.go
#	dns/filters.go
#	dns/resolver.go
#	go.mod
#	go.sum
#	hub/executor/executor.go
#	hub/route/configs.go
#	listener/listener.go
#	listener/tproxy/tproxy_linux_iptables.go
#	listener/tun/dev/dev.go
#	listener/tun/dev/dev_darwin.go
#	listener/tun/dev/dev_linux.go
#	listener/tun/dev/dev_windows.go
#	listener/tun/dev/wintun/config.go
#	listener/tun/dev/wintun/dll_windows.go
#	listener/tun/dev/wintun/session_windows.go
#	listener/tun/dev/wintun/wintun_windows.go
#	listener/tun/ipstack/commons/dns.go
#	listener/tun/ipstack/gvisor/tun.go
#	listener/tun/ipstack/gvisor/tundns.go
#	listener/tun/ipstack/gvisor/utils.go
#	listener/tun/ipstack/stack_adapter.go
#	listener/tun/ipstack/system/dns.go
#	listener/tun/ipstack/system/tcp.go
#	listener/tun/ipstack/system/tun.go
#	listener/tun/tun_adapter.go
#	main.go
#	rule/common/base.go
#	rule/common/domain.go
#	rule/common/domain_keyword.go
#	rule/common/domain_suffix.go
#	rule/common/final.go
#	rule/common/geoip.go
#	rule/common/geosite.go
#	rule/common/ipcidr.go
#	rule/common/port.go
#	rule/parser.go
#	rule/process.go
#	test/go.mod
#	test/go.sum
#	transport/vless/xtls.go
#	tunnel/tunnel.go
2022-03-17 17:41:02 +08:00
c503e44324 Merge pull request #17 from Adlyq/Alpha
[Fix] Parse
2022-03-17 12:28:45 +08:00
ce509295c0 [Fix] Parse 2022-03-17 12:26:43 +08:00
f671d6a1fd [Fix] Parse 2022-03-17 12:23:50 +08:00
8d0ae4284d Chore: use gateway address of fake ip pool as the TUN device address 2022-03-17 07:41:18 +08:00
e194efcecb Migration: go 1.18 2022-03-17 01:51:28 +08:00
609d69191a Merge remote-tracking branch 'clash/dev' into Alpha
# Conflicts:
#	.github/workflows/docker.yml
#	adapter/outboundgroup/fallback.go
#	adapter/outboundgroup/loadbalance.go
#	adapter/outboundgroup/relay.go
#	adapter/outboundgroup/selector.go
#	adapter/outboundgroup/urltest.go
#	config/config.go
#	go.mod
#	go.sum
#	main.go
#	test/go.mod
#	test/go.sum
2022-03-17 01:41:51 +08:00
c791044ddf Merge remote-tracking branch 'origin/Alpha' into Alpha 2022-03-17 00:12:26 +08:00
dc2abe6eeb [Build test] 1.18
[Updata] wintun.dll
2022-03-17 00:12:11 +08:00
1071e3f4a3 [Build test] 1.18
[Updata] wintun.dll
2022-03-17 00:02:22 +08:00
acc249495d [Build test] 1.18 2022-03-16 23:30:29 +08:00
5a2cc9a36f [Fix] 优化geodata初始化逻辑 2022-03-16 23:09:05 +08:00
1cc6cfab9c [Fix] 优化geodata初始化逻辑 2022-03-16 23:02:16 +08:00
0183d752a0 [Fix] 优化geodata初始化逻辑 2022-03-16 22:55:18 +08:00
b8d635a4b3 Migration: go 1.18 2022-03-16 22:00:20 +08:00
2f24e49ff6 [build test] 1.18 2022-03-16 21:47:00 +08:00
346d817dba Chore: Merge branch 'ogn-dev' into with-tun 2022-03-16 20:16:30 +08:00
3a9bbf6c73 Fix: should keep alive in tcp relay 2022-03-16 18:17:28 +08:00
016862f7a5 [build test]1.18 2022-03-16 17:54:44 +08:00
c3df768f79 [build test] 2022-03-16 17:33:08 +08:00
0f2123179a [build test] 2022-03-16 17:29:09 +08:00
fb7d340233 Fix: docker build makefile 2022-03-16 12:13:59 +08:00
6a661bff0c Migration: go 1.18 2022-03-16 12:10:13 +08:00
1034780e8e [build test] 2022-03-16 00:43:08 +08:00
f01ac69654 Merge remote-tracking branch 'clash/dev' into Alpha
# Conflicts:
#	.github/workflows/codeql-analysis.yml
#	.github/workflows/docker.yml
#	.github/workflows/linter.yml
#	.github/workflows/stale.yml
#	Makefile
#	component/dialer/dialer.go
#	config/config.go
#	constant/metadata.go
#	constant/rule.go
#	rule/common/domain.go
#	rule/common/domain_keyword.go
#	rule/common/domain_suffix.go
#	rule/common/final.go
#	rule/common/ipcidr.go
#	rule/geoip.go
#	rule/parser.go
#	rule/port.go
#	rule/process.go
2022-03-15 23:13:41 +08:00
c85305ead8 [Skip CI] 2022-03-15 22:25:33 +08:00
3e89bee524 [Skip CI] 2022-03-15 11:47:42 +08:00
d1dd21417b Feature: add tzdata to Dockerfile (#2027)
Co-authored-by: suyaqi <suyaqi@wy.net>
2022-03-15 11:30:52 +08:00
9ff32d9e29 Chore: use slice instead of map for system stack udp receiver queue 2022-03-15 05:19:29 +08:00
d486ee467a Fix: test 2022-03-15 03:39:45 +08:00
20b66d9550 Style: code style 2022-03-15 02:55:06 +08:00
5abd03e241 Fix: exclude the broadcast address to fake ip pool 2022-03-15 02:43:40 +08:00
68fccfacc0 [Skip CI] 2022-03-15 02:20:19 +08:00
cf52fbed65 [Skip CI] 2022-03-15 02:06:57 +08:00
a924819fbf [Fixed] rule-set of classical allow adding GEOIP 2022-03-14 21:48:36 +08:00
13c82754ff [Fixed] show rule count when parse failed 2022-03-14 21:43:58 +08:00
002163f07b [Fixed] memory leak 2022-03-13 18:35:55 +08:00
9c5b184db6 [Fixed] handle network protocol[0] panic (not pretty) 2022-03-13 18:34:49 +08:00
359f8ffca3 Fix: should use the correct gateway for TUN system stack 2022-03-13 17:48:43 +08:00
46b9a1092d Chore: embed the RuleExtra into Base 2022-03-13 01:22:05 +08:00
8fbf93ccc8 Chore: Merge branch 'ogn-dev' into with-tun 2022-03-13 01:15:35 +08:00
b866f06414 Chore: move find connection process to tunnel (#2016) 2022-03-12 19:07:53 +08:00
8b4f9a35f6 Chore: bump to go1.18rc1, use netip.Addr to replace net.IP with system TUN stack 2022-03-12 02:16:13 +08:00
9683c297a7 Chore: add more details to process resolving (#2017) 2022-03-09 13:41:50 +08:00
8333815e95 Chore: refactor TUN 2022-03-09 05:08:35 +08:00
d49871224c Fix: should only resolve local process name 2022-03-09 00:32:21 +08:00
ba7bcce895 Chore: code style 2022-03-09 00:32:21 +08:00
71e002c2ef Merge branch 'ogn-dev' into with-tun 2022-03-09 00:30:38 +08:00
f6c7281bb7 Chore: update github action workflow 2022-03-06 21:48:37 +08:00
83bfe521b1 Fix: should split linux process name with space (#2008) 2022-03-05 18:25:16 +08:00
3ab784dd80 Merge pull request #16 from Dabrit/test
[Skip CI]README Improvements
2022-03-05 09:03:25 +08:00
f1c4d85eb3 Update README.md 2022-03-05 01:44:48 +08:00
b9fc393f95 Naming edited 2022-03-05 01:33:11 +08:00
557347d366 Merge pull request #15 from Dabrit/test
Optimize reading experience of linux users
2022-03-04 23:25:02 +08:00
a7f3b85200 Edit username to adapt Linux username naming rule 2022-03-04 22:36:15 +08:00
7550067fde [Fixed] skip maybe invaild ip data packet 2022-03-04 22:32:33 +08:00
076a0840bf [Fixed] domian or ipcidr is used before initialization 2022-03-04 22:32:25 +08:00
5ebcc526de [Fixed] match not some ip in ipcidr provider 2022-03-04 22:32:25 +08:00
3772ad8ddb Revise mismatching targets from README. 2022-03-04 22:22:49 +08:00
17c53b92b9 Fix: iptables routing mark init 2022-03-03 05:02:17 +08:00
0b9022b868 Chore: update dependencies 2022-03-03 04:23:03 +08:00
5e0d4930cb Merge branch 'ogn-dev' into with-tun 2022-03-03 04:17:00 +08:00
b52d0c16e9 Chore: vmess test remove all alterid 2022-02-27 18:00:04 +08:00
5ad7237fa7 Merge pull request #14 from Adlyq/Alpha
Fix the filter under proxy-group to filter other groups
2022-02-27 00:34:08 +08:00
49e25f502f Merge pull request #11 from ttyykpe/patch-1
Makefile add android-armv8
2022-02-27 00:33:58 +08:00
06942c67fd Fix the filter under proxy-group to filter other groups 2022-02-23 16:17:29 +08:00
705e5098ab Chore: use SIMD for AMD64 and ARM64 system stack checksum 2022-02-23 14:51:04 +08:00
ac5c57ecef Chore: compatible with VMESS WS older version configurations 2022-02-23 14:21:53 +08:00
cd3b139c3f Chore: use "-m mark --mark" instead of "-m owner --uid-owner" 2022-02-23 14:19:59 +08:00
592b6a785e Fix: find process name by UDP network on macOS 2022-02-23 14:04:47 +08:00
2f234cf6bc Feature: process condition for rules 2022-02-23 14:01:53 +08:00
132a6a6a2f Fix: listener tcp keepalive & reuse net.BufferedConn (#1987) 2022-02-23 11:22:46 +08:00
d876d6e74c Feature: resolve ip with a proxy adapter 2022-02-23 02:38:50 +08:00
b192238699 Merge from remote branch 2022-02-23 01:00:27 +08:00
3b2ec3d880 Chore: upgrade gvisor 2022-02-22 22:30:41 +08:00
9259c9f3ff Makefile add android-armv8 2022-02-21 18:04:38 +08:00
03e4b5d525 Chore: use golangci-lint config file 2022-02-19 00:08:51 +08:00
a0221bf897 Fix: routing-mark should effect on root 2022-02-17 14:23:47 +08:00
37cf166d14 Merge pull request #10 from Adlyq/Alpha
Full regexp support
2022-02-16 23:10:07 +08:00
27292dac0c Replace the regular implementation of the filter for proxy-providers and proxy-groups with regex2 2022-02-16 22:18:05 +08:00
847c91503b [build] 2022-02-06 05:08:11 +08:00
ca8ed0a01b [Fix]GeoSite.dat initial in logic rule 2022-02-06 04:41:34 +08:00
46dc262e8e 合并拉取请求 #9
add the doc of local build
2022-02-06 04:34:08 +08:00
7465eaafa1 [Fix]GeoSite.dat initial in logic rule 2022-02-06 04:30:54 +08:00
d70cfefde7 add the doc of local build 2022-02-06 04:02:26 +08:00
52c37f7140 Merge pull request #8 from qzi/Dev
add trojan xtls sample
2022-02-06 03:52:41 +08:00
180bce2940 add trojan xtls sample 2022-02-06 03:37:40 +08:00
4a446c4e31 [build] 2022-02-06 01:59:35 +08:00
d7f5e8d3de [Skip CI] 2022-02-06 00:56:13 +08:00
0a180eeb40 忽略geosite文件大小写 2022-02-06 00:51:37 +08:00
7ff48ea42d [build] 2022-02-05 22:05:20 +08:00
a0e44f4041 [FEAT]
1.Add geodata loader mode switch
yaml   geodata-loader: memconservative / standard
2.Add AutoIptables mode switch
yaml   auto-iptables: true
3.support trojan xtls
4.update gvisor
5.Fix process
6.Fix darwin autoRoute
2022-02-05 21:33:49 +08:00
2f6f9ebc2e Merge branch 'Dev' into Meta
# Conflicts:
#	config/config.go
2022-02-05 19:30:12 +08:00
28a1475f66 [FEAT] Add geodata loader mode switch 2022-02-05 02:42:49 +08:00
c28f42d823 [FEAT] Add geodata loader mode switch 2022-02-05 00:51:06 +08:00
2bf34c766e [Feat]
support trojan xtls
change geodataloader mode as memconservative
2022-02-04 23:33:36 +08:00
35b19c3d7f Merge branch 'Dev' into Feature
# Conflicts:
#	Makefile
2022-02-04 18:44:35 +08:00
90e6ed4612 [Fixed] Fixed clash process name is Clash.Meta 2022-02-04 17:38:06 +08:00
ae5a790510 [Fixed] Abnormal rule when host is ip addr 2022-02-04 17:38:06 +08:00
3b277aa8ec [Feat]
update gvisor
Chore: use "-m mark --mark" instead of "-m owner --uid-owner"
2022-02-04 06:11:24 +08:00
176eb3926b Merge remote-tracking branch 'pro-plus/plus-pro' into Feature
# Conflicts:
#	.github/workflows/Alpha.yml
#	.github/workflows/codeql-analysis.yml
#	.github/workflows/docker.yml
#	.github/workflows/linter.yml
#	.github/workflows/stale.yml
#	Makefile
#	README.md
#	adapter/outbound/vless.go
#	component/dialer/dialer.go
#	component/geodata/geodata.go
#	component/geodata/router/condition.go
#	config/config.go
#	config/initial.go
#	constant/metadata.go
#	constant/path.go
#	constant/rule.go
#	constant/rule_extra.go
#	dns/filters.go
#	go.mod
#	go.sum
#	hub/executor/executor.go
#	hub/route/configs.go
#	listener/listener.go
#	listener/tun/dev/dev.go
#	listener/tun/dev/dev_darwin.go
#	listener/tun/dev/dev_linux.go
#	listener/tun/dev/dev_windows.go
#	listener/tun/dev/dev_windows_extra.go
#	listener/tun/dev/wintun/dll_windows.go
#	listener/tun/dev/wintun/session_windows.go
#	listener/tun/ipstack/gvisor/tun.go
#	listener/tun/ipstack/gvisor/tundns.go
#	listener/tun/ipstack/stack_adapter.go
#	listener/tun/ipstack/system/tun.go
#	listener/tun/tun_adapter.go
#	main.go
#	rule/base.go
#	rule/common/process.go
#	rule/geoip.go
#	rule/parser.go
#	rule/port.go
#	test/go.mod
#	test/go.sum
#	test/vless_test.go
#	transport/vless/xtls.go
#	tunnel/tunnel.go
2022-02-04 05:30:21 +08:00
776728fb30 [Feat]
update gvisor
Chore: use "-m mark --mark" instead of "-m owner --uid-owner"
2022-02-04 04:47:40 +08:00
a732e1a603 Merge remote-tracking branch 'clash/dev' into Dev 2022-02-04 02:40:15 +08:00
1cdaf782ba Merge remote-tracking branch 'clash/dev' into Feature 2022-02-04 02:38:32 +08:00
f1157d0a09 Chore: use "-m mark --mark" instead of "-m owner --uid-owner" 2022-02-02 21:59:44 +08:00
f376409041 Chore: upgrade gvisor 2022-02-01 02:00:10 +08:00
45b3afdd33 Fix: new version golangci-lint check 2022-01-30 01:49:27 +08:00
875fdb3a5b Revert "Chore: upgrade gvisor version"
This reverts commit d633e3d96e.
2022-01-30 00:45:02 +08:00
25e115d042 Feature: process condition for rules 2022-01-28 22:52:35 +08:00
d633e3d96e Chore: upgrade gvisor version 2022-01-28 22:42:58 +08:00
6e9d837a7d Merge from remote branch 2022-01-28 19:51:40 +08:00
63b9d66365 [Feat]
1.Add DNS over QUIC support
2.Replace Country.mmdb with GeoIP.dat
3.build with Alpha tag
2022-01-27 12:45:11 +08:00
be0fadc09e [Feat]
1.Add DNS over QUIC support
2.Replace Country.mmdb with GeoIP.dat
3.build with Alpha tag
2022-01-27 12:25:53 +08:00
b1a639feae Fix: domain trie search 2022-01-26 22:28:13 +08:00
76dccebbf6 github action build config 2022-01-26 21:35:18 +08:00
cd5b735973 [Refactor] logic rule parse 2022-01-26 21:34:49 +08:00
9e4e1482d9 [chore] Replace Country.mmdb with GeoIP.dat 2022-01-26 12:01:14 +08:00
9974fba56e Update dev.yml 2022-01-25 21:59:48 +08:00
4bd5764c4e Update Makefile 2022-01-25 21:47:11 +08:00
deeab8b45f [test] dev build 2022-01-25 21:34:06 +08:00
af30664c51 [test] dev build 2022-01-25 21:12:49 +08:00
6962f0b7e1 [update] dev build 2022-01-25 20:54:56 +08:00
6e5859d1bf [update] dev build 2022-01-25 20:53:07 +08:00
87ca93b979 Update build.yaml 2022-01-25 20:40:03 +08:00
11052d8f77 github action add build
(cherry picked from commit bdec838673767977c14191861ac1b9a8291e2ffc)
2022-01-25 20:33:30 +08:00
a5ce62db33 Merge branch 'clash-dev' into Dev 2022-01-25 15:05:24 +08:00
2f8e575308 [Fixed] modified RULE-SET supported rule 2022-01-23 18:35:48 +08:00
62b70725ef [Fixed] GEOSITE rule load fail 2022-01-23 18:27:44 +08:00
8595d6c2e9 [Feature]
1.Add Network rule, match network type(TCP/UDP)
2.Add logic rules(NOT,OR,AND)
-AND,((DOMAIN,baidu.com),(NETWORK,UDP)),REJECT

(cherry picked from commit d7092e2e37f2c48282c878edea1b2ebc2912b09a)
2022-01-22 22:37:07 +08:00
03b956b7a3 [Fixed] auto-route support use ip route 2022-01-22 13:24:31 +08:00
e5c99cbee7 modify gitignore 2022-01-21 22:39:00 +08:00
58a47e1835 [Style] clear unless notes 2022-01-21 22:38:28 +08:00
daf83eb6f7 [Fixed] select group crash 2022-01-21 22:38:02 +08:00
bb68b59c9a Merge pull request #7 from CHIZI-0618/DnsHijack
Fix DnsHijack default value bug.
2022-01-21 18:27:26 +08:00
c3cfa3d6cd Fix DnsHijack default value bug. 2022-01-21 18:11:21 +08:00
b15344ec78 [Refactor]
1.allow maybe empty group
2.use COMPATIBLE(DIRECT alias) when proxy group is empty
3.http provider pass through tunnel
2022-01-18 21:09:36 +08:00
cfe7354c07 Improve: change provider file modify time when updated (#1918) 2022-01-18 13:32:47 +08:00
56c38890f9 Merge from remote branch[ssh] 2022-01-18 10:05:06 +08:00
daae846db3 Merge from remote branch 2022-01-18 09:51:20 +08:00
9732efe938 Fix: tls handshake requires a timeout (#1893) 2022-01-15 19:33:21 +08:00
ee6c1871a9 [Refactor] lazy loading geosite.bat 2022-01-11 22:17:24 +08:00
8f3385bbb6 Feature: support snell v3 (#1884) 2022-01-10 20:24:20 +08:00
00e44cd141 [Style] Modify the default configuration, tun config delete default hijack dns and modify auto-route to false. modify NameServer to 223.5.5.5 and 119.29.29.29 by Skyxim 2022-01-09 00:36:05 +08:00
4ab986cccb [Refactor] gvisor support hijack dns list
dns-hijack:
 - 1.1.1.1
 - 8.8.8.8:53
 - tcp://1.1.1.1:53
 - udp://223.5.5.5
 - 10.0.0.1:5353
2022-01-09 00:35:45 +08:00
64869d0f17 [Fixed] Remove the Linux automatic routing configuration Change the name of the Linux network card to utun 2022-01-08 16:57:59 +08:00
7f0368da66 [Style] Adjust delete routes on macos 2022-01-08 16:55:02 +08:00
4f1b227ca2 [Style] Positive health check 2022-01-08 09:23:49 +08:00
16abba385a [Style] Adjust the routing table of tun on mac 2022-01-07 22:40:05 +08:00
75b5f633cd [Fixed] Positive health check multithreading is not safe 2022-01-07 12:58:40 +08:00
8ae68552a6 [Fixed] Stupid mistakes 2022-01-06 10:49:50 +08:00
d35d6c9ac9 [Fixed] Stupid mistakes 2022-01-06 10:49:26 +08:00
a832cfdb65 [Fixed] compatible cfw 2022-01-05 19:28:54 +08:00
951a5a0eb5 [update]readme 2022-01-05 18:45:32 +08:00
89609cc4a2 [update]readme 2022-01-05 17:04:56 +08:00
bfb976bbdc [test]Add name filter to proxy group 2022-01-05 12:19:49 +08:00
d237b041b3 Fix: ignore empty dns server error 2022-01-05 11:41:31 +08:00
a15d2535f1 升级版本号 2022-01-05 11:41:17 +08:00
610c79570a make tun config compatible with premium 2022-01-05 11:24:00 +08:00
051c81518c make tun config compatible with premium 2022-01-05 01:56:35 +08:00
0209efd423 Revert "make tun config compatible with premium"
This reverts commit ba6fdd2962.
2022-01-05 01:56:05 +08:00
ba6fdd2962 make tun config compatible with premium 2022-01-05 01:50:43 +08:00
c14dd79e69 Merge from remote branch 2022-01-05 01:46:37 +08:00
9475799615 make tun config compatible with premium 2022-01-05 00:33:42 +08:00
14917c8af1 merge clash 1.9.0 2022-01-04 17:58:50 +08:00
3bb32d12e0 Merge remote-tracking branch 'clash/dev' into Meta
# Conflicts:
#	.github/workflows/docker.yml
#	dns/server.go
#	go.mod
#	go.sum
#	hub/executor/executor.go
#	test/go.mod
#	test/go.sum
2022-01-04 17:31:07 +08:00
3cb87e083c Fix: duplicate provider err typo 2022-01-03 17:21:27 +08:00
8c6d0c6757 Chore: fix docker dependencies security warning 2022-01-02 11:15:40 +08:00
cb95326aca Chore: update dependencies 2022-01-02 01:15:49 +08:00
8679968ab0 Fix: multiple port string parsing overflow (#1868)
Ports in TCP and UDP should be parsed as an unsigned integer,
otherwise ports > 32767 get truncated to 32767. As this is
the case with Metadata.UDPAddr(), this fundamentally breaks
UDP connections where demand for high port numbers is high.

This commit fixes all known cases where ParseInt is used for ports,
and has been verified to fix Discord voice connections on port
50001~50004.

Fixes: d40e5e4fe6

Co-authored-by: Hamster Tian <haotia@gmail.com>
2022-01-02 01:09:29 +08:00
204a72bbd3 Chore: remove forward compatible code 2022-01-02 00:48:57 +08:00
013b839678 [Fix] Linux Tun 2021-12-27 07:09:45 +08:00
a06382cebc [test] 2021-12-27 06:44:17 +08:00
ebc3f36236 [fix]autoIptables 2021-12-27 03:29:14 +08:00
e2a0437685 [fix] 2021-12-27 03:16:48 +08:00
82c8e02d02 [Style] Add User-Agent for provider request 2021-12-26 22:26:53 +08:00
7267c58913 Chore: ReCreate* do side effect job (#1849) 2021-12-26 22:08:53 +08:00
a210ec4197 [Feature] 添加unified-delay boolean 控制延迟测试,默认为false,当设置true时忽略握手延迟,将统一延迟结果,从而利于不同协议的url-test 2021-12-26 21:20:41 +08:00
14ae87fcd0 Chore: remove reduce regex compile (#1855) 2021-12-26 20:47:12 +08:00
0b72395704 Merge pull request #5 from xsxun/patch-1
Update vless.go, fix udp blocked
2021-12-20 18:13:43 +08:00
8955107d6b Update vless.go 2021-12-20 12:59:06 +08:00
Fan
ee6fc12709 Fix: when both providers and proxies are present, use the health check configuration for proxies (#1821)
Co-authored-by: Ho <ho@fluidex.com>
2021-12-12 20:37:30 +08:00
69aef9cec0 [Fixed] Configure tun interface on linux 2021-12-11 22:34:45 +08:00
9e44e21406 [Fixed] launch resolver an enhancer when tun mode 2021-12-09 23:00:54 +08:00
b0fdd8dc47 [Fixed] Add retry to open tun 2021-12-09 22:52:32 +08:00
e92ef587bb [Fixed] The array may be sent out of bounds 2021-12-09 22:52:32 +08:00
5657aa50cf Merge from remote branch 2021-12-09 21:38:24 +08:00
7d17d53a8f [readme] 2021-12-09 17:54:53 +08:00
78e105f3b2 Chore: builtin right mime of .js (#1808) 2021-12-08 13:38:25 +08:00
58ef4ddbba [Fixed]Meaningless pointer 2021-12-07 20:49:39 +08:00
a78b89d16e Revert: Revert Redir-Host, please add fallback dns and append proxy adapter
DNS pass proxy use:
- protocol://ip:port#AdapterName
- protocol://ip:port/query#AdapterName

sure as:
- tls://1.1.1.1:853#DNS
2021-12-06 22:45:59 +08:00
833b43a538 Fixed: Does RuleSet resolve ip logic modification 2021-12-06 21:47:22 +08:00
8df3efe932 [Fix] 修正因xray服务端alpn参数为http/1.1而导致无法连接的问题 2021-12-06 00:19:03 +08:00
645c3154d6 [Fix] 修正因xray服务端alpn参数为http/1.1而导致无法连接的问题 2021-12-05 03:51:26 +08:00
a847d7b58d [Fix] 修正因xray服务端alpn参数为http/1.1而导致无法连接的问题 2021-12-05 02:18:58 +08:00
37ea8aff5c README 2021-12-05 00:48:35 +08:00
cb4ce8be6a Makefile 2021-12-04 21:43:33 +08:00
a85395e777 readme 2021-12-04 20:50:57 +08:00
819b29956b readme 2021-12-04 20:40:09 +08:00
eb999b3bf1 fix AutoIptables 2021-12-04 19:59:41 +08:00
8580ee8898 [style] 2021-12-04 17:41:13 +08:00
58552447ef [fix]Linux TProxy 2021-12-04 14:34:01 +08:00
23ca356447 Fixed: Modify the trigger condition, only if it fails successively 2021-12-04 00:16:39 +08:00
fae65b97ec fix Makefile 2021-12-03 22:13:05 +08:00
99f0231a9b style 2021-12-03 21:54:45 +08:00
edf1bb476d test 2021-12-03 20:38:40 +08:00
5c53243e81 Experimental: Positive health testing 2021-12-03 14:35:21 +08:00
b99b4ad15f Fixed:Rule-Set Supported RuleExtra 2021-12-02 23:32:30 +08:00
6369921364 Merge pull request #4 from Skyxim/meta
Feature:Supported Rule-Set
2021-12-02 23:17:02 +08:00
c6f923041f Feature:Supported Rule-Set 2021-12-02 22:56:17 +08:00
08607fb6b4 Feature: add linux/arm/v6 for the container image (#1771) 2021-12-02 21:12:45 +08:00
53eb3f15bb Revert "[fix]code"
This reverts commit 0431969a73.
2021-12-02 20:08:34 +08:00
b15a7c8b6f Revert "[test]"
This reverts commit bf6bfdd930.
2021-12-02 20:08:28 +08:00
038f973f90 Merge remote-tracking branch 'origin/Meta' into Meta
# Conflicts:
#	tunnel/tunnel.go
2021-12-02 18:06:47 +08:00
bf6bfdd930 [test] 2021-12-02 18:06:14 +08:00
0431969a73 [fix]code 2021-12-02 03:39:37 +08:00
c7b257b188 [style] 2021-12-01 19:25:32 +08:00
885f69b81d [style] 2021-12-01 17:08:44 +08:00
cb52682790 [style] 2021-12-01 16:51:31 +08:00
c65835d9e4 [style] embed_wintun.dll 2021-11-30 18:00:19 +08:00
92bb026f70 [style] embed_wintun.dll 2021-11-30 17:58:21 +08:00
c22c7efd07 [fix] embed_windows 2021-11-27 22:10:37 +08:00
e4b30dacd4 [fix] embed_windows 2021-11-27 21:51:38 +08:00
353ae30839 [test] embed_windows 2021-11-27 21:36:10 +08:00
828ff82ff2 [test] embed_windows 2021-11-27 21:23:34 +08:00
35cf39e415 Revert "[test] rule providers"
This reverts commit 078389f4f6.
2021-11-26 00:57:41 +08:00
340efef2d8 Revert "[test] rule providers"
This reverts commit 14af94205c.
2021-11-26 00:57:36 +08:00
796eb5c95c Revert "[test] rule providers"
This reverts commit d4cc650633.
2021-11-26 00:57:33 +08:00
0f2b87497b Revert "[fix]code"
This reverts commit 06e9243fda.
2021-11-26 00:57:29 +08:00
06e9243fda [fix]code 2021-11-26 00:27:00 +08:00
d4cc650633 [test] rule providers 2021-11-25 23:33:06 +08:00
14af94205c [test] rule providers 2021-11-25 23:20:08 +08:00
078389f4f6 [test] rule providers 2021-11-25 23:14:31 +08:00
cad18b7529 [fix] rule providers 2021-11-25 21:52:07 +08:00
075d8ed094 Fix: fakeip pool cycle used 2021-11-23 22:01:49 +08:00
b1bed7623d Fix: provider filter potential panic 2021-11-21 17:44:03 +08:00
aeddc8eb1d fix proxies callback 2021-11-21 16:57:22 +08:00
f7393509a3 fix python310 2021-11-21 15:09:22 +08:00
1401a82bb0 Feature: add filter on proxy provider (#1511) 2021-11-20 23:38:49 +08:00
8e641a4e31 Fix: should return io.EOF immediately 2021-11-20 23:01:22 +08:00
4524cf4418 Fix: should return io.EOF immediately 2021-11-20 12:44:31 +08:00
0db15d46c3 Change: use nop packet conn for reject 2021-11-20 12:34:14 +08:00
223de1f3fd [update]version 2021-11-18 23:54:20 +08:00
1fb2bc07d7 [update]readme 2021-11-17 19:55:14 +08:00
eb57d246cf [test]tun 2021-11-17 19:35:34 +08:00
0001a1b844 [Fix]Vless tls must not be true 2021-11-17 19:09:01 +08:00
b20e202321 [Fix]Vless tls must not be true 2021-11-17 17:56:24 +08:00
900e852525 [test] 2021-11-17 16:03:47 +08:00
1f3968bd50 [test]core 1.8 2021-11-17 15:00:32 +08:00
5d510eb5aa [test]core 1.8 2021-11-16 20:08:52 +08:00
3d246d5150 Merge from remote branch 2021-11-14 20:25:22 +08:00
08c43b8876 Fix: revert ssr udp fix 2021-11-14 14:48:00 +08:00
3686446919 Fix: resolver dial context options 2021-11-12 11:05:02 +08:00
a412745314 Merge from remote branch 2021-11-11 00:54:43 +08:00
d0c23998d2 Fix: resolver dial context udp 2021-11-11 00:53:42 +08:00
499beb7344 Fix: bind iface should throw control error 2021-11-10 22:19:11 +08:00
038cc1f6b5 Merge from remote branch 2021-11-09 21:12:08 +08:00
6bd186d3c0 Merge from remote branch 2021-11-09 21:11:38 +08:00
4c6bb7178b Feature: resolve ip with proxy adapter 2021-11-09 19:44:16 +08:00
cec14db4a8 Merge pull request #1 from Dreamacro/master
更新
2021-11-09 16:14:04 +08:00
c9be614821 Fix: windows arm7 build 2021-11-08 21:24:39 +08:00
b56d35040d Chore: update dependencies and rename profile props 2021-11-08 20:48:29 +08:00
bd2ea2b917 Feature: mark on socket (#1705) 2021-11-08 16:59:48 +08:00
e622d8dd38 Fix: parse dial interface option 2021-11-08 13:31:08 +08:00
d40e5e4fe6 Fix: codeql alerts 2021-11-08 00:32:21 +08:00
1a7830f18e Feature: dial different NIC for all proxies (#1714) 2021-11-07 16:48:51 +08:00
53287d597b Chore: use custom buffer pool for lwIP stack 2021-11-04 18:33:11 +08:00
964bbe1957 Chore: adjust all udp alloc size
Chore: adjust all udp alloc size
2021-11-04 00:44:16 +08:00
bcb301b730 Chore: adjust all udp alloc size 2021-11-03 22:29:24 +08:00
c824ace2d7 Wintun: use new swdevice-based API for upcoming Wintun 0.14 2021-11-03 15:10:31 +08:00
ac9e5c6913 Wintun: use new swdevice-based API for upcoming Wintun 0.14 2021-11-03 15:02:40 +08:00
b515a4e270 Chore: move "geodata" to package "component" 2021-11-02 18:23:01 +08:00
78cef7df59 Chore: move "geodata" to package "component" 2021-10-29 00:52:44 +08:00
62b3ebe49f Chore: update dependencies 2021-10-28 13:35:27 +08:00
325b7f455f Chore: version fmt 2021-10-28 12:55:40 +08:00
ff420ed2ee Merge from remote branch 2021-10-28 12:30:30 +08:00
d1568325e6 Merge from remote branch 2021-10-28 12:30:02 +08:00
ddf28dfe8b Merge from remote branch 2021-10-28 11:36:41 +08:00
2680e8ffa3 Merge from remote branch 2021-10-28 11:36:11 +08:00
2953772a0e Style: format code 2021-10-28 00:06:55 +08:00
5a27df899f Chore: script built 2021-10-27 23:10:11 +08:00
ebbc9604ce Chore: use uber max procs 2021-10-27 21:27:19 +08:00
ab12b440aa Merge remote branch 2021-10-21 22:40:07 +08:00
4b614090f8 Merge remote branch 2021-10-21 22:37:30 +08:00
63d07db4bf Chore: script built 2021-10-21 20:22:23 +08:00
a7aea12aa6 Fix: remove ResponseHeaderTimeout limitation (#1690) 2021-10-20 13:44:05 +08:00
c6cceeb0c5 Chore: use alpn http 1.1 only on trojan websocket by default 2021-10-19 22:34:18 +08:00
967932d02c Fix: set dnsmode behavior 2021-10-18 23:03:25 +08:00
81d5da51a3 Fix: unexpected proxy dial behavior on mapping mode 2021-10-18 21:08:27 +08:00
fea9d1c5e2 Fix: replace vmess grpc test image 2021-10-16 20:35:06 +08:00
df3a491d40 Feature: support trojan websocket 2021-10-16 20:19:59 +08:00
68753b4ae1 Chore: contexify ProxyAdapter ListenPacket 2021-10-15 21:44:53 +08:00
cbea46b0c8 Merge remote branch 2021-10-15 14:14:51 +08:00
c0e9d69163 Feature: add mode script 2021-10-15 14:11:14 +08:00
583b2a5ace Change: use interface HardwareAddr for dhcp discovery 2021-10-14 22:54:43 +08:00
13bd601cac Fix: #1660 panic 2021-10-11 21:05:38 +08:00
3d5681cffd Feature: persistence fakeip (#1662) 2021-10-11 20:48:58 +08:00
a1c2478e74 Chore: actions split lint and release 2021-10-11 20:08:18 +08:00
f1cf7e9269 Style: use gofumpt for fmt 2021-10-10 23:44:09 +08:00
4ce35870fe Chore: remove deprecated ioutil 2021-10-09 20:35:06 +08:00
1996bef9e6 Chore: doh request should with id 0 (#1660) 2021-10-07 22:57:55 +08:00
66cb0b1218 Fix: cache kv db should not block on init 2021-10-05 22:47:26 +08:00
b9d470cf79 Fix: dhcp client should request special interface 2021-10-05 13:31:19 +08:00
4f1fac02ab Chore: add remove TODO 2021-10-05 12:42:21 +08:00
537b672fcf Change: use bbolt as cache db 2021-10-04 19:20:11 +08:00
ced9749104 Fix: http proxy should response correct http version (#1651) 2021-09-30 16:30:07 +08:00
d29d824da8 Improve: avoid bufconn twice (#1650) 2021-09-30 04:11:37 +08:00
862174d21b Feature: add lwIP TCP/IP stack to tun listener 2021-09-30 04:05:52 +08:00
9aeb4c8cfe Improve: avoid bufconn twice (#1650) 2021-09-28 23:15:53 +08:00
433d35e866 Chore: format with go 1.17 2021-09-24 04:37:04 +08:00
32d8f849ee Chore: update gvisor 2021-09-23 02:42:17 +08:00
8be1d5effb Merge from remote branch 2021-09-22 22:11:51 +08:00
70c8605cca Improve: use one bytes.Buffer pool 2021-09-20 21:02:18 +08:00
5b1a0a523f Chore: update README.md 2021-09-20 17:22:40 +08:00
5f03238c8a Chore: force set latest go version to action 2021-09-18 18:22:28 +08:00
b398f1e6f3 Chore: force set latest go version to action 2021-09-18 00:18:47 +08:00
6f94d56383 Fix: gvisor ipv6 routeing in Tun 2021-09-17 16:49:53 +08:00
fbda82218e Merge from remote branch 2021-09-17 15:07:27 +08:00
b3cd4ebbd3 Fix: use 1.17.x on github actions 2021-09-15 20:21:30 +08:00
b0f83e401f Fix: socks4 request continues after authentication failed (#1624) 2021-09-15 16:45:57 +08:00
f5806d9263 Fix: http/https proxy authentication (#1613) 2021-09-14 00:08:23 +08:00
55600c49c9 Fix: potential pitfalls 2021-09-13 23:58:48 +08:00
beb88cc46f Fix: should not trust address of http.Client (#1616) 2021-09-13 23:46:39 +08:00
d49b38b00f Fix: should not unmarshal to pointer (#1615) 2021-09-13 23:43:28 +08:00
85dc0b5527 Fix: potential overflow in ssr (#1600) 2021-09-09 22:07:27 +08:00
0c79d1207e Fix: potential overflow in ssr (#1600) 2021-09-09 20:30:34 +08:00
77a6a08192 Fix: VLESS test cases 2021-09-08 23:34:57 +08:00
1df5317e13 Feature: add VLESS test case [ssh] 2021-09-08 22:36:54 +08:00
ae619e4163 Fix: VLESS WSOpts 2021-09-08 21:32:08 +08:00
738bd3b0dd Fix: vmess ws headers not set properly (#1595) 2021-09-08 21:20:16 +08:00
400dc923e0 Fix: vmess ws headers not set properly (#1595) 2021-09-08 14:44:24 +08:00
03be2512ca Merge from remote branch 2021-09-08 04:43:53 +08:00
6ddd9e6fb8 Merge from remote branch 2021-09-08 04:42:56 +08:00
9254d2411e Fix: VLESS WSOpts Headers 2021-09-08 04:34:11 +08:00
5b7f0de48b Chore: update dependencies 2021-09-07 20:16:07 +08:00
a5b950a779 Feature: add dhcp type dns client (#1509) 2021-09-06 23:07:34 +08:00
a2d59d6ef5 Feature: skip DIRECT proxies in relay (#1583) 2021-09-06 21:39:28 +08:00
8ef5cdb8be Test: use local clash pkg 2021-09-05 14:38:07 +08:00
c7b718f651 Test: release resources correctly 2021-09-05 14:25:55 +08:00
ff56e5c5de Test: fix direct listen fail 2021-09-04 22:44:18 +08:00
661c417fce Test: use shadowsocks-rust for ss benchmark 2021-09-04 22:31:08 +08:00
b904ca0bcc Feature: add source ipcidr condition to rule final 2021-09-01 18:29:48 +08:00
fb836fe441 Fix: remove trim source ipcidr 2021-09-01 01:02:42 +08:00
b23bc77001 Fix: source ipcidr condition for rule IPCIDR 2021-09-01 00:53:13 +08:00
16fcee802b Merge from remote branch 2021-09-01 00:41:32 +08:00
48aef1829f Merge from remote branch 2021-09-01 00:38:43 +08:00
4cc16e0136 Feature: add source ipcidr condition for all rules 2021-08-31 21:46:04 +08:00
7d20097465 Fix: ssr auth aes128 udp hmac verify 2021-08-30 00:15:57 +08:00
a20b9a3960 Chore: make geoip match case-insensitive (#1574) 2021-08-29 22:19:22 +08:00
e0d3f926b7 Feature: add geoip-code option 2021-08-25 15:15:13 +08:00
121bc910f6 Chore: adjust vmess 0rtt code and split xray test 2021-08-22 16:16:45 +08:00
4522cdc551 Feature: support xray's ws-0rtt path (#1558) 2021-08-22 16:03:46 +08:00
410772e81c Test: add vmess ws 0-rtt test 2021-08-22 01:17:29 +08:00
0267b2efad Feature: add vmess WebSocket early data (#1505)
Co-authored-by: ShinyGwyn <79344143+ShinyGwyn@users.noreply.github.com>
2021-08-22 00:25:29 +08:00
c6d375eda2 Fix: HTTP proxy internal linkage signature (#1555) 2021-08-20 23:38:47 +08:00
847f41952e Fix: grpc transport path should not escape 2021-08-19 22:11:56 +08:00
47044ec0d8 Fix: dependabot alerts 2021-08-18 20:20:00 +08:00
426ca36118 Chore: upgrade test package 2021-08-18 13:31:34 +08:00
571d2a0075 Migration: go 1.17 2021-08-18 13:26:23 +08:00
1be09f5751 Chore: fix issue template render type 2021-08-13 22:44:22 +08:00
2663cb2e6e Chore: update github issue template 2021-08-13 22:35:48 +08:00
9b0bbb90ff Chore: upgrade github actions 2021-08-07 22:27:23 +08:00
83c9664c17 Merge from remote branch 2021-08-05 00:49:17 +08:00
588645a2c3 Fix: interface nil check panic from previous commit 2021-08-04 23:52:50 +08:00
1bfebd0d03 Fix: listener patch diff 2021-08-01 00:35:37 +08:00
ac9e90c812 Merge from remote branch 2021-07-28 22:14:20 +08:00
ba2fd00f01 Merge from remote branch 2021-07-28 22:13:21 +08:00
3705996974 Chore: split SOCKS version inbound metadata type (#1513) 2021-07-27 13:58:29 +08:00
09299e5e5a Fix: error var name 2021-07-27 02:38:41 +08:00
09697b7679 Chore: adjust batch 2021-07-23 00:30:23 +08:00
4578b2c826 Fix: remove Content-Length from CONNECT response (#1502) 2021-07-22 18:06:03 +08:00
b3a293ab07 Chore: benchmark explanation 2021-07-22 00:01:11 +08:00
507ba16065 Fix: incorrect use batch 2021-07-21 23:53:31 +08:00
aa9f8a39a3 Fix: socks inbound packet typo 2021-07-21 23:08:52 +08:00
8d37220566 Fix: limit concurrency number of provider health check 2021-07-21 17:01:15 +08:00
53e17a916b Chore: logging remote port on request (#1494) 2021-07-19 15:31:38 +08:00
247dd84970 Chore: logging real listen port (#1492) 2021-07-19 14:07:51 +08:00
c2f3111922 Test: add direct benchmark 2021-07-18 19:26:51 +08:00
44872300e9 Test: ss use aes-256-gcm and vmess-aead for benchmark 2021-07-18 17:37:23 +08:00
91ed0118f6 Test: add basic protocol benchmark 2021-07-18 17:23:30 +08:00
a461c2306a Feature: SOCKS4/SOCKS4A Inbound Compatible Support (#1491) 2021-07-18 16:09:09 +08:00
46f4f84442 Chore: use iife replace init in some cases 2021-07-11 19:43:25 +08:00
250a9f4f84 Fix: reorder apply config to ensure update proxies and rules 2021-07-10 17:01:40 +08:00
96e5a52651 Style: code style 2021-07-09 02:19:43 +08:00
5852245045 Merge from remote branch 2021-07-07 03:53:32 +08:00
b4d93c4438 Feature: add xtls support for VLESS 2021-07-06 23:55:34 +08:00
56dff65149 Feature: support multiport condition for rule SRC-PORT and DST-PORT 2021-07-06 15:07:05 +08:00
b4292d0972 Fix: staticcheck error 2021-07-06 00:33:13 +08:00
d755383e39 Chore: move provider interface to constant 2021-07-06 00:31:13 +08:00
e2c7b19000 Fix: fix yaml syntax 2021-07-03 22:41:31 +08:00
8a488bab72 Merge from remote branch 2021-07-03 22:33:18 +08:00
3afe3810bf Merge from remote branch 2021-07-03 22:31:12 +08:00
dff1e8f1ce Chore: update dependencies 2021-07-03 21:01:41 +08:00
995aa7a8fc Fix: remove ClientSessionCache and add NextProtos for vmess to fix #1468 2021-07-03 20:34:44 +08:00
d7732f6ebc Code: refresh code 2021-07-01 22:49:29 +08:00
3ca5d17c40 Fix: enable DNS server message compression (#1451) 2021-06-24 13:38:44 +08:00
244cb370a4 Change: config reload API use default path when both path and payload don't exist (#1447) 2021-06-21 17:33:34 +08:00
c35cb24bda Chore: use unix.ByteSliceToString transform cstring 2021-06-15 21:03:47 +08:00
b6ff08074c Refactor: plain http proxy (#1443) 2021-06-15 17:13:40 +08:00
70d53fd45a Chore: update development wiki to README.md 2021-06-13 23:11:49 +08:00
f231a63e93 Chore: Listener should not expose original net.Listener 2021-06-13 23:05:22 +08:00
6091fcdfec Style: code style 2021-06-13 17:23:10 +08:00
bcfc15e398 chore: expose udp field to proxies API (#1441) 2021-06-10 15:08:33 +08:00
045edc188c Style: code style 2021-06-10 14:05:56 +08:00
0778591524 Feature: dns resolve domain through nameserver-policy (#1406) 2021-05-19 11:17:35 +08:00
d5e52bed43 Feature: add protocol test 2021-05-17 20:33:00 +08:00
06fdd3abe0 Fix: vmess http should use Host header on request 2021-05-16 20:05:41 +08:00
4e5898197a Fix: build broken 2021-05-13 22:39:33 +08:00
f96ebab99f Chore: split component to transport 2021-05-13 22:19:34 +08:00
3c54f99fea Chore: update dependencies 2021-05-08 19:29:12 +08:00
824f5bd731 Fix: reuse http connection broken on previous commit 2021-05-07 11:08:46 +08:00
3f3db8476e Fix: HTTP inbound leak 2021-05-06 22:34:37 +08:00
f375f080da Fix: skip deleted node from url-test group (#1378)
Co-authored-by: fish <fish@youme.im>
2021-05-01 17:21:09 +08:00
e19e9ef5a4 Style: code style 2021-04-29 11:23:14 +08:00
682e65cb54 Style: code style 2021-04-26 20:42:17 +08:00
16a6d409d9 Feature: add freebsd arm64 to Makefile (#1370) 2021-04-22 16:38:13 +08:00
4186bcf1b2 Fix: should write file if provider initialize from HTTP (#1365) 2021-04-19 17:40:38 +08:00
df5112175f Fix: io timeout when snell v2 reuse connection (#1362) 2021-04-19 14:36:06 +08:00
d9341a49ea Fix: trojan should safe close connection 2021-04-19 12:20:37 +08:00
4e9e4b6cde Fix: grpc transport concurrent write 2021-04-14 21:46:05 +08:00
936b7012ba Feature: PROCESS-NAME support freebsd 13, fix panic on unsupported platforms (#1351) 2021-04-14 17:57:17 +08:00
a9cbd9ec98 Fix: use bufio.Reader on grpc to avoid panic 2021-04-14 00:16:59 +08:00
c9943fb857 Fix: grpc implementation SetDeadline for udp issue 2021-04-13 23:34:33 +08:00
a40274e2a2 Fix: vmess aead writer concurrent write (#1350) 2021-04-13 23:32:53 +08:00
b59d45c660 Feature: add CodeQL security checks (#1349) 2021-04-13 21:25:55 +08:00
7b01e103c2 Chore: use correctly vmess http2 default host 2021-04-10 12:10:10 +08:00
93a8acecce Fix: vmess h2 use server as host if host option is empty 2021-04-09 18:15:46 +08:00
586bb91c0c Fix: grpc transport panic 2021-04-09 18:11:07 +08:00
baf03b81e3 Fix: remove unused function 2021-04-08 22:27:41 +08:00
9807e1189c Chore: update dependencies 2021-04-08 22:15:30 +08:00
3d5a0d9f73 Fix: trojan/vmess grpc broken 2021-04-07 22:57:46 +08:00
cc96187f58 Fix: trojan grpc udp broken 2021-04-05 23:26:13 +08:00
3aefa1d924 Chore: some chores 2021-04-05 13:31:10 +08:00
42e21b3733 Chore: refine go import 2021-04-05 13:00:49 +08:00
0a35237915 Fix: should reset fast node when tolerance enable and not alive on url-test group 2021-04-04 17:40:25 +08:00
a1f3a5ea26 Chore: -v add golang version 2021-04-04 17:36:22 +08:00
e63f995258 Chore: update dependencies (#1331) 2021-04-03 14:59:03 +08:00
d0c829c578 Fix: domain dns should follow hosts config, close #1318 2021-04-01 21:20:44 +08:00
4ad9761b32 Fix: don't resolve AAAA record when ipv6 is false and use go dns resolver 2021-04-01 18:03:30 +08:00
1f593d37fb Chore: use mixed-port instead of port when initial config (#1319) 2021-04-01 15:35:33 +08:00
109bfcb0f9 Feature: add vmess aead header support 2021-03-30 17:34:16 +08:00
7ee49f5171 Fix: HTTP server should close when Connection is close 2021-03-30 16:33:49 +08:00
d759d16944 Style: cleanup code 2021-03-24 01:00:21 +08:00
807d53c1e7 Chore: Clarify the definition of StreamConn and DialContext 2021-03-22 23:26:20 +08:00
1355196b7c Fix: grpc connection panic 2021-03-18 23:19:00 +08:00
573316bcde Feature: add gRPC Transport for vmess/trojan (#1287)
Co-authored-by: eMeab <32988354+eMeab@users.noreply.github.com>
Co-authored-by: Dreamacro <8615343+Dreamacro@users.noreply.github.com>
2021-03-18 19:40:34 +08:00
784c28266c Fix: vmess http broken 2021-03-18 17:11:10 +08:00
5da1b2a8aa Fix: set metadata.AddrType if host is ip string after remove host (#1291) 2021-03-12 17:41:37 +08:00
0976d27cb1 Fix: github actions remove prerelease option 2021-03-10 21:22:22 +08:00
6c83ff3496 Chore: update dependencies 2021-03-10 21:13:23 +08:00
f7f97ef625 Fix: some HTTP proxy request broken 2021-03-10 16:23:55 +08:00
5acdd72a1d Fix: remove host if host is ip string 2021-03-10 12:49:30 +08:00
f53686103d Chore: reset udp timeout after sending each packet (#1260) 2021-02-26 10:40:55 +08:00
f63c9eb22f Chore: update staticcheck command on actions 2021-02-21 19:37:37 +08:00
a37243cf30 Fix: store cache correctly 2021-02-21 01:07:22 +08:00
b3c1b4a840 Chore: update dependencies 2021-02-19 20:35:10 +08:00
14bbf6eedc Feature: support store group selected node to cache (enable by default) 2021-02-18 23:41:50 +08:00
aa81193d5b Feature: add darwin arm64 to Makefile (Apple Silicon) (#1234) 2021-02-18 18:15:09 +08:00
9eb98e399d Improve: refactor ssr and fix #995 (#1189)
Co-authored-by: goomada <madao@DESKTOP-IOEBS0C.localdomain>
2021-02-15 14:32:03 +08:00
d48cfecf60 Chore: API support patch ipv6 config (#1217) 2021-02-05 16:43:42 +08:00
6036fb63ba Chore: avoid provider unnecessary write file operations (#1210) 2021-02-02 17:52:46 +08:00
cd48f69b1f Fix: wrap net.Conn to avoid using *net.TCPConn.(ReadFrom) (#1209) 2021-02-01 20:06:45 +08:00
fcc594ae26 Chore: use jsdelivr CDN for Country.mmdb (#1057) 2021-01-30 00:40:35 +08:00
f4de055aa1 Refactor: make inbound request contextual 2021-01-23 14:58:09 +08:00
35925cb3da Chore: standardized Dockerfile label (#1191)
Signed-off-by: Junjie Yuan <yuan@junjie.pro>
2021-01-20 16:08:24 +08:00
ff430df845 Fix: connectivity of ssr auth_chain_(ab) protocol (#1180) 2021-01-13 23:35:41 +08:00
e4cdea2111 chore: use singleDo to get interface info 2021-01-13 17:30:54 +08:00
b6ee47a541 Fix: get general should return correct result (#1172) 2021-01-07 13:59:39 +08:00
b25009cde7 Fix: unnecessary write operation on provider (#1170) 2021-01-06 14:20:15 +08:00
6fedd7ec84 Fix: dns client should not bind local address 2021-01-04 00:51:53 +08:00
9619c3fb20 Fix: support unspecified UDP bind address (#1159) 2020-12-31 18:58:03 +08:00
02d029dd2d Fix: close http Response body on provider (#1154) 2020-12-29 11:28:22 +08:00
09c28e0355 Fix: fallback bind fn should not bind global unicast 2020-12-28 22:24:58 +08:00
3600077f3b Chore: update dependencies 2020-12-27 18:59:59 +08:00
de7656a787 Chore: update premium README 2020-12-27 00:14:24 +08:00
5dfe7f8561 Fix: handle keep alive on http connect proxy 2020-12-24 14:55:11 +08:00
ed27898a33 Fix: snell should support the config without obfs 2020-12-24 13:47:56 +08:00
532396d25c Fix: PROCESS-NAME rule for UDP sessions on Windows (#1140) 2020-12-22 15:13:44 +08:00
4b1b494164 Chore: move find process name to a single part 2020-12-17 22:17:27 +08:00
0d33dc3eb9 Chore: health checks return immediately if completed (#1097) 2020-11-24 22:52:23 +08:00
994cbff215 Fix: should not log rule when rule = nil 2020-11-22 23:38:12 +08:00
bea2ee8bf2 Chore: log rule msg on dial error 2020-11-22 19:12:36 +08:00
1e5593f1a9 Chore: update dependencies 2020-11-20 20:36:20 +08:00
34febc4579 Chore: more detailed error when dial failed 2020-11-20 00:27:37 +08:00
97581148b5 Fix: static check 2020-11-19 00:56:36 +08:00
0402878daa Feature: add lazy for proxy group and provider 2020-11-19 00:53:22 +08:00
4735f61fd1 Feature: add disable-udp option for all proxy group 2020-11-13 21:48:52 +08:00
16ae107e70 Chore: push image to github docker registry 2020-11-10 15:19:12 +08:00
83efe2ae57 Feature: add TCP TPROXY support (#1049) 2020-11-09 10:46:10 +08:00
87e4d94290 Fix: tunnel manager & tracker race condition (#1048) 2020-10-29 17:51:14 +08:00
b98e9ea202 Improve: #1038 and #1041 2020-10-29 00:32:31 +08:00
9a62b1081d Feature: support round-robin strategy for load-balance group (#1044) 2020-10-28 22:35:02 +08:00
2cd1b890ce Fix: tunnel UDP race condition (#1043) 2020-10-28 21:26:50 +08:00
ba060bd0ee Fix: should not bind interface on local address 2020-10-25 20:31:01 +08:00
b1795b1e3d Fix: stale typo 2020-10-25 11:53:03 +08:00
76c9820065 Fix: undefined variable 2020-10-23 17:49:34 +08:00
2db4ce57ef Chore: make stale time into 60 days 2020-10-23 00:30:17 +08:00
50b3d497f6 Feature: use native syscall to bind interface on Linux and macOS 2020-10-22 22:32:03 +08:00
2321e9139d Chore: deprecated eapache/channels 2020-10-20 17:44:39 +08:00
baabf21340 Chore: update github workflow 2020-10-17 13:46:05 +08:00
d3bb4c65a8 Fix: missing fake-ip record should return error 2020-10-17 12:52:43 +08:00
8c3e2a7559 Chore: fix typo (#1017) 2020-10-14 19:56:02 +08:00
bc52f8e4fd Chore: return empty record in SVCB/HTTPSSVC on fake-ip mode 2020-10-13 00:15:49 +08:00
d3b14c325f Fix: the priority of fake-ip-filter 2020-10-09 00:04:24 +08:00
4859b158b4 Chore: make builds reproducible (#1006) 2020-10-08 17:54:38 +08:00
d65b51c62b Feature: http support custom sni 2020-10-02 11:34:40 +08:00
a6444bb449 Feature: support domain in fallback filter (#964) 2020-09-28 22:17:10 +08:00
e09931dcf7 Chore: remove broken test temporarily 2020-09-26 20:36:52 +08:00
5bd189f2d0 Feature: support VMess HTTP/2 transport (#903) 2020-09-26 20:33:57 +08:00
8766287e72 Chore: sync necessary changes from premium 2020-09-21 22:22:07 +08:00
10f9571c9e Fix: pool gc test 2020-09-21 00:44:47 +08:00
96a8259c42 Feature: support snell v2 (#952)
Co-authored-by: Dreamacro <8615343+Dreamacro@users.noreply.github.com>
2020-09-21 00:33:13 +08:00
68dd0622b8 Chore: code style 2020-09-20 15:53:27 +08:00
558ac6b965 Chore: split enhanced mode instance (#936)
Co-authored-by: Dreamacro <305009791@qq.com>
2020-09-17 10:48:42 +08:00
e773f95f21 Fix: PROCESS-NAME on FreeBSD 11.x (#947) 2020-09-07 17:43:34 +08:00
314ce1c249 Feature: vmess network http support TLS (https) 2020-09-04 21:27:19 +08:00
13275b1aa6 Chore: use only one goroutine to handle statistic (#940) 2020-09-03 10:30:18 +08:00
02d9169b5d Fix: potential PCB buffer overflow on bsd systems (#941) 2020-09-03 10:27:20 +08:00
7631bcc99e Improve: use atomic for connection statistic (#938) 2020-09-02 16:34:12 +08:00
a32ee13fc9 Feature: reuse dns resolver cache when hot reload 2020-08-31 00:32:18 +08:00
b8ed738238 Chore: update actions version 2020-08-30 23:06:21 +08:00
687c2a21cf Fix: vmess UDP option should be effect 2020-08-30 22:49:55 +08:00
ad18064e6b Chore: code style (#933) 2020-08-30 19:53:00 +08:00
c9735ef75b Fix: static check 2020-08-25 22:36:38 +08:00
b70882f01a Chore: add static check 2020-08-25 22:32:23 +08:00
5805334ccd Chore: pass staticcheck 2020-08-25 22:19:59 +08:00
c1b4382fe8 Feature: add Windows ARM32 build (#902)
Co-authored-by: MarksonHon <50002150+MarksonHon@users.noreply.github.com>
2020-08-16 13:50:56 +08:00
008743f20b Chore: update dependencies 2020-08-16 11:32:51 +08:00
50d778da3c Chore: cache process name when resolve failed (#900) 2020-08-15 16:55:55 +08:00
8b7c731fd6 Fix: ssr broken (#895) 2020-08-12 20:50:56 +08:00
0b7918de9c Migration: go 1.15 2020-08-12 13:47:50 +08:00
4f61c04519 Fix: ssr typo (#887) 2020-08-11 10:35:30 +08:00
89cf06036d Feature: dns server could lookup hosts (#872) 2020-08-11 10:28:17 +08:00
4ba6f248bc Fix: ssr bounds out of range panic (#882) 2020-08-11 10:17:40 +08:00
83a684c551 Change: adjust tolerance logic (#864) 2020-08-06 20:12:03 +08:00
92a23f1eab Feature: PROCESS-NAME for windows (#840) 2020-08-06 19:59:20 +08:00
622ac45258 Feature: PROCESS-NAME for freebsd (#855) 2020-07-31 20:01:19 +08:00
791d203b5f Fix: update cache if a process was found (#850) 2020-07-30 17:15:06 +08:00
77d6f9ae6f Fix: handle snell server reported error message properly (#848) 2020-07-30 15:54:26 +08:00
b1d9dfd6bf Improve: simplify macOS process searching 2020-07-29 11:27:18 +08:00
6532947e71 Fix: invert should resolve ip (#836) 2020-07-27 13:47:00 +08:00
6c5f23f552 Merge branch 'dev' of github.com:Dreamacro/clash into dev 2020-07-27 11:58:02 +08:00
78c3034158 Chore: rename NoResolveIP to ShouldResolveIP 2020-07-27 11:57:55 +08:00
8f0098092d Fix: protect alive with atomic value (#834) 2020-07-25 17:47:11 +08:00
33a6579a3a Feature: add ssr support (#805)
* Refactor ssr stream cipher to expose iv and key

References:
https://github.com/Dreamacro/go-shadowsocks2
https://github.com/sh4d0wfiend/go-shadowsocksr2

* Implement ssr obfs

Reference:
https://github.com/mzz2017/shadowsocksR

* Implement ssr protocol

References:
https://github.com/mzz2017/shadowsocksR
https://github.com/shadowsocksRb/shadowsocksr-libev
https://github.com/shadowsocksr-backup/shadowsocksr
2020-07-22 23:02:15 +08:00
b4221d4b74 Chore: README.md style fixed (#825)
make every item in TODO list has the same style
2020-07-22 21:34:37 +08:00
0e4b9daaad Improve: add cache for macOS PROCESS-NAME 2020-07-22 20:35:27 +08:00
ee72865f48 Fix: recycle buf on http obfs 2020-07-22 20:29:39 +08:00
6521acf8f1 Improve: check uid on process search & fix typo (#824) 2020-07-22 20:22:34 +08:00
4f73410618 Feature: add PROCESS-NAME rule for linux (#822) 2020-07-22 19:05:10 +08:00
20eff200b1 Fix: dns should put msg to cache while exchangeWithoutCache (#820) 2020-07-20 21:16:36 +08:00
ae1e1dc9f6 Feature: support PROCESS-NAME on macOS 2020-07-19 13:18:23 +08:00
cf9e1545a4 Improve: fix go test race detect 2020-07-18 20:56:13 +08:00
6c7a8fffe0 Chore: should not write file on file provider 2020-07-18 19:32:40 +08:00
3a3e2c05af Chore: add rule payload in log 2020-07-18 19:22:09 +08:00
02c7fd8d70 Fix: write msg cache multiple times (#812)
Co-authored-by: john.xu <john.xu@bytedance.com>
2020-07-17 17:34:40 +08:00
e6aa452b51 Fix: ticker leak 2020-07-13 00:25:54 +08:00
35449bfa17 Feature: add github stale action 2020-07-09 10:27:05 +08:00
acd51bbc90 Fix: obfs host should not have 80 port 2020-07-01 00:01:36 +08:00
f44cd9180c Chore: update GitHub issue template 2020-06-30 13:55:26 +08:00
93c987a6cb Fix: typo in dialer.go (#767) 2020-06-28 10:59:04 +08:00
3f0584ac09 Chore: move documentations to wiki (#766) 2020-06-28 10:39:30 +08:00
59968fff1c Fix: github actions tag build 2020-06-27 21:09:04 +08:00
7c62fe41b4 Chore: remove forward compatibility code 2020-06-27 14:28:10 +08:00
2781090405 Chore: move experimental features to stable 2020-06-27 14:19:31 +08:00
14c9cf1b97 Fix: domain trie crash if not match in #758 (#762) 2020-06-24 19:46:37 +08:00
3dfff84cc3 Fix: domain trie should backtrack to parent if match fail (#758) 2020-06-24 18:41:23 +08:00
5f3db72422 Fix: docker multiplatform build 2020-06-21 12:38:14 +08:00
18bb285a90 Fix: external-ui should relative with clash HomeDir 2020-06-18 21:33:57 +08:00
60bad66bc3 Change: ipv6 logic 2020-06-18 18:11:02 +08:00
99b34e8d8b Fix: cannot listen socks5 port on wsl (#748) 2020-06-15 10:34:15 +08:00
9f1d85ab6e Fix: fake-ip-filter on fakeip mode should lookup ip-host mapping (#743) 2020-06-14 00:41:53 +08:00
4323dd24d0 Fix: don't auto health check on provider health check disabled 2020-06-14 00:32:04 +08:00
59bda1d547 Change: local resolve DNS in UDP request due to TURN failed 2020-06-12 23:39:03 +08:00
1c760935f4 Chore: add error msg when dial vmess 2020-06-11 22:19:47 +08:00
4f674755ce Fix: trim . for socks5 host 2020-06-11 12:11:44 +08:00
f1b792bd26 Fix: trim FQDN on http proxy request 2020-06-11 11:10:08 +08:00
58c077b45e Fix: actions tag replace 2020-06-08 13:53:04 +08:00
1854199c47 Chore: update dependencies 2020-06-07 18:14:04 +08:00
ecac8eb8e5 Fix: add lock for inbound proxy recreate 2020-06-07 17:57:41 +08:00
48cff50a4c Feature: connections add rule payload 2020-06-07 17:28:56 +08:00
fb628e9c62 Feature: add default hosts localhost 2020-06-07 17:25:51 +08:00
2dece02df6 Chore: code adjustments 2020-06-07 16:54:41 +08:00
8f32e6a60f Improve: safe write provider file 2020-06-07 00:36:54 +08:00
98614a1f3f Chore: move rule parser to rules 2020-06-05 17:43:50 +08:00
c1b4c94b9c Chore: remove unused hooks directory 2020-06-05 12:49:24 +08:00
7ddbc12cdb Chore: rm unused Dockerfile 2020-06-04 10:57:43 +08:00
1a217e21e9 Chore: use actions build docker image 2020-06-04 10:38:30 +08:00
147a7ce779 Fix: panic of socks5 client missing authentication 2020-06-03 18:49:57 +08:00
fb0289bb4c Chore: open ForceAttemptHTTP2 on DoH 2020-06-01 13:43:26 +08:00
3e7970612a Chore: provider error adjust 2020-06-01 00:39:41 +08:00
46244a6496 Chore: mode use lower case (backward compatible) 2020-06-01 00:32:37 +08:00
71d30e6654 Feature: support vmess tls custom servername 2020-06-01 00:27:04 +08:00
008731c249 Fix: make os.Stat return correct err on provider 2020-05-29 21:56:29 +08:00
5628f97da1 Feature: add tolerance for url-test 2020-05-29 17:47:50 +08:00
8d0c6c6e66 Feature: domain trie support wildcard alias 2020-05-28 12:13:05 +08:00
5073c3cde8 Chore: add trimpath for go build 2020-05-20 15:13:33 +08:00
3a27cfc4a1 Feature: add Mixed(http+socks5) proxy listening (#685) 2020-05-12 11:29:53 +08:00
3638b077cd Chore: update premium link 2020-05-08 21:52:17 +08:00
646bd4eeb4 Chore: update dependencies and README.md 2020-05-07 21:58:53 +08:00
752f87a8dc Feature: support proxy-group in relay (#597) 2020-05-07 21:42:52 +08:00
b979ff0bc2 Feature: implemented a strategy similar to optimistic DNS (#647) 2020-05-07 15:10:14 +08:00
b085addbb0 Fix: use domain first on direct dial (#672) 2020-05-05 12:39:25 +08:00
94e0e4b000 Fix: make selector react immediately 2020-04-30 20:13:27 +08:00
7d51ab5846 Fix: dns return empty success for AAAA & recursion in fake ip mode (#663) 2020-04-29 11:21:37 +08:00
41a9488cfa Feature: add more command-line options (#656)
add command-line options to override `external-controller`, `secret` and `external-ui` (#531)
2020-04-27 22:23:09 +08:00
51b6b8521b Fix: typo (#657) 2020-04-27 22:20:35 +08:00
e5379558f6 Fix: redir-host should lookup hosts 2020-04-27 21:28:24 +08:00
d1fd57c432 Fix: select group can use provider real-time 2020-04-27 21:23:23 +08:00
18603c9a46 Improve: provider can be auto GC 2020-04-26 22:38:15 +08:00
5036f62a9c Chore: update dependencies 2020-04-25 00:43:32 +08:00
2047b8eda1 Chore: remove unused parameter netType (#651) 2020-04-25 00:39:30 +08:00
0e56c195bb Improve: pool buffer alloc 2020-04-25 00:30:40 +08:00
2b33bfae6b Fix: API auth bypass 2020-04-24 23:49:35 +08:00
3fc6d55003 Fix: domain wildcard behavior 2020-04-24 23:49:19 +08:00
8eddcd77bf Chore: dialer hook should return a error 2020-04-24 23:48:55 +08:00
27dd1d7944 Improve: add basic auth support for provider URL (#645) 2020-04-20 21:22:23 +08:00
b1cf2ec837 Fix: dns tcp-tls X509.HostnameError (#638) 2020-04-17 11:29:59 +08:00
84f627f302 Feature: verify mmdb on initial 2020-04-16 19:12:25 +08:00
5c03613858 Chore: picker support get first error 2020-04-16 18:31:40 +08:00
1825535abd Improve: recycle buffer after packet used 2020-04-16 18:19:36 +08:00
2750c7ead0 Fix: set SO_REUSEADDR for UDP listeners on linux (#630) 2020-04-11 21:45:56 +08:00
3ccd7def86 Fix: typo (#624) 2020-04-08 15:49:12 +08:00
65dab4e34f Feature: domain trie support dot dot wildcard 2020-04-08 15:45:59 +08:00
5591e15452 Fix: vmess pure TLS mode 2020-04-03 16:04:24 +08:00
19f809b1c8 Feature: refactor vmess & add http network 2020-03-31 16:07:21 +08:00
206767247e Fix: udp traffic track (#608) 2020-03-28 20:05:38 +08:00
518354e7eb Fix: dns request panic and close #527 2020-03-24 10:13:53 +08:00
86dfb6562c Chore: update dependencies 2020-03-22 17:41:58 +08:00
c0a2473160 Feature: support relay (proxy chains) (#539) 2020-03-21 23:46:49 +08:00
70a19b999d Chore: update README to better describe what Clash do atm (#586) 2020-03-20 12:35:30 +08:00
e54f51af81 Fix: trojan split udp packet 2020-03-20 00:02:05 +08:00
b068466108 Improve: add session cache for trojan 2020-03-19 22:39:09 +08:00
b562f28c1b Feature: support trojan 2020-03-19 20:26:53 +08:00
230e01f078 Fix: config parse panic 2020-03-19 11:04:56 +08:00
082847b403 Chore: support MarshalYAML to some config filed (#581) 2020-03-15 19:40:39 +08:00
9471d80785 Fix: dns fallback logic 2020-03-13 00:11:54 +08:00
b263095533 Fix: TPROXY fakeip (#572) 2020-03-10 20:36:24 +08:00
Ti
14d5137703 Fix: rules parse (#568) 2020-03-09 10:40:21 +08:00
d8a771916a Fix: provider parse 2020-03-08 23:34:46 +08:00
f7f30d3406 Feature: add UDP TPROXY support on Linux (#562) 2020-03-08 21:58:49 +08:00
b2c9cbb43e Chore: update dependencies 2020-03-08 13:01:06 +08:00
c733f80793 Fix: #563 and fallback error return 2020-03-08 13:00:42 +08:00
88d8f93793 Change: rename some field 2020-03-07 20:01:24 +08:00
e57a13ed7a Fix: mutable SplitAddr cause panic 2020-03-02 23:47:23 +08:00
23525ecc15 Migration: go 1.14 2020-03-01 01:48:08 +08:00
814bd05315 Fix: ss udp return error when addr parse failed 2020-03-01 01:46:02 +08:00
e81b88fb94 Feature: add configuration test command (#524) 2020-02-29 17:48:26 +08:00
c4994d6429 Fix: dns not cache RcodeServerFailure 2020-02-25 21:53:28 +08:00
0740d20ba0 Chore: disable url-test http redirect (#536) 2020-02-25 16:08:13 +08:00
9eaca6e4ab Fix: provider fallback should reparse proxies 2020-02-22 15:18:03 +08:00
609869bf5a Change: make ping under authentication 2020-02-21 18:00:19 +08:00
d68339cea7 Fix: socks5 inbound return remote udp addr for identity 2020-02-20 11:29:16 +08:00
0f4cdbf187 Chore: remove unused code 2020-02-18 16:05:12 +08:00
f3f8e7e52f Chore: remove println 2020-02-18 14:26:42 +08:00
8d07c1eb3e Chore: initial config with port 2020-02-18 13:48:15 +08:00
46edae9896 Fix: domain dns crash 2020-02-17 22:13:15 +08:00
df0ab6aa8e Fix: ipv6 dns crash 2020-02-17 20:11:46 +08:00
7b48138ad0 Fix: vmess udp crash 2020-02-17 17:34:19 +08:00
e9032c55fa Chore: update dependencies 2020-02-17 12:25:55 +08:00
d75cb069d9 Feature: add default-nameserver and outbound interface 2020-02-15 21:42:46 +08:00
f69f635e0b Chore: add issue templates 2020-02-14 19:16:43 +08:00
8b5e511426 Fix: use the fastest whether the result is successful 2020-02-14 16:36:20 +08:00
6641bf7c07 Fix: vmessUDPConn should return a correctly address 2020-02-12 13:12:07 +08:00
afc9f3f59a Chore: use custom dialer 2020-02-09 17:02:48 +08:00
a55be58c01 Fix: provider should fallback to read remote when local file invalid 2020-02-08 16:21:52 +08:00
dcf97ff5b4 Fix: should prehandle metadata before resolve 2020-02-07 20:53:43 +08:00
72c0af9739 Chore: udp resolve ip on local 2020-01-31 19:26:33 +08:00
b0f9c6afa8 Fix: should close socks udp PacketConn 2020-01-31 15:03:59 +08:00
19bb0b655c Fix: match log display 2020-01-31 14:58:54 +08:00
26ce3e8814 Improve: udp NAT type 2020-01-31 14:43:54 +08:00
aa207ec664 Fix: qemu permission 2020-01-30 21:19:51 +08:00
c626b988a6 Fix: add docker hub pre build 2020-01-30 17:25:55 +08:00
82c387e92b Chore: fix typo (#490) 2020-01-30 17:03:10 +08:00
14fb789002 Feature: add arm32 and arm64 docker image 2020-01-28 16:39:52 +08:00
9071351022 Chore: aggregate mmdb (#474) 2020-01-11 21:07:01 +08:00
f688eda2c2 Chore: fix typo (#479) 2020-01-11 21:02:55 +08:00
2810533df4 Chore: export raw config struct (#475) 2020-01-11 00:22:34 +08:00
6b7144acce Chore: export reset manager statistic api (#476) 2020-01-11 00:20:10 +08:00
e68c0d088b Fix: upstream dns ExchangeContext workaround (#468) 2020-01-10 14:13:44 +08:00
2c0cc374d3 Fix: README typo 2020-01-09 18:13:15 +08:00
86d3d77a7f Chore: increase DNS timeout (#464) 2020-01-01 19:23:34 +08:00
14a3ff32f6 Chore: update dependencies 2020-01-01 18:30:26 +08:00
50704eaeeb Fix: udp crash 2019-12-31 14:47:00 +08:00
38458cc4d0 Migration: change geoip address 2019-12-31 12:30:42 +08:00
b19a49335f Fix: http vehicle shouldn't save file 2019-12-30 23:01:24 +08:00
9dda932494 Fix: reject should support udp and return dial error 2019-12-30 10:51:35 +08:00
6ce7b6ef83 Chore: update TUN release 2019-12-28 22:42:30 +08:00
639 changed files with 70015 additions and 6999 deletions

82
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,82 @@
name: Bug report
description: Create a report to help us improve
title: "[Bug] "
body:
- type: checkboxes
id: ensure
attributes:
label: Verify steps
description: "
在提交之前,请确认
Please verify that you've followed these steps
"
options:
- label: "
确保你使用的是**本仓库**最新的的 clash 或 clash Alpha 版本
Ensure you are using the latest version of Clash or Clash Premium from **this repository**.
"
required: true
- label: "
如果你可以自己 debug 并解决的话,提交 PR 吧
Is this something you can **debug and fix**? Send a pull request! Bug fixes and documentation fixes are welcome.
"
required: false
- label: "
我已经在 [Issue Tracker](……/) 中找过我要提出的问题
I have searched on the [issue tracker](……/) for a related issue.
"
required: true
- label: "
我已经使用 Alpha 分支版本测试过,问题依旧存在
I have tested using the dev branch, and the issue still exists.
"
required: true
- label: "
我已经仔细看过 [Documentation](https://wiki.metacubex.one/) 并无法自行解决问题
I have read the [documentation](https://wiki.metacubex.one/) and was unable to solve the issue.
"
required: true
- label: "
这是 Clash 核心的问题,并非我所使用的 Clash 衍生版本(如 OpenClash、KoolClash 等)的特定问题
This is an issue of the Clash core *per se*, not to the derivatives of Clash, like OpenClash or KoolClash.
"
required: true
- type: input
attributes:
label: Clash version
description: "use `clash -v`"
validations:
required: true
- type: dropdown
id: os
attributes:
label: What OS are you seeing the problem on?
multiple: true
options:
- macOS
- Windows
- Linux
- OpenBSD/FreeBSD
- type: textarea
attributes:
render: yaml
label: "Clash config"
description: "
在下方附上 Clash core 配置文件,请确保配置文件中没有敏感信息(比如:服务器地址,密码,端口等)
Paste the Clash core configuration file below, please make sure that there is no sensitive information in the configuration file (e.g., server address/url, password, port)
"
validations:
required: true
- type: textarea
attributes:
render: shell
label: Clash log
description: "
在下方附上 Clash Core 的日志log level 使用 DEBUG
Paste the Clash core log below with the log level set to `DEBUG`.
"
- type: textarea
attributes:
label: Description
validations:
required: true

View File

@ -0,0 +1,36 @@
name: Feature request
description: Suggest an idea for this project
title: "[Feature] "
body:
- type: checkboxes
id: ensure
attributes:
label: Verify steps
description: "
在提交之前,请确认
Please verify that you've followed these steps
"
options:
- label: "
我已经在 [Issue Tracker](……/) 中找过我要提出的请求
I have searched on the [issue tracker](……/) for a related feature request.
"
required: true
- label: "
我已经仔细看过 [Documentation](https://wiki.metacubex.one/) 并无法找到这个功能
I have read the [documentation](https://wiki.metacubex.one/) and was unable to solve the issue.
"
required: true
- type: textarea
attributes:
label: Description
description: 请详细、清晰地表达你要提出的论述,例如这个问题如何影响到你?你想实现什么功能?目前 Clash Core 的行为是什麽?
validations:
required: true
- type: textarea
attributes:
label: Possible Solution
description: "
此项非必须,但是如果你有想法的话欢迎提出。
Not obligatory, but suggest a fix/reason for the bug, or ideas how to implement the addition or change
"

26
.github/release.sh vendored Normal file
View File

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

35
.github/rename-cgo.sh vendored Normal file
View File

@ -0,0 +1,35 @@
#!/bin/bash
FILENAMES=$(ls)
for FILENAME in $FILENAMES
do
if [[ $FILENAME =~ "darwin-10.16-arm64" ]];then
echo "rename darwin-10.16-arm64 $FILENAME"
mv $FILENAME clash.meta-darwin-arm64-cgo
elif [[ $FILENAME =~ "darwin-10.16-amd64" ]];then
echo "rename darwin-10.16-amd64 $FILENAME"
mv $FILENAME clash.meta-darwin-amd64-cgo
elif [[ $FILENAME =~ "windows-4.0-386" ]];then
echo "rename windows 386 $FILENAME"
mv $FILENAME clash.meta-windows-386-cgo.exe
elif [[ $FILENAME =~ "windows-4.0-amd64" ]];then
echo "rename windows amd64 $FILENAME"
mv $FILENAME clash.meta-windows-amd64-cgo.exe
elif [[ $FILENAME =~ "clash.meta-linux-arm-5" ]];then
echo "rename clash.meta-linux-arm-5 $FILENAME"
mv $FILENAME clash.meta-linux-armv5-cgo
elif [[ $FILENAME =~ "clash.meta-linux-arm-6" ]];then
echo "rename clash.meta-linux-arm-6 $FILENAME"
mv $FILENAME clash.meta-linux-armv6-cgo
elif [[ $FILENAME =~ "clash.meta-linux-arm-7" ]];then
echo "rename clash.meta-linux-arm-7 $FILENAME"
mv $FILENAME clash.meta-linux-armv7-cgo
elif [[ $FILENAME =~ "linux" ]];then
echo "rename linux $FILENAME"
mv $FILENAME $FILENAME-cgo
elif [[ $FILENAME =~ "android" ]];then
echo "rename android $FILENAME"
mv $FILENAME $FILENAME-cgo
else echo "skip $FILENAME"
fi
done

View File

@ -0,0 +1,51 @@
name: Android Branch Auto Sync
on:
workflow_dispatch:
push:
paths-ignore:
- "docs/**"
- "README.md"
- ".github/ISSUE_TEMPLATE/**"
branches:
- Alpha
- android-open
tags:
- "v*"
pull_request_target:
branches:
- Alpha
- android-open
jobs:
update-dependencies:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Configure Git
run: |
git config --global user.name 'GitHub Action'
git config --global user.email 'action@github.com'
- name: Sync android-real with Alpha rebase android-open
run: |
git fetch origin
git checkout origin/Alpha -b android-real
git merge --squash origin/android-open
git commit -m "Android: patch"
- name: Check for conflicts
run: |
CONFLICTS=$(git diff --name-only --diff-filter=U)
if [ ! -z "$CONFLICTS" ]; then
echo "There are conflicts in the following files:"
echo $CONFLICTS
exit 1
fi
- name: Push changes
run: |
git push origin android-real --force

350
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,350 @@
name: Build
on:
workflow_dispatch:
push:
paths-ignore:
- "docs/**"
- "README.md"
- ".github/ISSUE_TEMPLATE/**"
branches:
- Alpha
tags:
- "v*"
pull_request_target:
branches:
- Alpha
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true
env:
REGISTRY: docker.io
jobs:
Build:
permissions: write-all
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", 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",
}
- { 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" }
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set variables
run: echo "VERSION=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set variables
if: ${{github.ref_name=='Alpha'}}
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set variables
if: ${{github.ref_name=='Beta'}}
run: echo "VERSION=beta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set variables
if: ${{github.ref_name=='Meta'}}
run: echo "VERSION=meta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set variables
if: ${{github.ref_name=='' || github.ref_type=='tag'}}
run: echo "VERSION=$(git describe --tags)" >> $GITHUB_ENV
shell: bash
- name: Set ENV
run: |
sudo timedatectl set-timezone "Asia/Shanghai"
echo "NAME=clash.meta" >> $GITHUB_ENV
echo "REPO=${{ github.repository }}" >> $GITHUB_ENV
echo "ShortSHA=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV
echo "BUILDTIME=$(date)" >> $GITHUB_ENV
echo "BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set ENV
run: |
echo "TAGS=with_gvisor,with_lwip" >> $GITHUB_ENV
echo "LDFLAGS=-X 'github.com/Dreamacro/clash/constant.Version=${VERSION}' -X 'github.com/Dreamacro/clash/constant.BuildTime=${BUILDTIME}' -w -s -buildid=" >> $GITHUB_ENV
shell: bash
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: "1.21"
check-latest: true
- name: Test
if: ${{ matrix.job.id=='1' && matrix.job.type=='WithoutCGO' }}
run: |
go test ./...
- name: Build WithoutCGO
if: ${{ matrix.job.type=='WithoutCGO' }}
env:
NAME: Clash.Meta
BINDIR: bin
run: make -j$(($(nproc) + 1)) ${{ matrix.job.target }}
- uses: nttld/setup-ndk@v1
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }}
id: setup-ndk
with:
ndk-version: r26
add-to-path: false
local-cache: true
- 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: 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: 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: 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: Zip
if: ${{ success() }}
run: |
cd bin
ls -la
chmod +x *
cp ../.github/release.sh ./
bash ./release.sh
rm ./release.sh
ls -la
cd ..
- name: Save version
run: echo ${VERSION} > bin/version.txt
shell: bash
- uses: actions/upload-artifact@v3
if: ${{ success() }}
with:
name: artifact
path: bin/
Upload-Prerelease:
permissions: write-all
if: ${{ github.ref_type=='branch' && github.event_name != 'pull_request' }}
needs: [Build]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: artifact
path: bin/
- 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: 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/Clash.Meta/wiki/FAQ)
[查看文档 / Docs](https://metacubex.github.io/Meta-Docs/)
EOF
- name: Upload Prerelease
uses: softprops/action-gh-release@v1
if: ${{ success() }}
with:
tag_name: Prerelease-${{ github.ref_name }}
files: |
bin/*
prerelease: true
generate_release_notes: true
body_path: release.txt
Upload-Release:
permissions: write-all
if: ${{ github.ref_type=='tag' }}
needs: [Build]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: artifact
path: bin/
- name: Display structure of downloaded files
run: ls -R
working-directory: bin
- name: Upload Release
uses: softprops/action-gh-release@v1
if: ${{ success() }}
with:
tag_name: ${{ github.ref_name }}
files: bin/*
generate_release_notes: true
Docker:
if: ${{ github.event_name != 'pull_request' }}
permissions: write-all
needs: [Build]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/download-artifact@v3
with:
name: artifact
path: bin/
- name: Display structure of downloaded files
run: ls -R
working-directory: bin
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v2
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
with:
images: ${{ env.REGISTRY }}/${{ secrets.DOCKERHUB_ACCOUNT }}/${{secrets.DOCKERHUB_REPO}}
- name: Show files
run: |
ls .
ls bin/
- name: Log into registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
# 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
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
push: ${{ github.event_name != 'pull_request' }}
platforms: |
linux/386
linux/amd64
linux/arm64/v8
linux/arm/v7
# linux/riscv64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

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

View File

@ -1,44 +0,0 @@
name: Go
on: [push, pull_request]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Setup Go
uses: actions/setup-go@v1
with:
go-version: 1.13.x
- name: Check out code into the Go module directory
uses: actions/checkout@v1
- name: Cache go module
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Get dependencies and run test
run: |
go test ./...
- name: Build
if: startsWith(github.ref, 'refs/tags/')
env:
NAME: clash
BINDIR: bin
run: make -j releases
- name: Upload Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
files: bin/*
draft: true
prerelease: true

8
.gitignore vendored
View File

@ -12,7 +12,7 @@ bin/*
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# dep
# go mod vendor
vendor
# GoLand
@ -20,3 +20,9 @@ vendor
# macOS file
.DS_Store
# test suite
test/config/cache*
/output
.vscode/
.fleet/

17
.golangci.yaml Normal file
View File

@ -0,0 +1,17 @@
linters:
disable-all: true
enable:
- gofumpt
- staticcheck
- govet
- gci
linters-settings:
gci:
custom-order: true
sections:
- standard
- prefix(github.com/Dreamacro/clash)
- default
staticcheck:
go: '1.19'

View File

@ -1,18 +1,27 @@
FROM golang:alpine as builder
FROM alpine:latest as builder
ARG TARGETPLATFORM
RUN echo "I'm building for $TARGETPLATFORM"
RUN apk add --no-cache make git && \
wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz -O /tmp/GeoLite2-Country.tar.gz && \
tar zxvf /tmp/GeoLite2-Country.tar.gz -C /tmp && \
mv /tmp/GeoLite2-Country_*/GeoLite2-Country.mmdb /Country.mmdb
WORKDIR /clash-src
COPY . /clash-src
RUN go mod download && \
make linux-amd64 && \
mv ./bin/clash-linux-amd64 /clash
RUN apk add --no-cache gzip && \
mkdir /clash-config && \
wget -O /clash-config/geoip.metadb https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb && \
wget -O /clash-config/geosite.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat && \
wget -O /clash-config/geoip.dat https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat
COPY docker/file-name.sh /clash/file-name.sh
WORKDIR /clash
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 clash.gz && gzip -d clash.gz && echo "$FILE_NAME" > /clash-config/test
FROM alpine:latest
LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/Clash.Meta"
RUN apk add --no-cache ca-certificates
COPY --from=builder /Country.mmdb /root/.config/clash/
COPY --from=builder /clash /
ENTRYPOINT ["/clash"]
RUN apk add --no-cache ca-certificates tzdata iptables
VOLUME ["/root/.config/clash/"]
COPY --from=builder /clash-config/ /root/.config/clash/
COPY --from=builder /clash/clash /clash
RUN chmod +x /clash
ENTRYPOINT [ "/clash" ]

107
Makefile
View File

@ -1,42 +1,80 @@
NAME=clash
NAME=clash.meta
BINDIR=bin
VERSION=$(shell git describe --tags || echo "unknown version")
BRANCH=$(shell git branch --show-current)
ifeq ($(BRANCH),Alpha)
VERSION=alpha-$(shell git rev-parse --short HEAD)
else ifeq ($(BRANCH),Beta)
VERSION=beta-$(shell git rev-parse --short HEAD)
else ifeq ($(BRANCH),)
VERSION=$(shell git describe --tags)
else
VERSION=$(shell git rev-parse --short HEAD)
endif
BUILDTIME=$(shell date -u)
GOBUILD=CGO_ENABLED=0 go build -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
GOBUILD=CGO_ENABLED=0 go build -tags with_gvisor -trimpath -ldflags '-X "github.com/Dreamacro/clash/constant.Version=$(VERSION)" \
-X "github.com/Dreamacro/clash/constant.BuildTime=$(BUILDTIME)" \
-w -s'
-w -s -buildid='
PLATFORM_LIST = \
darwin-amd64 \
linux-386 \
darwin-arm64 \
linux-amd64-compatible \
linux-amd64 \
linux-armv5 \
linux-armv6 \
linux-armv7 \
linux-armv8 \
linux-arm64 \
linux-mips64 \
linux-mips64le \
linux-mips-softfloat \
linux-mips-hardfloat \
linux-mipsle-softfloat \
linux-mipsle-hardfloat \
linux-mips64 \
linux-mips64le \
linux-riscv64 \
linux-loong64 \
android-arm64 \
freebsd-386 \
freebsd-amd64
freebsd-amd64 \
freebsd-arm64
WINDOWS_ARCH_LIST = \
windows-386 \
windows-amd64
windows-amd64-compatible \
windows-amd64 \
windows-arm64 \
windows-arm32v7
all: linux-amd64 darwin-amd64 windows-amd64 # Most used
all:linux-amd64 linux-arm64\
darwin-amd64 darwin-arm64\
windows-amd64 windows-arm64\
darwin-all: darwin-amd64 darwin-arm64
docker:
GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64:
GOARCH=amd64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64-compatible:
GOARCH=amd64 GOOS=darwin GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-arm64:
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-386:
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64:
GOARCH=amd64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64-compatible:
GOARCH=amd64 GOOS=linux GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-arm64:
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv5:
GOARCH=arm GOOS=linux GOARM=5 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -47,9 +85,6 @@ linux-armv6:
linux-armv7:
GOARCH=arm GOOS=linux GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-armv8:
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-mips-softfloat:
GOARCH=mips GOMIPS=softfloat GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -68,17 +103,38 @@ linux-mips64:
linux-mips64le:
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-riscv64:
GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-loong64:
GOARCH=loong64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
android-arm64:
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-386:
GOARCH=386 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-amd64:
GOARCH=amd64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
GOARCH=amd64 GOOS=freebsd GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
freebsd-arm64:
GOARCH=arm64 GOOS=freebsd $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
windows-386:
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64:
GOARCH=amd64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64-compatible:
GOARCH=amd64 GOOS=windows GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-arm64:
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-arm32v7:
GOARCH=arm GOOS=windows GOARM=7 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
gz_releases=$(addsuffix .gz, $(PLATFORM_LIST))
zip_releases=$(addsuffix .zip, $(WINDOWS_ARCH_LIST))
@ -93,5 +149,20 @@ $(zip_releases): %.zip : %
all-arch: $(PLATFORM_LIST) $(WINDOWS_ARCH_LIST)
releases: $(gz_releases) $(zip_releases)
vet:
go test ./...
lint:
golangci-lint run ./...
clean:
rm $(BINDIR)/*
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 ./...

BIN
Meta.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

352
README.md
View File

@ -1,321 +1,101 @@
<h1 align="center">
<img src="https://github.com/Dreamacro/clash/raw/master/docs/logo.png" alt="Clash" width="200">
<br>Clash<br>
<img src="Meta.png" alt="Meta Kennel" width="200">
<br>Meta Kernel<br>
</h1>
<h4 align="center">A rule-based tunnel in Go.</h4>
<h3 align="center">Another Clash Kernel.</h3>
<p align="center">
<a href="https://github.com/Dreamacro/clash/actions">
<img src="https://img.shields.io/github/workflow/status/Dreamacro/clash/Go?style=flat-square" alt="Github Actions">
<a href="https://goreportcard.com/report/github.com/Clash-Mini/Clash.Meta">
<img src="https://goreportcard.com/badge/github.com/Clash-Mini/Clash.Meta?style=flat-square">
</a>
<a href="https://goreportcard.com/report/github.com/Dreamacro/clash">
<img src="https://goreportcard.com/badge/github.com/Dreamacro/clash?style=flat-square">
<img src="https://img.shields.io/github/go-mod/go-version/Dreamacro/clash?style=flat-square">
<a href="https://github.com/Clash-Mini/Clash.Meta/releases">
<img src="https://img.shields.io/github/release/Clash-Mini/Clash.Meta/all.svg?style=flat-square">
</a>
<a href="https://github.com/Dreamacro/clash/releases">
<img src="https://img.shields.io/github/release/Dreamacro/clash/all.svg?style=flat-square">
<a href="https://github.com/Clash-Mini/Clash.Meta">
<img src="https://img.shields.io/badge/release-Meta-00b4f0?style=flat-square">
</a>
</p>
## Features
- Local HTTP/HTTPS/SOCKS server
- GeoIP rule support
- Supports Vmess, Shadowsocks, Snell and SOCKS5 protocol
- Supports Netfilter TCP redirecting
- Comprehensive HTTP API
- Local HTTP/HTTPS/SOCKS server with authentication support
- VMess, VLESS, Shadowsocks, Trojan, Snell, TUIC, Hysteria protocol support
- Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP.
- Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes
- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node
based off latency
- Remote providers, allowing users to get node lists remotely instead of hard-coding in config
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
- Comprehensive HTTP RESTful API controller
## Install
## Dashboard
Clash Requires Go >= 1.13. You can build it from source:
A web dashboard with first-class support for this project has been created; it can be checked out at [metacubexd](https://github.com/MetaCubeX/metacubexd).
```sh
$ go get -u -v github.com/Dreamacro/clash
## Configration example
Configuration example is located at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml).
## Docs
Documentation can be found in [Clash.Meta Docs](https://clash-meta.wiki).
## For development
Requirements:
[Go 1.20 or newer](https://go.dev/dl/)
Build Clash.Meta:
```shell
git clone https://github.com/MetaCubeX/Clash.Meta.git
cd Clash.Meta && go mod download
go build
```
Pre-built binaries are available here: [release](https://github.com/Dreamacro/clash/releases)
Set go proxy if a connection to GitHub is not possible:
Check Clash version with:
```sh
$ clash -v
```shell
go env -w GOPROXY=https://goproxy.io,direct
```
## Daemon
Build with gvisor tun stack:
Unfortunately, there is no native and elegant way to implement daemons on Golang.
So we can use third-party daemon tools like PM2, Supervisor or the like.
In the case of [pm2](https://github.com/Unitech/pm2), we can start the daemon this way:
```sh
$ pm2 start clash
```shell
go build -tags with_gvisor
```
If you have Docker installed, you can run clash directly using `docker-compose`.
### IPTABLES configuration
[Run clash in docker](https://github.com/Dreamacro/clash/wiki/Run-clash-in-docker)
Work on Linux OS which supported `iptables`
## Config
```yaml
# Enable the TPROXY listener
tproxy-port: 9898
The default configuration directory is `$HOME/.config/clash`.
The name of the configuration file is `config.yaml`.
If you want to use another directory, use `-d` to control the configuration directory.
For example, you can use the current directory as the configuration directory:
```sh
$ clash -d .
iptables:
enable: true # default is false
inbound-interface: eth0 # detect the inbound interface, default is 'lo'
```
<details>
<summary>This is an example configuration file (click to expand)</summary>
## Debugging
```yml
# port of HTTP
port: 7890
Check [wiki](https://wiki.metacubex.one/api/#debug) to get an instruction on using debug
API.
# port of SOCKS5
socks-port: 7891
## Credits
# redir port for Linux and macOS
# redir-port: 7892
allow-lan: false
# Only applicable when setting allow-lan to true
# "*": bind all IP addresses
# 192.168.122.11: bind a single IPv4 address
# "[aaaa::a8aa:ff:fe09:57d8]": bind a single IPv6 address
# bind-address: "*"
# Rule / Global/ Direct (default is Rule)
mode: Rule
# set log level to stdout (default is info)
# info / warning / error / debug / silent
log-level: info
# RESTful API for clash
external-controller: 127.0.0.1:9090
# you can put the static web resource (such as clash-dashboard) to a directory, and clash would serve in `${API}/ui`
# input is a relative path to the configuration directory or an absolute path
# external-ui: folder
# Secret for RESTful API (Optional)
# secret: ""
# experimental feature
experimental:
ignore-resolve-fail: true # ignore dns resolve fail, default value is true
# authentication of local SOCKS5/HTTP(S) server
# authentication:
# - "user1:pass1"
# - "user2:pass2"
# # experimental hosts, support wildcard (e.g. *.clash.dev Even *.foo.*.example.com)
# # static domain has a higher priority than wildcard domain (foo.example.com > *.example.com)
# hosts:
# '*.clash.dev': 127.0.0.1
# 'alpha.clash.dev': '::1'
# dns:
# enable: true # set true to enable dns (default is false)
# ipv6: false # default is false
# listen: 0.0.0.0:53
# enhanced-mode: redir-host # or fake-ip
# # fake-ip-range: 198.18.0.1/16 # if you don't know what it is, don't change it
# fake-ip-filter: # fake ip white domain list
# - *.lan
# - localhost.ptlogin2.qq.com
# nameserver:
# - 114.114.114.114
# - tls://dns.rubyfish.cn:853 # dns over tls
# - https://1.1.1.1/dns-query # dns over https
# fallback: # concurrent request with nameserver, fallback used when GEOIP country isn't CN
# - tcp://1.1.1.1
# fallback-filter:
# geoip: true # default
# ipcidr: # ips in these subnets will be considered polluted
# - 240.0.0.0/4
Proxy:
# shadowsocks
# The supported ciphers(encrypt methods):
# aes-128-gcm aes-192-gcm aes-256-gcm
# aes-128-cfb aes-192-cfb aes-256-cfb
# aes-128-ctr aes-192-ctr aes-256-ctr
# rc4-md5 chacha20-ietf xchacha20
# chacha20-ietf-poly1305 xchacha20-ietf-poly1305
- name: "ss1"
type: ss
server: server
port: 443
cipher: chacha20-ietf-poly1305
password: "password"
# udp: true
# old obfs configuration format remove after prerelease
- name: "ss2"
type: ss
server: server
port: 443
cipher: chacha20-ietf-poly1305
password: "password"
plugin: obfs
plugin-opts:
mode: tls # or http
# host: bing.com
- name: "ss3"
type: ss
server: server
port: 443
cipher: chacha20-ietf-poly1305
password: "password"
plugin: v2ray-plugin
plugin-opts:
mode: websocket # no QUIC now
# tls: true # wss
# skip-cert-verify: true
# host: bing.com
# path: "/"
# mux: true
# headers:
# custom: value
# vmess
# cipher support auto/aes-128-gcm/chacha20-poly1305/none
- name: "vmess"
type: vmess
server: server
port: 443
uuid: uuid
alterId: 32
cipher: auto
# udp: true
# tls: true
# skip-cert-verify: true
# network: ws
# ws-path: /path
# ws-headers:
# Host: v2ray.com
# socks5
- name: "socks"
type: socks5
server: server
port: 443
# username: username
# password: password
# tls: true
# skip-cert-verify: true
# udp: true
# http
- name: "http"
type: http
server: server
port: 443
# username: username
# password: password
# tls: true # https
# skip-cert-verify: true
# snell
- name: "snell"
type: snell
server: server
port: 44046
psk: yourpsk
# obfs-opts:
# mode: http # or tls
# host: bing.com
Proxy Group:
# url-test select which proxy will be used by benchmarking speed to a URL.
- name: "auto"
type: url-test
proxies:
- ss1
- ss2
- vmess1
url: 'http://www.gstatic.com/generate_204'
interval: 300
# fallback select an available policy by priority. The availability is tested by accessing an URL, just like an auto url-test group.
- name: "fallback-auto"
type: fallback
proxies:
- ss1
- ss2
- vmess1
url: 'http://www.gstatic.com/generate_204'
interval: 300
# load-balance: The request of the same eTLD will be dial on the same proxy.
- name: "load-balance"
type: load-balance
proxies:
- ss1
- ss2
- vmess1
url: 'http://www.gstatic.com/generate_204'
interval: 300
# select is used for selecting proxy or proxy group
# you can use RESTful API to switch proxy, is recommended for use in GUI.
- name: Proxy
type: select
proxies:
- ss1
- ss2
- vmess1
- auto
Rule:
- DOMAIN-SUFFIX,google.com,auto
- DOMAIN-KEYWORD,google,auto
- DOMAIN,google.com,auto
- DOMAIN-SUFFIX,ad.com,REJECT
# rename SOURCE-IP-CIDR and would remove after prerelease
- SRC-IP-CIDR,192.168.1.201/32,DIRECT
# optional param "no-resolve" for IP rules (GEOIP IP-CIDR)
- IP-CIDR,127.0.0.0/8,DIRECT
- GEOIP,CN,DIRECT
- DST-PORT,80,DIRECT
- SRC-PORT,7777,DIRECT
# FINAL would remove after prerelease
# you also can use `FINAL,Proxy` or `FINAL,,Proxy` now
- MATCH,auto
```
</details>
## Advanced
[Provider](https://github.com/Dreamacro/clash/wiki/Provider)
## Documentations
https://clash.gitbook.io/
## Thanks
[riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
[v2ray/v2ray-core](https://github.com/v2ray/v2ray-core)
- [Dreamacro/clash](https://github.com/Dreamacro/clash)
- [SagerNet/sing-box](https://github.com/SagerNet/sing-box)
- [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
- [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core)
- [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go)
- [yaling888/clash-plus-pro](https://github.com/yaling888/clash)
## License
This software is released under the GPL-3.0 license.
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FDreamacro%2Fclash.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FDreamacro%2Fclash?ref=badge_large)
## TODO
- [x] Complementing the necessary rule operators
- [x] Redir proxy
- [x] UDP support
- [x] Connection manager
- [ ] Event API

372
adapter/adapter.go Normal file
View File

@ -0,0 +1,372 @@
package adapter
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"net/netip"
"net/url"
"strconv"
"time"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/queue"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/puzpuzpuz/xsync/v2"
)
var UnifiedDelay = atomic.NewBool(false)
const (
defaultHistoriesNum = 10
)
type extraProxyState struct {
history *queue.Queue[C.DelayHistory]
alive atomic.Bool
}
type Proxy struct {
C.ProxyAdapter
history *queue.Queue[C.DelayHistory]
alive atomic.Bool
url string
extra *xsync.MapOf[string, *extraProxyState]
}
// Alive implements C.Proxy
func (p *Proxy) Alive() bool {
return p.alive.Load()
}
// AliveForTestUrl implements C.Proxy
func (p *Proxy) AliveForTestUrl(url string) bool {
if state, ok := p.extra.Load(url); ok {
return state.alive.Load()
}
return p.alive.Load()
}
// Dial implements C.Proxy
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
defer cancel()
return p.DialContext(ctx, metadata)
}
// DialContext implements C.ProxyAdapter
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
return conn, err
}
// DialUDP implements C.ProxyAdapter
func (p *Proxy) DialUDP(metadata *C.Metadata) (C.PacketConn, error) {
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout)
defer cancel()
return p.ListenPacketContext(ctx, metadata)
}
// ListenPacketContext implements C.ProxyAdapter
func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
return pc, err
}
// DelayHistory implements C.Proxy
func (p *Proxy) DelayHistory() []C.DelayHistory {
queueM := p.history.Copy()
histories := []C.DelayHistory{}
for _, item := range queueM {
histories = append(histories, item)
}
return histories
}
// DelayHistoryForTestUrl implements C.Proxy
func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory {
var queueM []C.DelayHistory
if state, ok := p.extra.Load(url); ok {
queueM = state.history.Copy()
}
if queueM == nil {
queueM = p.history.Copy()
}
histories := []C.DelayHistory{}
for _, item := range queueM {
histories = append(histories, item)
}
return histories
}
func (p *Proxy) ExtraDelayHistory() map[string][]C.DelayHistory {
extraHistory := map[string][]C.DelayHistory{}
p.extra.Range(func(k string, v *extraProxyState) bool {
testUrl := k
state := v
histories := []C.DelayHistory{}
queueM := state.history.Copy()
for _, item := range queueM {
histories = append(histories, item)
}
extraHistory[testUrl] = histories
return true
})
return extraHistory
}
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
// implements C.Proxy
func (p *Proxy) LastDelay() (delay uint16) {
var max uint16 = 0xffff
if !p.alive.Load() {
return max
}
history := p.history.Last()
if history.Delay == 0 {
return max
}
return history.Delay
}
// LastDelayForTestUrl implements C.Proxy
func (p *Proxy) LastDelayForTestUrl(url string) (delay uint16) {
var max uint16 = 0xffff
alive := p.alive.Load()
history := p.history.Last()
if state, ok := p.extra.Load(url); ok {
alive = state.alive.Load()
history = state.history.Last()
}
if !alive {
return max
}
if history.Delay == 0 {
return max
}
return history.Delay
}
// MarshalJSON implements C.ProxyAdapter
func (p *Proxy) MarshalJSON() ([]byte, error) {
inner, err := p.ProxyAdapter.MarshalJSON()
if err != nil {
return inner, err
}
mapping := map[string]any{}
_ = json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory()
mapping["extra"] = p.ExtraDelayHistory()
mapping["alive"] = p.Alive()
mapping["name"] = p.Name()
mapping["udp"] = p.SupportUDP()
mapping["xudp"] = p.SupportXUDP()
mapping["tfo"] = p.SupportTFO()
return json.Marshal(mapping)
}
// URLTest get the delay for the specified URL
// implements C.Proxy
func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16], store C.DelayHistoryStoreType) (t uint16, err error) {
defer func() {
alive := err == nil
store = p.determineFinalStoreType(store, url)
switch store {
case C.OriginalHistory:
p.alive.Store(alive)
record := C.DelayHistory{Time: time.Now()}
if alive {
record.Delay = t
}
p.history.Put(record)
if p.history.Len() > defaultHistoriesNum {
p.history.Pop()
}
// test URL configured by the proxy provider
if len(p.url) == 0 {
p.url = url
}
case C.ExtraHistory:
record := C.DelayHistory{Time: time.Now()}
if alive {
record.Delay = t
}
p.history.Put(record)
if p.history.Len() > defaultHistoriesNum {
p.history.Pop()
}
state, ok := p.extra.Load(url)
if !ok {
state = &extraProxyState{
history: queue.New[C.DelayHistory](defaultHistoriesNum),
alive: atomic.NewBool(true),
}
p.extra.Store(url, state)
}
state.alive.Store(alive)
state.history.Put(record)
if state.history.Len() > defaultHistoriesNum {
state.history.Pop()
}
default:
log.Debugln("health check result will be discarded, url: %s alive: %t, delay: %d", url, alive, t)
}
}()
unifiedDelay := UnifiedDelay.Load()
addr, err := urlToMetadata(url)
if err != nil {
return
}
start := time.Now()
instance, err := p.DialContext(ctx, &addr)
if err != nil {
return
}
defer func() {
_ = instance.Close()
}()
req, err := http.NewRequest(http.MethodHead, url, nil)
if err != nil {
return
}
req = req.WithContext(ctx)
transport := &http.Transport{
DialContext: func(context.Context, string, string) (net.Conn, error) {
return instance, nil
},
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := http.Client{
Timeout: 30 * time.Second,
Transport: transport,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
defer client.CloseIdleConnections()
resp, err := client.Do(req)
if err != nil {
return
}
_ = resp.Body.Close()
if unifiedDelay {
second := time.Now()
resp, err = client.Do(req)
if err == nil {
_ = resp.Body.Close()
start = second
}
}
if expectedStatus != nil && !expectedStatus.Check(uint16(resp.StatusCode)) {
// maybe another value should be returned for differentiation
err = errors.New("response status is inconsistent with the expected status")
}
t = uint16(time.Since(start) / time.Millisecond)
return
}
func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{
ProxyAdapter: adapter,
history: queue.New[C.DelayHistory](defaultHistoriesNum),
alive: atomic.NewBool(true),
url: "",
extra: xsync.NewMapOf[*extraProxyState]()}
}
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
u, err := url.Parse(rawURL)
if err != nil {
return
}
port := u.Port()
if port == "" {
switch u.Scheme {
case "https":
port = "443"
case "http":
port = "80"
default:
err = fmt.Errorf("%s scheme not Support", rawURL)
return
}
}
uintPort, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return
}
addr = C.Metadata{
Host: u.Hostname(),
DstIP: netip.Addr{},
DstPort: uint16(uintPort),
}
return
}
func (p *Proxy) determineFinalStoreType(store C.DelayHistoryStoreType, url string) C.DelayHistoryStoreType {
if store != C.DropHistory {
return store
}
if len(p.url) == 0 || url == p.url {
return C.OriginalHistory
}
if p.extra.Size() < 2*C.DefaultMaxHealthCheckUrlNum {
return C.ExtraHistory
}
_, ok := p.extra.Load(url)
if ok {
return C.ExtraHistory
}
return store
}

View File

@ -0,0 +1,65 @@
package inbound
import (
"net"
C "github.com/Dreamacro/clash/constant"
)
type Addition func(metadata *C.Metadata)
func ApplyAdditions(metadata *C.Metadata, additions ...Addition) {
for _, addition := range additions {
addition(metadata)
}
}
func WithInName(name string) Addition {
return func(metadata *C.Metadata) {
metadata.InName = name
}
}
func WithInUser(user string) Addition {
return func(metadata *C.Metadata) {
metadata.InUser = user
}
}
func WithSpecialRules(specialRules string) Addition {
return func(metadata *C.Metadata) {
metadata.SpecialRules = specialRules
}
}
func WithSpecialProxy(specialProxy string) Addition {
return func(metadata *C.Metadata) {
metadata.SpecialProxy = specialProxy
}
}
func WithDstAddr(addr net.Addr) Addition {
return func(metadata *C.Metadata) {
_ = metadata.SetRemoteAddr(addr)
}
}
func WithSrcAddr(addr net.Addr) Addition {
return func(metadata *C.Metadata) {
m := C.Metadata{}
if err := m.SetRemoteAddr(addr);err ==nil{
metadata.SrcIP = m.DstIP
metadata.SrcPort = m.DstPort
}
}
}
func WithInAddr(addr net.Addr) Addition {
return func(metadata *C.Metadata) {
m := C.Metadata{}
if err := m.SetRemoteAddr(addr);err ==nil{
metadata.InIP = m.DstIP
metadata.InPort = m.DstPort
}
}
}

45
adapter/inbound/auth.go Normal file
View File

@ -0,0 +1,45 @@
package inbound
import (
"net"
"net/netip"
C "github.com/Dreamacro/clash/constant"
)
var skipAuthPrefixes []netip.Prefix
func SetSkipAuthPrefixes(prefixes []netip.Prefix) {
skipAuthPrefixes = prefixes
}
func SkipAuthPrefixes() []netip.Prefix {
return skipAuthPrefixes
}
func SkipAuthRemoteAddr(addr net.Addr) bool {
m := C.Metadata{}
if err := m.SetRemoteAddr(addr); err != nil {
return false
}
return skipAuth(m.AddrPort().Addr())
}
func SkipAuthRemoteAddress(addr string) bool {
m := C.Metadata{}
if err := m.SetRemoteAddress(addr); err != nil {
return false
}
return skipAuth(m.AddrPort().Addr())
}
func skipAuth(addr netip.Addr) bool {
if addr.IsValid() {
for _, prefix := range skipAuthPrefixes {
if prefix.Contains(addr.Unmap()) {
return true
}
}
}
return false
}

20
adapter/inbound/http.go Normal file
View File

@ -0,0 +1,20 @@
package inbound
import (
"net"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
)
// NewHTTP receive normal http request and return HTTPContext
func NewHTTP(target socks5.Addr, srcConn net.Conn, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) {
metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP
metadata.Type = C.HTTP
metadata.RawSrcAddr = srcConn.RemoteAddr()
metadata.RawDstAddr = srcConn.LocalAddr()
ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
ApplyAdditions(metadata, additions...)
return conn, metadata
}

17
adapter/inbound/https.go Normal file
View File

@ -0,0 +1,17 @@
package inbound
import (
"net"
"net/http"
C "github.com/Dreamacro/clash/constant"
)
// NewHTTPS receive CONNECT request and return ConnContext
func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) {
metadata := parseHTTPAddr(request)
metadata.Type = C.HTTPS
ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
ApplyAdditions(metadata, additions...)
return conn, metadata
}

30
adapter/inbound/listen.go Normal file
View File

@ -0,0 +1,30 @@
package inbound
import (
"context"
"net"
"github.com/sagernet/tfo-go"
)
var (
lc = tfo.ListenConfig{
DisableTFO: true,
}
)
func SetTfo(open bool) {
lc.DisableTFO = !open
}
func SetMPTCP(open bool) {
setMultiPathTCP(&lc.ListenConfig, open)
}
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
return lc.Listen(ctx, network, address)
}
func Listen(network, address string) (net.Listener, error) {
return ListenContext(context.Background(), network, address)
}

View File

@ -0,0 +1,10 @@
//go:build !go1.21
package inbound
import "net"
const multipathTCPAvailable = false
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
}

View File

@ -0,0 +1,11 @@
//go:build go1.21
package inbound
import "net"
const multipathTCPAvailable = true
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
listenConfig.SetMultipathTCP(open)
}

22
adapter/inbound/packet.go Normal file
View File

@ -0,0 +1,22 @@
package inbound
import (
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
)
// NewPacket is PacketAdapter generator
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions ...Addition) (C.UDPPacket, *C.Metadata) {
metadata := parseSocksAddr(target)
metadata.NetWork = C.UDP
metadata.Type = source
metadata.RawSrcAddr = packet.LocalAddr()
metadata.RawDstAddr = metadata.UDPAddr()
ApplyAdditions(metadata, WithSrcAddr(packet.LocalAddr()))
if p, ok := packet.(C.UDPPacketInAddr); ok {
ApplyAdditions(metadata, WithInAddr(p.InAddr()))
}
ApplyAdditions(metadata, additions...)
return packet, metadata
}

18
adapter/inbound/socket.go Normal file
View File

@ -0,0 +1,18 @@
package inbound
import (
"net"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
)
// NewSocket receive TCP inbound and return ConnContext
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Addition) (net.Conn, *C.Metadata) {
metadata := parseSocksAddr(target)
metadata.NetWork = C.TCP
metadata.Type = source
ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
ApplyAdditions(metadata, additions...)
return conn, metadata
}

63
adapter/inbound/util.go Normal file
View File

@ -0,0 +1,63 @@
package inbound
import (
"net"
"net/http"
"net/netip"
"strconv"
"strings"
"github.com/Dreamacro/clash/common/nnip"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
)
func parseSocksAddr(target socks5.Addr) *C.Metadata {
metadata := &C.Metadata{}
switch target[0] {
case socks5.AtypDomainName:
// trim for FQDN
metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".")
metadata.DstPort = uint16((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
case socks5.AtypIPv4:
metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv4len]))
metadata.DstPort = uint16((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
case socks5.AtypIPv6:
ip6, _ := netip.AddrFromSlice(target[1 : 1+net.IPv6len])
metadata.DstIP = ip6.Unmap()
metadata.DstPort = uint16((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
}
return metadata
}
func parseHTTPAddr(request *http.Request) *C.Metadata {
host := request.URL.Hostname()
port := request.URL.Port()
if port == "" {
port = "80"
}
// trim FQDN (#737)
host = strings.TrimRight(host, ".")
var uint16Port uint16
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
uint16Port = uint16(port)
}
metadata := &C.Metadata{
NetWork: C.TCP,
Host: host,
DstIP: netip.Addr{},
DstPort: uint16Port,
}
ip, err := netip.ParseAddr(host)
if err == nil {
metadata.DstIP = ip
}
return metadata
}

287
adapter/outbound/base.go Normal file
View File

@ -0,0 +1,287 @@
package outbound
import (
"context"
"encoding/json"
"net"
"strings"
"syscall"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
)
type Base struct {
name string
addr string
iface string
tp C.AdapterType
udp bool
xudp bool
tfo bool
mpTcp bool
rmark int
id string
prefer C.DNSPrefer
}
// Name implements C.ProxyAdapter
func (b *Base) Name() string {
return b.name
}
// Id implements C.ProxyAdapter
func (b *Base) Id() string {
if b.id == "" {
b.id = utils.NewUUIDV6().String()
}
return b.id
}
// Type implements C.ProxyAdapter
func (b *Base) Type() C.AdapterType {
return b.tp
}
// StreamConnContext implements C.ProxyAdapter
func (b *Base) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, C.ErrNotSupport
}
func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
return nil, C.ErrNotSupport
}
// DialContextWithDialer implements C.ProxyAdapter
func (b *Base) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
return nil, C.ErrNotSupport
}
// ListenPacketContext implements C.ProxyAdapter
func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
return nil, C.ErrNotSupport
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (b *Base) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
return nil, C.ErrNotSupport
}
// SupportWithDialer implements C.ProxyAdapter
func (b *Base) SupportWithDialer() C.NetWork {
return C.InvalidNet
}
// SupportUOT implements C.ProxyAdapter
func (b *Base) SupportUOT() bool {
return false
}
// SupportUDP implements C.ProxyAdapter
func (b *Base) SupportUDP() bool {
return b.udp
}
// SupportXUDP implements C.ProxyAdapter
func (b *Base) SupportXUDP() bool {
return b.xudp
}
// SupportTFO implements C.ProxyAdapter
func (b *Base) SupportTFO() bool {
return b.tfo
}
// IsL3Protocol implements C.ProxyAdapter
func (b *Base) IsL3Protocol(metadata *C.Metadata) bool {
return false
}
// MarshalJSON implements C.ProxyAdapter
func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": b.Type().String(),
"id": b.Id(),
})
}
// Addr implements C.ProxyAdapter
func (b *Base) Addr() string {
return b.addr
}
// Unwrap implements C.ProxyAdapter
func (b *Base) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
return nil
}
// DialOptions return []dialer.Option from struct
func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
if b.iface != "" {
opts = append(opts, dialer.WithInterface(b.iface))
}
if b.rmark != 0 {
opts = append(opts, dialer.WithRoutingMark(b.rmark))
}
switch b.prefer {
case C.IPv4Only:
opts = append(opts, dialer.WithOnlySingleStack(true))
case C.IPv6Only:
opts = append(opts, dialer.WithOnlySingleStack(false))
case C.IPv4Prefer:
opts = append(opts, dialer.WithPreferIPv4())
case C.IPv6Prefer:
opts = append(opts, dialer.WithPreferIPv6())
default:
}
if b.tfo {
opts = append(opts, dialer.WithTFO(true))
}
if b.mpTcp {
opts = append(opts, dialer.WithMPTCP(true))
}
return opts
}
type BasicOption struct {
TFO bool `proxy:"tfo,omitempty" group:"tfo,omitempty"`
MPTCP bool `proxy:"mptcp,omitempty" group:"mptcp,omitempty"`
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"`
DialerProxy string `proxy:"dialer-proxy,omitempty"` // don't apply this option into groups, but can set a group name in a proxy
}
type BaseOption struct {
Name string
Addr string
Type C.AdapterType
UDP bool
XUDP bool
TFO bool
MPTCP bool
Interface string
RoutingMark int
Prefer C.DNSPrefer
}
func NewBase(opt BaseOption) *Base {
return &Base{
name: opt.Name,
addr: opt.Addr,
tp: opt.Type,
udp: opt.UDP,
xudp: opt.XUDP,
tfo: opt.TFO,
mpTcp: opt.MPTCP,
iface: opt.Interface,
rmark: opt.RoutingMark,
prefer: opt.Prefer,
}
}
type conn struct {
N.ExtendedConn
chain C.Chain
actualRemoteDestination string
}
func (c *conn) RemoteDestination() string {
return c.actualRemoteDestination
}
// Chains implements C.Connection
func (c *conn) Chains() C.Chain {
return c.chain
}
// AppendToChains implements C.Connection
func (c *conn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name())
}
func (c *conn) Upstream() any {
return c.ExtendedConn
}
func (c *conn) WriterReplaceable() bool {
return true
}
func (c *conn) ReaderReplaceable() bool {
return true
}
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
if _, ok := c.(syscall.Conn); !ok { // exclusion system conn like *net.TCPConn
c = N.NewDeadlineConn(c) // most conn from outbound can't handle readDeadline correctly
}
return &conn{N.NewExtendedConn(c), []string{a.Name()}, parseRemoteDestination(a.Addr())}
}
type packetConn struct {
N.EnhancePacketConn
chain C.Chain
adapterName string
connID string
actualRemoteDestination string
}
func (c *packetConn) RemoteDestination() string {
return c.actualRemoteDestination
}
// Chains implements C.Connection
func (c *packetConn) Chains() C.Chain {
return c.chain
}
// AppendToChains implements C.Connection
func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name())
}
func (c *packetConn) LocalAddr() net.Addr {
lAddr := c.EnhancePacketConn.LocalAddr()
return N.NewCustomAddr(c.adapterName, c.connID, lAddr) // make quic-go's connMultiplexer happy
}
func (c *packetConn) Upstream() any {
return c.EnhancePacketConn
}
func (c *packetConn) WriterReplaceable() bool {
return true
}
func (c *packetConn) ReaderReplaceable() bool {
return true
}
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
epc := N.NewEnhancePacketConn(pc)
if _, ok := pc.(syscall.Conn); !ok { // exclusion system conn like *net.UDPConn
epc = N.NewDeadlineEnhancePacketConn(epc) // most conn from outbound can't handle readDeadline correctly
}
return &packetConn{epc, []string{a.Name()}, a.Name(), utils.NewUUIDV4().String(), parseRemoteDestination(a.Addr())}
}
func parseRemoteDestination(addr string) string {
if dst, _, err := net.SplitHostPort(addr); err == nil {
return dst
} else {
if addrError, ok := err.(*net.AddrError); ok && strings.Contains(addrError.Err, "missing port") {
return dst
} else {
return ""
}
}
}

View File

@ -0,0 +1,86 @@
package outbound
import (
"context"
"errors"
"net/netip"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
)
type Direct struct {
*Base
}
type DirectOption struct {
BasicOption
Name string `proxy:"name"`
}
// DialContext implements C.ProxyAdapter
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
if err != nil {
return nil, err
}
N.TCPKeepAlive(c)
return NewConn(c, d), nil
}
// ListenPacketContext implements C.ProxyAdapter
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DefaultResolver)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
pc, err := dialer.NewDialer(d.Base.DialOptions(opts...)...).ListenPacket(ctx, "udp", "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
if err != nil {
return nil, err
}
return newPacketConn(pc, d), nil
}
func NewDirectWithOption(option DirectOption) *Direct {
return &Direct{
Base: &Base{
name: option.Name,
tp: C.Direct,
udp: true,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
}
}
func NewDirect() *Direct {
return &Direct{
Base: &Base{
name: "DIRECT",
tp: C.Direct,
udp: true,
prefer: C.DualStack,
},
}
}
func NewCompatible() *Direct {
return &Direct{
Base: &Base{
name: "COMPATIBLE",
tp: C.Compatible,
udp: true,
prefer: C.DualStack,
},
}
}

186
adapter/outbound/http.go Normal file
View File

@ -0,0 +1,186 @@
package outbound
import (
"bufio"
"context"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"net/http"
"strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant"
)
type Http struct {
*Base
user string
pass string
tlsConfig *tls.Config
option *HttpOption
}
type HttpOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UserName string `proxy:"username,omitempty"`
Password string `proxy:"password,omitempty"`
TLS bool `proxy:"tls,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Headers map[string]string `proxy:"headers,omitempty"`
}
// StreamConnContext implements C.ProxyAdapter
func (h *Http) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if h.tlsConfig != nil {
cc := tls.Client(c, h.tlsConfig)
err := cc.HandshakeContext(ctx)
c = cc
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
}
}
if err := h.shakeHand(metadata, c); err != nil {
return nil, err
}
return c, nil
}
// DialContext implements C.ProxyAdapter
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
return h.DialContextWithDialer(ctx, dialer.NewDialer(h.Base.DialOptions(opts...)...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(h.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(h.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", h.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
}
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = h.StreamConnContext(ctx, c, metadata)
if err != nil {
return nil, err
}
return NewConn(c, h), nil
}
// SupportWithDialer implements C.ProxyAdapter
func (h *Http) SupportWithDialer() C.NetWork {
return C.TCP
}
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
addr := metadata.RemoteAddress()
HeaderString := "CONNECT " + addr + " HTTP/1.1\r\n"
tempHeaders := map[string]string{
"Host": addr,
"User-Agent": "Go-http-client/1.1",
"Proxy-Connection": "Keep-Alive",
}
for key, value := range h.option.Headers {
tempHeaders[key] = value
}
if h.user != "" && h.pass != "" {
auth := h.user + ":" + h.pass
tempHeaders["Proxy-Authorization"] = "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
}
for key, value := range tempHeaders {
HeaderString += key + ": " + value + "\r\n"
}
HeaderString += "\r\n"
_, err := rw.Write([]byte(HeaderString))
if err != nil {
return err
}
resp, err := http.ReadResponse(bufio.NewReader(rw), nil)
if err != nil {
return err
}
if resp.StatusCode == http.StatusOK {
return nil
}
if resp.StatusCode == http.StatusProxyAuthRequired {
return errors.New("HTTP need auth")
}
if resp.StatusCode == http.StatusMethodNotAllowed {
return errors.New("CONNECT method not allowed by proxy")
}
if resp.StatusCode >= http.StatusInternalServerError {
return errors.New(resp.Status)
}
return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode)
}
func NewHttp(option HttpOption) (*Http, error) {
var tlsConfig *tls.Config
if option.TLS {
sni := option.Server
if option.SNI != "" {
sni = option.SNI
}
var err error
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ServerName: sni,
}, option.Fingerprint)
if err != nil {
return nil, err
}
}
return &Http{
Base: &Base{
name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Http,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
user: option.UserName,
pass: option.Password,
tlsConfig: tlsConfig,
option: &option,
}, nil
}

View File

@ -0,0 +1,290 @@
package outbound
import (
"context"
"crypto/tls"
"encoding/base64"
"fmt"
"net"
"net/netip"
"strconv"
"time"
"github.com/metacubex/quic-go"
"github.com/metacubex/quic-go/congestion"
M "github.com/sagernet/sing/common/metadata"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
hyCongestion "github.com/Dreamacro/clash/transport/hysteria/congestion"
"github.com/Dreamacro/clash/transport/hysteria/core"
"github.com/Dreamacro/clash/transport/hysteria/obfs"
"github.com/Dreamacro/clash/transport/hysteria/pmtud_fix"
"github.com/Dreamacro/clash/transport/hysteria/transport"
"github.com/Dreamacro/clash/transport/hysteria/utils"
)
const (
mbpsToBps = 125000
DefaultStreamReceiveWindow = 15728640 // 15 MB/s
DefaultConnectionReceiveWindow = 67108864 // 64 MB/s
DefaultALPN = "hysteria"
DefaultProtocol = "udp"
DefaultHopInterval = 10
)
type Hysteria struct {
*Base
option *HysteriaOption
client *core.Client
}
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
tcpConn, err := h.client.DialTCP(metadata.String(), metadata.DstPort, h.genHdc(ctx, opts...))
if err != nil {
return nil, err
}
return NewConn(tcpConn, h), nil
}
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
udpConn, err := h.client.DialUDP(h.genHdc(ctx, opts...))
if err != nil {
return nil, err
}
return newPacketConn(&hyPacketConn{udpConn}, h), nil
}
func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
return &hyDialerWithContext{
ctx: context.Background(),
hyDialer: func(network string) (net.PacketConn, error) {
var err error
var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...)
if len(h.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(h.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
rAddrPort, _ := netip.ParseAddrPort(h.Addr())
return cDialer.ListenPacket(ctx, network, "", rAddrPort)
},
remoteAddr: func(addr string) (net.Addr, error) {
return resolveUDPAddrWithPrefer(ctx, "udp", addr, h.prefer)
},
}
}
type HysteriaOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port,omitempty"`
Ports string `proxy:"ports,omitempty"`
Protocol string `proxy:"protocol,omitempty"`
ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash
Up string `proxy:"up"`
UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash
Down string `proxy:"down"`
DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash
Auth string `proxy:"auth,omitempty"`
AuthString string `proxy:"auth-str,omitempty"`
Obfs string `proxy:"obfs,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
CustomCA string `proxy:"ca,omitempty"`
CustomCAString string `proxy:"ca-str,omitempty"`
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
ReceiveWindow int `proxy:"recv-window,omitempty"`
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
FastOpen bool `proxy:"fast-open,omitempty"`
HopInterval int `proxy:"hop-interval,omitempty"`
}
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
var up, down uint64
up = StringToBps(c.Up)
if up == 0 {
return 0, 0, fmt.Errorf("invaild upload speed: %s", c.Up)
}
down = StringToBps(c.Down)
if down == 0 {
return 0, 0, fmt.Errorf("invaild download speed: %s", c.Down)
}
return up, down, nil
}
func NewHysteria(option HysteriaOption) (*Hysteria, error) {
clientTransport := &transport.ClientTransport{
Dialer: &net.Dialer{
Timeout: 8 * time.Second,
},
}
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
ports := option.Ports
serverName := option.Server
if option.SNI != "" {
serverName = option.SNI
}
tlsConfig := &tls.Config{
ServerName: serverName,
InsecureSkipVerify: option.SkipCertVerify,
MinVersion: tls.VersionTLS13,
}
var err error
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
if err != nil {
return nil, err
}
if len(option.ALPN) > 0 {
tlsConfig.NextProtos = option.ALPN
} else {
tlsConfig.NextProtos = []string{DefaultALPN}
}
quicConfig := &quic.Config{
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
InitialConnectionReceiveWindow: uint64(option.ReceiveWindow),
MaxConnectionReceiveWindow: uint64(option.ReceiveWindow),
KeepAlivePeriod: 10 * time.Second,
DisablePathMTUDiscovery: option.DisableMTUDiscovery,
EnableDatagrams: true,
}
if option.ObfsProtocol != "" {
option.Protocol = option.ObfsProtocol
}
if option.Protocol == "" {
option.Protocol = DefaultProtocol
}
if option.HopInterval == 0 {
option.HopInterval = DefaultHopInterval
}
hopInterval := time.Duration(int64(option.HopInterval)) * time.Second
if option.ReceiveWindow == 0 {
quicConfig.InitialStreamReceiveWindow = DefaultStreamReceiveWindow / 10
quicConfig.MaxStreamReceiveWindow = DefaultStreamReceiveWindow
}
if option.ReceiveWindow == 0 {
quicConfig.InitialConnectionReceiveWindow = DefaultConnectionReceiveWindow / 10
quicConfig.MaxConnectionReceiveWindow = DefaultConnectionReceiveWindow
}
if !quicConfig.DisablePathMTUDiscovery && pmtud_fix.DisablePathMTUDiscovery {
log.Infoln("hysteria: Path MTU Discovery is not yet supported on this platform")
}
var auth = []byte(option.AuthString)
if option.Auth != "" {
auth, err = base64.StdEncoding.DecodeString(option.Auth)
if err != nil {
return nil, err
}
}
var obfuscator obfs.Obfuscator
if len(option.Obfs) > 0 {
obfuscator = obfs.NewXPlusObfuscator([]byte(option.Obfs))
}
up, down, err := option.Speed()
if err != nil {
return nil, err
}
if option.UpSpeed != 0 {
up = uint64(option.UpSpeed * mbpsToBps)
}
if option.DownSpeed != 0 {
down = uint64(option.DownSpeed * mbpsToBps)
}
client, err := core.NewClient(
addr, ports, option.Protocol, auth, tlsConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
}, obfuscator, hopInterval, option.FastOpen,
)
if err != nil {
return nil, fmt.Errorf("hysteria %s create error: %w", addr, err)
}
return &Hysteria{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Hysteria,
udp: true,
tfo: option.FastOpen,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
client: client,
}, nil
}
type hyPacketConn struct {
core.UDPConn
}
func (c *hyPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
b, addrStr, err := c.UDPConn.ReadFrom()
if err != nil {
return
}
n = copy(p, b)
addr = M.ParseSocksaddr(addrStr).UDPAddr()
return
}
func (c *hyPacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) {
b, addrStr, err := c.UDPConn.ReadFrom()
if err != nil {
return
}
data = b
addr = M.ParseSocksaddr(addrStr).UDPAddr()
return
}
func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
err = c.UDPConn.WriteTo(p, M.SocksaddrFromNet(addr).String())
if err != nil {
return
}
n = len(p)
return
}
type hyDialerWithContext struct {
hyDialer func(network string) (net.PacketConn, error)
ctx context.Context
remoteAddr func(host string) (net.Addr, error)
}
func (h *hyDialerWithContext) ListenPacket(rAddr net.Addr) (net.PacketConn, error) {
network := "udp"
if addrPort, err := netip.ParseAddrPort(rAddr.String()); err == nil {
network = dialer.ParseNetwork(network, addrPort.Addr())
}
return h.hyDialer(network)
}
func (h *hyDialerWithContext) Context() context.Context {
return h.ctx
}
func (h *hyDialerWithContext) RemoteAddr(host string) (net.Addr, error) {
return h.remoteAddr(host)
}

View File

@ -0,0 +1,157 @@
package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"runtime"
"strconv"
CN "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant"
tuicCommon "github.com/Dreamacro/clash/transport/tuic/common"
"github.com/metacubex/sing-quic/hysteria2"
M "github.com/sagernet/sing/common/metadata"
)
func init() {
hysteria2.SetCongestionController = tuicCommon.SetCongestionController
}
type Hysteria2 struct {
*Base
option *Hysteria2Option
client *hysteria2.Client
dialer proxydialer.SingDialer
}
type Hysteria2Option struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Up string `proxy:"up,omitempty"`
Down string `proxy:"down,omitempty"`
Password string `proxy:"password,omitempty"`
Obfs string `proxy:"obfs,omitempty"`
ObfsPassword string `proxy:"obfs-password,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
CustomCA string `proxy:"ca,omitempty"`
CustomCAString string `proxy:"ca-str,omitempty"`
CWND int `proxy:"cwnd,omitempty"`
}
func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := h.Base.DialOptions(opts...)
h.dialer.SetDialer(dialer.NewDialer(options...))
c, err := h.client.DialConn(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil {
return nil, err
}
return NewConn(CN.NewRefConn(c, h), h), nil
}
func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
options := h.Base.DialOptions(opts...)
h.dialer.SetDialer(dialer.NewDialer(options...))
pc, err := h.client.ListenPacket(ctx)
if err != nil {
return nil, err
}
if pc == nil {
return nil, errors.New("packetConn is nil")
}
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(pc), h), h), nil
}
func closeHysteria2(h *Hysteria2) {
if h.client != nil {
_ = h.client.CloseWithError(errors.New("proxy removed"))
}
}
func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
var salamanderPassword string
if len(option.Obfs) > 0 {
if option.ObfsPassword == "" {
return nil, errors.New("missing obfs password")
}
switch option.Obfs {
case hysteria2.ObfsTypeSalamander:
salamanderPassword = option.ObfsPassword
default:
return nil, fmt.Errorf("unknown obfs type: %s", option.Obfs)
}
}
serverName := option.Server
if option.SNI != "" {
serverName = option.SNI
}
tlsConfig := &tls.Config{
ServerName: serverName,
InsecureSkipVerify: option.SkipCertVerify,
MinVersion: tls.VersionTLS13,
}
var err error
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
if err != nil {
return nil, err
}
if len(option.ALPN) > 0 {
tlsConfig.NextProtos = option.ALPN
}
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())
clientOptions := hysteria2.ClientOptions{
Context: context.TODO(),
Dialer: singDialer,
ServerAddress: M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)),
SendBPS: StringToBps(option.Up),
ReceiveBPS: StringToBps(option.Down),
SalamanderPassword: salamanderPassword,
Password: option.Password,
TLSConfig: tlsConfig,
UDPDisabled: false,
CWND: option.CWND,
}
client, err := hysteria2.NewClient(clientOptions)
if err != nil {
return nil, err
}
outbound := &Hysteria2{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Hysteria2,
udp: true,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
client: client,
dialer: singDialer,
}
runtime.SetFinalizer(outbound, closeHysteria2)
return outbound, nil
}

View File

@ -0,0 +1,35 @@
package outbound
import (
"encoding/base64"
"encoding/hex"
"errors"
tlsC "github.com/Dreamacro/clash/component/tls"
"golang.org/x/crypto/curve25519"
)
type RealityOptions struct {
PublicKey string `proxy:"public-key"`
ShortID string `proxy:"short-id"`
}
func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) {
if o.PublicKey != "" {
config := new(tlsC.RealityConfig)
n, err := base64.RawURLEncoding.Decode(config.PublicKey[:], []byte(o.PublicKey))
if err != nil || n != curve25519.ScalarSize {
return nil, errors.New("invalid REALITY public key")
}
n, err = hex.Decode(config.ShortID[:], []byte(o.ShortID))
if err != nil || n > tlsC.RealityMaxShortIDLen {
return nil, errors.New("invalid REALITY short ID")
}
return config, nil
}
return nil, nil
}

102
adapter/outbound/reject.go Normal file
View File

@ -0,0 +1,102 @@
package outbound
import (
"context"
"io"
"net"
"time"
"github.com/Dreamacro/clash/common/buf"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
)
type Reject struct {
*Base
}
type RejectOption struct {
Name string `proxy:"name"`
}
// DialContext implements C.ProxyAdapter
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
return NewConn(nopConn{}, r), nil
}
// ListenPacketContext implements C.ProxyAdapter
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
return newPacketConn(nopPacketConn{}, r), nil
}
func NewRejectWithOption(option RejectOption) *Reject {
return &Reject{
Base: &Base{
name: option.Name,
tp: C.Direct,
udp: true,
},
}
}
func NewReject() *Reject {
return &Reject{
Base: &Base{
name: "REJECT",
tp: C.Reject,
udp: true,
prefer: C.DualStack,
},
}
}
func NewPass() *Reject {
return &Reject{
Base: &Base{
name: "PASS",
tp: C.Pass,
udp: true,
prefer: C.DualStack,
},
}
}
type nopConn struct{}
func (rw nopConn) Read(b []byte) (int, error) {
return 0, io.EOF
}
func (rw nopConn) ReadBuffer(buffer *buf.Buffer) error {
return io.EOF
}
func (rw nopConn) Write(b []byte) (int, error) {
return 0, io.EOF
}
func (rw nopConn) WriteBuffer(buffer *buf.Buffer) error {
return io.EOF
}
func (rw nopConn) Close() error { return nil }
func (rw nopConn) LocalAddr() net.Addr { return nil }
func (rw nopConn) RemoteAddr() net.Addr { return nil }
func (rw nopConn) SetDeadline(time.Time) error { return nil }
func (rw nopConn) SetReadDeadline(time.Time) error { return nil }
func (rw nopConn) SetWriteDeadline(time.Time) error { return nil }
var udpAddrIPv4Unspecified = &net.UDPAddr{IP: net.IPv4zero, Port: 0}
type nopPacketConn struct{}
func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil }
func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF }
func (npc nopPacketConn) WaitReadFrom() ([]byte, func(), net.Addr, error) {
return nil, nil, nil, io.EOF
}
func (npc nopPacketConn) Close() error { return nil }
func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified }
func (npc nopPacketConn) SetDeadline(time.Time) error { return nil }
func (npc nopPacketConn) SetReadDeadline(time.Time) error { return nil }
func (npc nopPacketConn) SetWriteDeadline(time.Time) error { return nil }

View File

@ -0,0 +1,331 @@
package outbound
import (
"context"
"errors"
"fmt"
"net"
"strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/restls"
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
shadowtls "github.com/Dreamacro/clash/transport/sing-shadowtls"
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
restlsC "github.com/3andne/restls-client-go"
shadowsocks "github.com/metacubex/sing-shadowsocks2"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/uot"
)
type ShadowSocks struct {
*Base
method shadowsocks.Method
option *ShadowSocksOption
// obfs
obfsMode string
obfsOption *simpleObfsOption
v2rayOption *v2rayObfs.Option
shadowTLSOption *shadowtls.ShadowTLSOption
restlsConfig *restlsC.Config
}
type ShadowSocksOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"`
Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]any `proxy:"plugin-opts,omitempty"`
UDPOverTCP bool `proxy:"udp-over-tcp,omitempty"`
UDPOverTCPVersion int `proxy:"udp-over-tcp-version,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
}
type simpleObfsOption struct {
Mode string `obfs:"mode,omitempty"`
Host string `obfs:"host,omitempty"`
}
type v2rayObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"`
Fingerprint string `obfs:"fingerprint,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Mux bool `obfs:"mux,omitempty"`
}
type shadowTLSOption struct {
Password string `obfs:"password"`
Host string `obfs:"host"`
Fingerprint string `obfs:"fingerprint,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Version int `obfs:"version,omitempty"`
}
type restlsOption struct {
Password string `obfs:"password"`
Host string `obfs:"host"`
VersionHint string `obfs:"version-hint"`
RestlsScript string `obfs:"restls-script,omitempty"`
}
// StreamConnContext implements C.ProxyAdapter
func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
useEarly := false
switch ss.obfsMode {
case "tls":
c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
case "http":
_, port, _ := net.SplitHostPort(ss.addr)
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
case "websocket":
var err error
c, err = v2rayObfs.NewV2rayObfs(ctx, c, ss.v2rayOption)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
case shadowtls.Mode:
var err error
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
if err != nil {
return nil, err
}
useEarly = true
case restls.Mode:
var err error
c, err = restls.NewRestls(ctx, c, ss.restlsConfig)
if err != nil {
return nil, fmt.Errorf("%s (restls) connect error: %w", ss.addr, err)
}
useEarly = true
}
useEarly = useEarly || N.NeedHandshake(c)
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
uotDestination := uot.RequestDestination(uint8(ss.option.UDPOverTCPVersion))
if useEarly {
return ss.method.DialEarlyConn(c, uotDestination), nil
} else {
return ss.method.DialConn(c, uotDestination)
}
}
if useEarly {
return ss.method.DialEarlyConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)), nil
} else {
return ss.method.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
}
}
// DialContext implements C.ProxyAdapter
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.Base.DialOptions(opts...)...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(ss.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = ss.StreamConnContext(ctx, c, metadata)
return NewConn(c, ss), err
}
// ListenPacketContext implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
return ss.ListenPacketWithDialer(ctx, dialer.NewDialer(ss.Base.DialOptions(opts...)...), metadata)
}
// 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 {
return nil, err
}
return ss.ListenPacketOnStreamConn(ctx, tcpConn, metadata)
}
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer)
if err != nil {
return nil, err
}
pc, err := dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
if err != nil {
return nil, err
}
pc = ss.method.DialPacketConn(N.NewBindPacketConn(pc, addr))
return newPacketConn(pc, ss), nil
}
// SupportWithDialer implements C.ProxyAdapter
func (ss *ShadowSocks) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
if ss.option.UDPOverTCP {
// ss uot use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
destination := M.SocksaddrFromNet(metadata.UDPAddr())
if ss.option.UDPOverTCPVersion == uot.LegacyVersion {
return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), ss), nil
} else {
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), ss), nil
}
}
return nil, C.ErrNotSupport
}
// SupportUOT implements C.ProxyAdapter
func (ss *ShadowSocks) SupportUOT() bool {
return ss.option.UDPOverTCP
}
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
method, err := shadowsocks.CreateMethod(context.Background(), option.Cipher, shadowsocks.MethodOptions{
Password: option.Password,
})
if err != nil {
return nil, fmt.Errorf("ss %s initialize error: %w", addr, err)
}
var v2rayOption *v2rayObfs.Option
var obfsOption *simpleObfsOption
var shadowTLSOpt *shadowtls.ShadowTLSOption
var restlsConfig *restlsC.Config
obfsMode := ""
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
if option.Plugin == "obfs" {
opts := simpleObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize obfs error: %w", addr, err)
}
if opts.Mode != "tls" && opts.Mode != "http" {
return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode)
}
obfsMode = opts.Mode
obfsOption = &opts
} else if option.Plugin == "v2ray-plugin" {
opts := v2rayObfsOption{Host: "bing.com", Mux: true}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", addr, err)
}
if opts.Mode != "websocket" {
return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode)
}
obfsMode = opts.Mode
v2rayOption = &v2rayObfs.Option{
Host: opts.Host,
Path: opts.Path,
Headers: opts.Headers,
Mux: opts.Mux,
}
if opts.TLS {
v2rayOption.TLS = true
v2rayOption.SkipCertVerify = opts.SkipCertVerify
}
} else if option.Plugin == shadowtls.Mode {
obfsMode = shadowtls.Mode
opt := &shadowTLSOption{
Version: 2,
}
if err := decoder.Decode(option.PluginOpts, opt); err != nil {
return nil, fmt.Errorf("ss %s initialize shadow-tls-plugin error: %w", addr, err)
}
shadowTLSOpt = &shadowtls.ShadowTLSOption{
Password: opt.Password,
Host: opt.Host,
Fingerprint: opt.Fingerprint,
ClientFingerprint: option.ClientFingerprint,
SkipCertVerify: opt.SkipCertVerify,
Version: opt.Version,
}
} else if option.Plugin == restls.Mode {
obfsMode = restls.Mode
restlsOpt := &restlsOption{}
if err := decoder.Decode(option.PluginOpts, restlsOpt); err != nil {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
}
restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
if err != nil {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
}
}
switch option.UDPOverTCPVersion {
case uot.Version, uot.LegacyVersion:
case 0:
option.UDPOverTCPVersion = uot.LegacyVersion
default:
return nil, fmt.Errorf("ss %s unknown udp over tcp protocol version: %d", addr, option.UDPOverTCPVersion)
}
return &ShadowSocks{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Shadowsocks,
udp: option.UDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
method: method,
option: &option,
obfsMode: obfsMode,
v2rayOption: v2rayOption,
obfsOption: obfsOption,
shadowTLSOption: shadowTLSOpt,
restlsConfig: restlsConfig,
}, nil
}

View File

@ -0,0 +1,253 @@
package outbound
import (
"context"
"errors"
"fmt"
"net"
"strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/shadowsocks/core"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowstream"
"github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/clash/transport/ssr/obfs"
"github.com/Dreamacro/clash/transport/ssr/protocol"
)
type ShadowSocksR struct {
*Base
option *ShadowSocksROption
cipher core.Cipher
obfs obfs.Obfs
protocol protocol.Protocol
}
type ShadowSocksROption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
Cipher string `proxy:"cipher"`
Obfs string `proxy:"obfs"`
ObfsParam string `proxy:"obfs-param,omitempty"`
Protocol string `proxy:"protocol"`
ProtocolParam string `proxy:"protocol-param,omitempty"`
UDP bool `proxy:"udp,omitempty"`
}
// StreamConnContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c = ssr.obfs.StreamConn(c)
c = ssr.cipher.StreamConn(c)
var (
iv []byte
err error
)
switch conn := c.(type) {
case *shadowstream.Conn:
iv, err = conn.ObtainWriteIV()
if err != nil {
return nil, err
}
case *shadowaead.Conn:
return nil, fmt.Errorf("invalid connection type")
}
c = ssr.protocol.StreamConn(c, iv)
_, err = c.Write(serializesSocksAddr(metadata))
return c, err
}
// DialContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
return ssr.DialContextWithDialer(ctx, dialer.NewDialer(ssr.Base.DialOptions(opts...)...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(ssr.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(ssr.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
}
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = ssr.StreamConnContext(ctx, c, metadata)
return NewConn(c, ssr), err
}
// ListenPacketContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
return ssr.ListenPacketWithDialer(ctx, dialer.NewDialer(ssr.Base.DialOptions(opts...)...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if len(ssr.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(ssr.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ssr.addr, ssr.prefer)
if err != nil {
return nil, err
}
pc, err := dialer.ListenPacket(ctx, "udp", "", addr.AddrPort())
if err != nil {
return nil, err
}
epc := ssr.cipher.PacketConn(N.NewEnhancePacketConn(pc))
epc = ssr.protocol.PacketConn(epc)
return newPacketConn(&ssrPacketConn{EnhancePacketConn: epc, rAddr: addr}, ssr), nil
}
// SupportWithDialer implements C.ProxyAdapter
func (ssr *ShadowSocksR) SupportWithDialer() C.NetWork {
return C.ALLNet
}
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
// SSR protocol compatibility
// https://github.com/Dreamacro/clash/pull/2056
if option.Cipher == "none" {
option.Cipher = "dummy"
}
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
cipher := option.Cipher
password := option.Password
coreCiph, err := core.PickCipher(cipher, nil, password)
if err != nil {
return nil, fmt.Errorf("ssr %s initialize error: %w", addr, err)
}
var (
ivSize int
key []byte
)
if option.Cipher == "dummy" {
ivSize = 0
key = core.Kdf(option.Password, 16)
} else {
ciph, ok := coreCiph.(*core.StreamCipher)
if !ok {
return nil, fmt.Errorf("%s is not none or a supported stream cipher in ssr", cipher)
}
ivSize = ciph.IVSize()
key = ciph.Key
}
obfs, obfsOverhead, err := obfs.PickObfs(option.Obfs, &obfs.Base{
Host: option.Server,
Port: option.Port,
Key: key,
IVSize: ivSize,
Param: option.ObfsParam,
})
if err != nil {
return nil, fmt.Errorf("ssr %s initialize obfs error: %w", addr, err)
}
protocol, err := protocol.PickProtocol(option.Protocol, &protocol.Base{
Key: key,
Overhead: obfsOverhead,
Param: option.ProtocolParam,
})
if err != nil {
return nil, fmt.Errorf("ssr %s initialize protocol error: %w", addr, err)
}
return &ShadowSocksR{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.ShadowsocksR,
udp: option.UDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
cipher: coreCiph,
obfs: obfs,
protocol: protocol,
}, nil
}
type ssrPacketConn struct {
N.EnhancePacketConn
rAddr net.Addr
}
func (spc *ssrPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
if err != nil {
return
}
return spc.EnhancePacketConn.WriteTo(packet[3:], spc.rAddr)
}
func (spc *ssrPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, _, e := spc.EnhancePacketConn.ReadFrom(b)
if e != nil {
return 0, nil, e
}
addr := socks5.SplitAddr(b[:n])
if addr == nil {
return 0, nil, errors.New("parse addr error")
}
udpAddr := addr.UDPAddr()
if udpAddr == nil {
return 0, nil, errors.New("parse addr error")
}
copy(b, b[len(addr):])
return n - len(addr), udpAddr, e
}
func (spc *ssrPacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) {
data, put, _, err = spc.EnhancePacketConn.WaitReadFrom()
if err != nil {
return nil, nil, nil, err
}
_addr := socks5.SplitAddr(data)
if _addr == nil {
if put != nil {
put()
}
return nil, nil, nil, errors.New("parse addr error")
}
addr = _addr.UDPAddr()
if addr == nil {
if put != nil {
put()
}
return nil, nil, nil, errors.New("parse addr error")
}
data = data[len(_addr):]
return
}

118
adapter/outbound/singmux.go Normal file
View File

@ -0,0 +1,118 @@
package outbound
import (
"context"
"errors"
"runtime"
CN "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
mux "github.com/sagernet/sing-mux"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
)
type SingMux struct {
C.ProxyAdapter
base ProxyBase
client *mux.Client
dialer proxydialer.SingDialer
onlyTcp bool
}
type SingMuxOption struct {
Enabled bool `proxy:"enabled,omitempty"`
Protocol string `proxy:"protocol,omitempty"`
MaxConnections int `proxy:"max-connections,omitempty"`
MinStreams int `proxy:"min-streams,omitempty"`
MaxStreams int `proxy:"max-streams,omitempty"`
Padding bool `proxy:"padding,omitempty"`
Statistic bool `proxy:"statistic,omitempty"`
OnlyTcp bool `proxy:"only-tcp,omitempty"`
}
type ProxyBase interface {
DialOptions(opts ...dialer.Option) []dialer.Option
}
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := s.base.DialOptions(opts...)
s.dialer.SetDialer(dialer.NewDialer(options...))
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil {
return nil, err
}
return NewConn(CN.NewRefConn(c, s), s.ProxyAdapter), err
}
func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
if s.onlyTcp {
return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
}
options := s.base.DialOptions(opts...)
s.dialer.SetDialer(dialer.NewDialer(options...))
// sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
pc, err := s.client.ListenPacket(ctx, M.SocksaddrFromNet(metadata.UDPAddr()))
if err != nil {
return nil, err
}
if pc == nil {
return nil, E.New("packetConn is nil")
}
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(pc), s), s.ProxyAdapter), nil
}
func (s *SingMux) SupportUDP() bool {
if s.onlyTcp {
return s.ProxyAdapter.SupportUDP()
}
return true
}
func (s *SingMux) SupportUOT() bool {
if s.onlyTcp {
return s.ProxyAdapter.SupportUOT()
}
return true
}
func closeSingMux(s *SingMux) {
_ = s.client.Close()
}
func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.ProxyAdapter, error) {
singDialer := proxydialer.NewSingDialer(proxy, dialer.NewDialer(), option.Statistic)
client, err := mux.NewClient(mux.Options{
Dialer: singDialer,
Protocol: option.Protocol,
MaxConnections: option.MaxConnections,
MinStreams: option.MinStreams,
MaxStreams: option.MaxStreams,
Padding: option.Padding,
})
if err != nil {
return nil, err
}
outbound := &SingMux{
ProxyAdapter: proxy,
base: base,
client: client,
dialer: singDialer,
onlyTcp: option.OnlyTcp,
}
runtime.SetFinalizer(outbound, closeSingMux)
return outbound, nil
}

216
adapter/outbound/snell.go Normal file
View File

@ -0,0 +1,216 @@
package outbound
import (
"context"
"fmt"
"net"
"strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant"
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
"github.com/Dreamacro/clash/transport/snell"
)
type Snell struct {
*Base
option *SnellOption
psk []byte
pool *snell.Pool
obfsOption *simpleObfsOption
version int
}
type SnellOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Psk string `proxy:"psk"`
UDP bool `proxy:"udp,omitempty"`
Version int `proxy:"version,omitempty"`
ObfsOpts map[string]any `proxy:"obfs-opts,omitempty"`
}
type streamOption struct {
psk []byte
version int
addr string
obfsOption *simpleObfsOption
}
func streamConn(c net.Conn, option streamOption) *snell.Snell {
switch option.obfsOption.Mode {
case "tls":
c = obfs.NewTLSObfs(c, option.obfsOption.Host)
case "http":
_, port, _ := net.SplitHostPort(option.addr)
c = obfs.NewHTTPObfs(c, option.obfsOption.Host, port)
}
return snell.StreamConn(c, option.psk, option.version)
}
// StreamConnContext implements C.ProxyAdapter
func (s *Snell) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
if metadata.NetWork == C.UDP {
err := snell.WriteUDPHeader(c, s.version)
return c, err
}
err := snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version)
return c, err
}
// DialContext implements C.ProxyAdapter
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
if s.version == snell.Version2 && len(opts) == 0 {
c, err := s.pool.Get()
if err != nil {
return nil, err
}
if err = snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version); err != nil {
c.Close()
return nil, err
}
return NewConn(c, s), err
}
return s.DialContextWithDialer(ctx, dialer.NewDialer(s.Base.DialOptions(opts...)...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(s.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(s.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", s.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
}
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = s.StreamConnContext(ctx, c, metadata)
return NewConn(c, s), err
}
// ListenPacketContext implements C.ProxyAdapter
func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
return s.ListenPacketWithDialer(ctx, dialer.NewDialer(s.Base.DialOptions(opts...)...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.PacketConn, error) {
var err error
if len(s.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(s.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", s.addr)
if err != nil {
return nil, err
}
N.TCPKeepAlive(c)
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
err = snell.WriteUDPHeader(c, s.version)
if err != nil {
return nil, err
}
pc := snell.PacketConn(c)
return newPacketConn(pc, s), nil
}
// SupportWithDialer implements C.ProxyAdapter
func (s *Snell) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// SupportUOT implements C.ProxyAdapter
func (s *Snell) SupportUOT() bool {
return true
}
func NewSnell(option SnellOption) (*Snell, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
psk := []byte(option.Psk)
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
obfsOption := &simpleObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil {
return nil, fmt.Errorf("snell %s initialize obfs error: %w", addr, err)
}
switch obfsOption.Mode {
case "tls", "http", "":
break
default:
return nil, fmt.Errorf("snell %s obfs mode error: %s", addr, obfsOption.Mode)
}
// backward compatible
if option.Version == 0 {
option.Version = snell.DefaultSnellVersion
}
switch option.Version {
case snell.Version1, snell.Version2:
if option.UDP {
return nil, fmt.Errorf("snell version %d not support UDP", option.Version)
}
case snell.Version3:
default:
return nil, fmt.Errorf("snell version error: %d", option.Version)
}
s := &Snell{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Snell,
udp: option.UDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
psk: psk,
obfsOption: obfsOption,
version: option.Version,
}
if option.Version == snell.Version2 {
s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) {
var err error
var cDialer C.Dialer = dialer.NewDialer(s.Base.DialOptions()...)
if len(s.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(s.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(ctx, "tcp", addr)
if err != nil {
return nil, err
}
N.TCPKeepAlive(c)
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
})
}
return s, nil
}

250
adapter/outbound/socks5.go Normal file
View File

@ -0,0 +1,250 @@
package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"net/netip"
"strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
)
type Socks5 struct {
*Base
option *Socks5Option
user string
pass string
tls bool
skipCertVerify bool
tlsConfig *tls.Config
}
type Socks5Option struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UserName string `proxy:"username,omitempty"`
Password string `proxy:"password,omitempty"`
TLS bool `proxy:"tls,omitempty"`
UDP bool `proxy:"udp,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
}
// StreamConnContext implements C.ProxyAdapter
func (ss *Socks5) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if ss.tls {
cc := tls.Client(c, ss.tlsConfig)
err := cc.HandshakeContext(ctx)
c = cc
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
}
var user *socks5.User
if ss.user != "" {
user = &socks5.User{
Username: ss.user,
Password: ss.pass,
}
}
if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
return nil, err
}
return c, nil
}
// DialContext implements C.ProxyAdapter
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.Base.DialOptions(opts...)...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(ss.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = ss.StreamConnContext(ctx, c, metadata)
if err != nil {
return nil, err
}
return NewConn(c, ss), nil
}
// SupportWithDialer implements C.ProxyAdapter
func (ss *Socks5) SupportWithDialer() C.NetWork {
return C.TCP
}
// ListenPacketContext implements C.ProxyAdapter
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
var cDialer C.Dialer = dialer.NewDialer(ss.Base.DialOptions(opts...)...)
if len(ss.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(ss.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(ctx, "tcp", ss.addr)
if err != nil {
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
return
}
if ss.tls {
cc := tls.Client(c, ss.tlsConfig)
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
defer cancel()
err = cc.HandshakeContext(ctx)
c = cc
}
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
N.TCPKeepAlive(c)
var user *socks5.User
if ss.user != "" {
user = &socks5.User{
Username: ss.user,
Password: ss.pass,
}
}
udpAssocateAddr := socks5.AddrFromStdAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
bindAddr, err := socks5.ClientHandshake(c, udpAssocateAddr, socks5.CmdUDPAssociate, user)
if err != nil {
err = fmt.Errorf("client hanshake error: %w", err)
return
}
// Support unspecified UDP bind address.
bindUDPAddr := bindAddr.UDPAddr()
if bindUDPAddr == nil {
err = errors.New("invalid UDP bind address")
return
} else if bindUDPAddr.IP.IsUnspecified() {
serverAddr, err := resolveUDPAddr(ctx, "udp", ss.Addr())
if err != nil {
return nil, err
}
bindUDPAddr.IP = serverAddr.IP
}
pc, err := cDialer.ListenPacket(ctx, "udp", "", bindUDPAddr.AddrPort())
if err != nil {
return
}
go func() {
io.Copy(io.Discard, c)
c.Close()
// A UDP association terminates when the TCP connection that the UDP
// ASSOCIATE request arrived on terminates. RFC1928
pc.Close()
}()
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil
}
func NewSocks5(option Socks5Option) (*Socks5, error) {
var tlsConfig *tls.Config
if option.TLS {
tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ServerName: option.Server,
}
var err error
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
if err != nil {
return nil, err
}
}
return &Socks5{
Base: &Base{
name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Socks5,
udp: option.UDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
user: option.UserName,
pass: option.Password,
tls: option.TLS,
skipCertVerify: option.SkipCertVerify,
tlsConfig: tlsConfig,
}, nil
}
type socksPacketConn struct {
net.PacketConn
rAddr net.Addr
tcpConn net.Conn
}
func (uc *socksPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
if err != nil {
return
}
return uc.PacketConn.WriteTo(packet, uc.rAddr)
}
func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, _, e := uc.PacketConn.ReadFrom(b)
if e != nil {
return 0, nil, e
}
addr, payload, err := socks5.DecodeUDPPacket(b)
if err != nil {
return 0, nil, err
}
udpAddr := addr.UDPAddr()
if udpAddr == nil {
return 0, nil, errors.New("parse udp addr error")
}
// due to DecodeUDPPacket is mutable, record addr length
copy(b, payload)
return n - len(addr) - 3, udpAddr, nil
}
func (uc *socksPacketConn) Close() error {
uc.tcpConn.Close()
return uc.PacketConn.Close()
}

300
adapter/outbound/trojan.go Normal file
View File

@ -0,0 +1,300 @@
package outbound
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/trojan"
)
type Trojan struct {
*Base
instance *trojan.Trojan
option *TrojanOption
// for gun mux
gunTLSConfig *tls.Config
gunConfig *gun.Config
transport *gun.TransportWrap
realityConfig *tlsC.RealityConfig
}
type TrojanOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
ALPN []string `proxy:"alpn,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"`
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,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)
wsOpts := &trojan.WebsocketOption{
Host: host,
Port: port,
Path: t.option.WSOpts.Path,
}
if t.option.SNI != "" {
wsOpts.Host = t.option.SNI
}
if len(t.option.WSOpts.Headers) != 0 {
header := http.Header{}
for key, value := range t.option.WSOpts.Headers {
header.Add(key, value)
}
wsOpts.Headers = header
}
return t.instance.StreamWebsocketConn(ctx, c, wsOpts)
}
return t.instance.StreamConn(ctx, c)
}
// StreamConnContext implements C.ProxyAdapter
func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
if tlsC.HaveGlobalFingerprint() && len(t.option.ClientFingerprint) == 0 {
t.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
}
if t.transport != nil {
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.realityConfig)
} else {
c, err = t.plainStream(ctx, c)
}
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
if metadata.NetWork == C.UDP {
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
return c, err
}
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
return c, err
}
// DialContext implements C.ProxyAdapter
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
// gun transport
if t.transport != nil && len(opts) == 0 {
c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig)
if err != nil {
return nil, err
}
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
c.Close()
return nil, err
}
return NewConn(c, t), nil
}
return t.DialContextWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(t.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", t.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = t.StreamConnContext(ctx, c, metadata)
if err != nil {
return nil, err
}
return NewConn(c, t), err
}
// ListenPacketContext implements C.ProxyAdapter
func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
var c net.Conn
// grpc transport
if t.transport != nil && len(opts) == 0 {
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
if err != nil {
return nil, err
}
pc := t.instance.PacketConn(c)
return newPacketConn(pc, t), err
}
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if len(t.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", t.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
N.TCPKeepAlive(c)
c, err = t.plainStream(ctx, c)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
if err != nil {
return nil, err
}
pc := t.instance.PacketConn(c)
return newPacketConn(pc, t), err
}
// SupportWithDialer implements C.ProxyAdapter
func (t *Trojan) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (t *Trojan) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
pc := t.instance.PacketConn(c)
return newPacketConn(pc, t), err
}
// SupportUOT implements C.ProxyAdapter
func (t *Trojan) SupportUOT() bool {
return true
}
func NewTrojan(option TrojanOption) (*Trojan, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
tOption := &trojan.Option{
Password: option.Password,
ALPN: option.ALPN,
ServerName: option.Server,
SkipCertVerify: option.SkipCertVerify,
Fingerprint: option.Fingerprint,
ClientFingerprint: option.ClientFingerprint,
}
if option.SNI != "" {
tOption.ServerName = option.SNI
}
t := &Trojan{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Trojan,
udp: option.UDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
instance: trojan.New(tOption),
option: &option,
}
var err error
t.realityConfig, err = option.RealityOpts.Parse()
if err != nil {
return nil, err
}
tOption.Reality = t.realityConfig
if option.Network == "grpc" {
dialFn := func(network, addr string) (net.Conn, error) {
var err error
var cDialer C.Dialer = dialer.NewDialer(t.Base.DialOptions()...)
if len(t.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(t.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(context.Background(), "tcp", t.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
}
N.TCPKeepAlive(c)
return c, nil
}
tlsConfig := &tls.Config{
NextProtos: option.ALPN,
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: tOption.SkipCertVerify,
ServerName: tOption.ServerName,
}
var err error
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint)
if err != nil {
return nil, err
}
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint, t.realityConfig)
t.gunTLSConfig = tlsConfig
t.gunConfig = &gun.Config{
ServiceName: option.GrpcOpts.GrpcServiceName,
Host: tOption.ServerName,
}
}
return t, nil
}

316
adapter/outbound/tuic.go Normal file
View File

@ -0,0 +1,316 @@
package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"math"
"net"
"strconv"
"time"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/tuic"
"github.com/gofrs/uuid/v5"
"github.com/metacubex/quic-go"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/uot"
)
type Tuic struct {
*Base
option *TuicOption
client *tuic.PoolClient
}
type TuicOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Token string `proxy:"token,omitempty"`
UUID string `proxy:"uuid,omitempty"`
Password string `proxy:"password,omitempty"`
Ip string `proxy:"ip,omitempty"`
HeartbeatInterval int `proxy:"heartbeat-interval,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
ReduceRtt bool `proxy:"reduce-rtt,omitempty"`
RequestTimeout int `proxy:"request-timeout,omitempty"`
UdpRelayMode string `proxy:"udp-relay-mode,omitempty"`
CongestionController string `proxy:"congestion-controller,omitempty"`
DisableSni bool `proxy:"disable-sni,omitempty"`
MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"`
FastOpen bool `proxy:"fast-open,omitempty"`
MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
CWND int `proxy:"cwnd,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
CustomCA string `proxy:"ca,omitempty"`
CustomCAString string `proxy:"ca-str,omitempty"`
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
ReceiveWindow int `proxy:"recv-window,omitempty"`
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
SNI string `proxy:"sni,omitempty"`
UDPOverStream bool `proxy:"udp-over-stream,omitempty"`
UDPOverStreamVersion int `proxy:"udp-over-stream-version,omitempty"`
}
// DialContext implements C.ProxyAdapter
func (t *Tuic) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
return t.DialContextWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
func (t *Tuic) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.Conn, error) {
conn, err := t.client.DialContextWithDialer(ctx, metadata, dialer, t.dialWithDialer)
if err != nil {
return nil, err
}
return NewConn(conn, t), err
}
// ListenPacketContext implements C.ProxyAdapter
func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if t.option.UDPOverStream {
uotDestination := uot.RequestDestination(uint8(t.option.UDPOverStreamVersion))
uotMetadata := *metadata
uotMetadata.Host = uotDestination.Fqdn
uotMetadata.DstPort = uotDestination.Port
c, err := t.DialContextWithDialer(ctx, dialer, &uotMetadata)
if err != nil {
return nil, err
}
// tuic uos use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
destination := M.SocksaddrFromNet(metadata.UDPAddr())
if t.option.UDPOverStreamVersion == uot.LegacyVersion {
return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), t), nil
} else {
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), t), nil
}
}
pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer)
if err != nil {
return nil, err
}
return newPacketConn(pc, t), nil
}
// SupportWithDialer implements C.ProxyAdapter
func (t *Tuic) SupportWithDialer() C.NetWork {
return C.ALLNet
}
func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error) {
if len(t.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
if err != nil {
return nil, nil, err
}
}
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", t.addr, t.prefer)
if err != nil {
return nil, nil, err
}
addr = udpAddr
var pc net.PacketConn
pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
if err != nil {
return nil, nil, err
}
transport = &quic.Transport{Conn: pc}
transport.SetCreatedConn(true) // auto close conn
transport.SetSingleUse(true) // auto close transport
return
}
func NewTuic(option TuicOption) (*Tuic, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
serverName := option.Server
tlsConfig := &tls.Config{
ServerName: serverName,
InsecureSkipVerify: option.SkipCertVerify,
MinVersion: tls.VersionTLS13,
}
if option.SNI != "" {
tlsConfig.ServerName = option.SNI
}
var err error
tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString)
if err != nil {
return nil, err
}
if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
tlsConfig.NextProtos = option.ALPN
} else {
tlsConfig.NextProtos = []string{"h3"}
}
if option.RequestTimeout == 0 {
option.RequestTimeout = 8000
}
if option.HeartbeatInterval <= 0 {
option.HeartbeatInterval = 10000
}
udpRelayMode := tuic.QUIC
if option.UdpRelayMode != "quic" {
udpRelayMode = tuic.NATIVE
}
if option.MaxUdpRelayPacketSize == 0 {
option.MaxUdpRelayPacketSize = 1252
}
if option.MaxOpenStreams == 0 {
option.MaxOpenStreams = 100
}
if option.CWND == 0 {
option.CWND = 32
}
packetOverHead := tuic.PacketOverHeadV4
if len(option.Token) == 0 {
packetOverHead = tuic.PacketOverHeadV5
}
if option.MaxDatagramFrameSize == 0 {
option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + packetOverHead
}
if option.MaxDatagramFrameSize > 1400 {
option.MaxDatagramFrameSize = 1400
}
option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - packetOverHead
// ensure server's incoming stream can handle correctly, increase to 1.1x
quicMaxOpenStreams := int64(option.MaxOpenStreams)
quicMaxOpenStreams = quicMaxOpenStreams + int64(math.Ceil(float64(quicMaxOpenStreams)/10.0))
quicConfig := &quic.Config{
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
InitialConnectionReceiveWindow: uint64(option.ReceiveWindow),
MaxConnectionReceiveWindow: uint64(option.ReceiveWindow),
MaxIncomingStreams: quicMaxOpenStreams,
MaxIncomingUniStreams: quicMaxOpenStreams,
KeepAlivePeriod: time.Duration(option.HeartbeatInterval) * time.Millisecond,
DisablePathMTUDiscovery: option.DisableMTUDiscovery,
MaxDatagramFrameSize: int64(option.MaxDatagramFrameSize),
EnableDatagrams: true,
}
if option.ReceiveWindowConn == 0 {
quicConfig.InitialStreamReceiveWindow = tuic.DefaultStreamReceiveWindow / 10
quicConfig.MaxStreamReceiveWindow = tuic.DefaultStreamReceiveWindow
}
if option.ReceiveWindow == 0 {
quicConfig.InitialConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow / 10
quicConfig.MaxConnectionReceiveWindow = tuic.DefaultConnectionReceiveWindow
}
if len(option.Ip) > 0 {
addr = net.JoinHostPort(option.Ip, strconv.Itoa(option.Port))
}
if option.DisableSni {
tlsConfig.ServerName = ""
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
}
switch option.UDPOverStreamVersion {
case uot.Version, uot.LegacyVersion:
case 0:
option.UDPOverStreamVersion = uot.LegacyVersion
default:
return nil, fmt.Errorf("tuic %s unknown udp over stream protocol version: %d", addr, option.UDPOverStreamVersion)
}
t := &Tuic{
Base: &Base{
name: option.Name,
addr: addr,
tp: C.Tuic,
udp: true,
tfo: option.FastOpen,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
option: &option,
}
clientMaxOpenStreams := int64(option.MaxOpenStreams)
// to avoid tuic's "too many open streams", decrease to 0.9x
if clientMaxOpenStreams == 100 {
clientMaxOpenStreams = clientMaxOpenStreams - int64(math.Ceil(float64(clientMaxOpenStreams)/10.0))
}
if clientMaxOpenStreams < 1 {
clientMaxOpenStreams = 1
}
if len(option.Token) > 0 {
tkn := tuic.GenTKN(option.Token)
clientOption := &tuic.ClientOptionV4{
TlsConfig: tlsConfig,
QuicConfig: quicConfig,
Token: tkn,
UdpRelayMode: udpRelayMode,
CongestionController: option.CongestionController,
ReduceRtt: option.ReduceRtt,
RequestTimeout: time.Duration(option.RequestTimeout) * time.Millisecond,
MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize,
FastOpen: option.FastOpen,
MaxOpenStreams: clientMaxOpenStreams,
CWND: option.CWND,
}
t.client = tuic.NewPoolClientV4(clientOption)
} else {
maxUdpRelayPacketSize := option.MaxUdpRelayPacketSize
if maxUdpRelayPacketSize > tuic.MaxFragSizeV5 {
maxUdpRelayPacketSize = tuic.MaxFragSizeV5
}
clientOption := &tuic.ClientOptionV5{
TlsConfig: tlsConfig,
QuicConfig: quicConfig,
Uuid: uuid.FromStringOrNil(option.UUID),
Password: option.Password,
UdpRelayMode: udpRelayMode,
CongestionController: option.CongestionController,
ReduceRtt: option.ReduceRtt,
MaxUdpRelayPacketSize: maxUdpRelayPacketSize,
MaxOpenStreams: clientMaxOpenStreams,
CWND: option.CWND,
}
t.client = tuic.NewPoolClientV5(clientOption)
}
return t, nil
}

163
adapter/outbound/util.go Normal file
View File

@ -0,0 +1,163 @@
package outbound
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"net"
"net/netip"
"regexp"
"strconv"
"sync"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5"
)
var (
globalClientSessionCache tls.ClientSessionCache
once sync.Once
)
func getClientSessionCache() tls.ClientSessionCache {
once.Do(func() {
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
})
return globalClientSessionCache
}
func serializesSocksAddr(metadata *C.Metadata) []byte {
var buf [][]byte
addrType := metadata.AddrType()
aType := uint8(addrType)
p := uint(metadata.DstPort)
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
switch addrType {
case socks5.AtypDomainName:
lenM := uint8(len(metadata.Host))
host := []byte(metadata.Host)
buf = [][]byte{{aType, lenM}, host, port}
case socks5.AtypIPv4:
host := metadata.DstIP.AsSlice()
buf = [][]byte{{aType}, host, port}
case socks5.AtypIPv6:
host := metadata.DstIP.AsSlice()
buf = [][]byte{{aType}, host, port}
}
return bytes.Join(buf, nil)
}
func resolveUDPAddr(ctx context.Context, network, address string) (*net.UDPAddr, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
ip, err := resolver.ResolveProxyServerHost(ctx, host)
if err != nil {
return nil, err
}
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
}
func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, prefer C.DNSPrefer) (*net.UDPAddr, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
var ip netip.Addr
var fallback netip.Addr
switch prefer {
case C.IPv4Only:
ip, err = resolver.ResolveIPv4ProxyServerHost(ctx, host)
case C.IPv6Only:
ip, err = resolver.ResolveIPv6ProxyServerHost(ctx, host)
case C.IPv6Prefer:
var ips []netip.Addr
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
if err == nil {
for _, addr := range ips {
if addr.Is6() {
ip = addr
break
} else {
if !fallback.IsValid() {
fallback = addr
}
}
}
}
default:
// C.IPv4Prefer, C.DualStack and other
var ips []netip.Addr
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
if err == nil {
for _, addr := range ips {
if addr.Is4() {
ip = addr
break
} else {
if !fallback.IsValid() {
fallback = addr
}
}
}
}
}
if !ip.IsValid() && fallback.IsValid() {
ip = fallback
}
if err != nil {
return nil, err
}
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
}
func safeConnClose(c net.Conn, err error) {
if err != nil && c != nil {
_ = c.Close()
}
}
var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
func StringToBps(s string) uint64 {
if s == "" {
return 0
}
// when have not unit, use Mbps
if v, err := strconv.Atoi(s); err == nil {
return StringToBps(fmt.Sprintf("%d Mbps", v))
}
m := rateStringRegexp.FindStringSubmatch(s)
if m == nil {
return 0
}
var n uint64
switch m[2] {
case "K":
n = 1 << 10
case "M":
n = 1 << 20
case "G":
n = 1 << 30
case "T":
n = 1 << 40
default:
n = 1
}
v, _ := strconv.ParseUint(m[1], 10, 64)
n = v * n
if m[3] == "b" {
// Bits, need to convert to bytes
n = n >> 3
}
return n
}

609
adapter/outbound/vless.go Normal file
View File

@ -0,0 +1,609 @@
package outbound
import (
"context"
"crypto/tls"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"net/http"
"strconv"
"sync"
"github.com/Dreamacro/clash/common/convert"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/clash/transport/vless"
"github.com/Dreamacro/clash/transport/vmess"
vmessSing "github.com/metacubex/sing-vmess"
"github.com/metacubex/sing-vmess/packetaddr"
M "github.com/sagernet/sing/common/metadata"
)
const (
// max packet length
maxLength = 1024 << 3
)
type Vless struct {
*Base
client *vless.Client
option *VlessOption
// for gun mux
gunTLSConfig *tls.Config
gunConfig *gun.Config
transport *gun.TransportWrap
realityConfig *tlsC.RealityConfig
}
type VlessOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UUID string `proxy:"uuid"`
Flow string `proxy:"flow,omitempty"`
TLS bool `proxy:"tls,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
UDP bool `proxy:"udp,omitempty"`
PacketAddr bool `proxy:"packet-addr,omitempty"`
XUDP bool `proxy:"xudp,omitempty"`
PacketEncoding string `proxy:"packet-encoding,omitempty"`
Network string `proxy:"network,omitempty"`
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
WSPath string `proxy:"ws-path,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
ServerName string `proxy:"servername,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
}
func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
if tlsC.HaveGlobalFingerprint() && len(v.option.ClientFingerprint) == 0 {
v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
}
switch v.option.Network {
case "ws":
host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &vmess.WebsocketConfig{
Host: host,
Port: port,
Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
ClientFingerprint: v.option.ClientFingerprint,
Headers: http.Header{},
}
if len(v.option.WSOpts.Headers) != 0 {
for key, value := range v.option.WSOpts.Headers {
wsOpts.Headers.Add(key, value)
}
}
if v.option.TLS {
wsOpts.TLS = true
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: host,
InsecureSkipVerify: v.option.SkipCertVerify,
NextProtos: []string{"http/1.1"},
}
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
if err != nil {
return nil, err
}
if v.option.ServerName != "" {
wsOpts.TLSConfig.ServerName = v.option.ServerName
} else if host := wsOpts.Headers.Get("Host"); host != "" {
wsOpts.TLSConfig.ServerName = host
}
} else {
if host := wsOpts.Headers.Get("Host"); host == "" {
wsOpts.Headers.Set("Host", convert.RandHost())
convert.SetUserAgent(wsOpts.Headers)
}
}
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
case "http":
// readability first, so just copy default TLS logic
c, err = v.streamTLSConn(ctx, c, false)
if err != nil {
return nil, err
}
host, _, _ := net.SplitHostPort(v.addr)
httpOpts := &vmess.HTTPConfig{
Host: host,
Method: v.option.HTTPOpts.Method,
Path: v.option.HTTPOpts.Path,
Headers: v.option.HTTPOpts.Headers,
}
c = vmess.StreamHTTPConn(c, httpOpts)
case "h2":
c, err = v.streamTLSConn(ctx, c, true)
if err != nil {
return nil, err
}
h2Opts := &vmess.H2Config{
Hosts: v.option.HTTP2Opts.Host,
Path: v.option.HTTP2Opts.Path,
}
c, err = vmess.StreamH2Conn(c, h2Opts)
case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
default:
// default tcp network
// handle TLS
c, err = v.streamTLSConn(ctx, c, false)
}
if err != nil {
return nil, err
}
return v.streamConn(c, metadata)
}
func (v *Vless) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
if metadata.NetWork == C.UDP {
if v.option.PacketAddr {
metadata = &C.Metadata{
NetWork: C.UDP,
Host: packetaddr.SeqPacketMagicAddress,
DstPort: 443,
}
} else {
metadata = &C.Metadata{ // a clear metadata only contains ip
NetWork: C.UDP,
DstIP: metadata.DstIP,
DstPort: metadata.DstPort,
}
}
conn, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
if v.option.PacketAddr {
conn = packetaddr.NewBindConn(conn)
}
} else {
conn, err = v.client.StreamConn(c, parseVlessAddr(metadata, false))
}
if err != nil {
conn = nil
}
return
}
func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (net.Conn, error) {
if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := vmess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint,
ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig,
NextProtos: v.option.ALPN,
}
if isH2 {
tlsOpts.NextProtos = []string{"h2"}
}
if v.option.ServerName != "" {
tlsOpts.Host = v.option.ServerName
}
return vmess.StreamTLSConn(ctx, conn, &tlsOpts)
}
return conn, nil
}
// DialContext implements C.ProxyAdapter
func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
// gun transport
if v.transport != nil && len(opts) == 0 {
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
if err != nil {
return nil, err
}
return NewConn(c, v), nil
}
return v.DialContextWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(v.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = v.StreamConnContext(ctx, c, metadata)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
return NewConn(c, v), err
}
// ListenPacketContext implements C.ProxyAdapter
func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
var c net.Conn
// gun transport
if v.transport != nil && len(opts) == 0 {
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = v.streamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("new vless client error: %v", err)
}
return v.ListenPacketOnStreamConn(ctx, c, metadata)
}
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if len(v.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = v.StreamConnContext(ctx, c, metadata)
if err != nil {
return nil, fmt.Errorf("new vless client error: %v", err)
}
return v.ListenPacketOnStreamConn(ctx, c, metadata)
}
// SupportWithDialer implements C.ProxyAdapter
func (v *Vless) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
if v.option.XUDP {
var globalID [8]byte
if metadata.SourceValid() {
globalID = utils.GlobalID(metadata.SourceAddress())
}
return newPacketConn(N.NewThreadSafePacketConn(
vmessSing.NewXUDPConn(c,
globalID,
M.SocksaddrFromNet(metadata.UDPAddr())),
), v), nil
} else if v.option.PacketAddr {
return newPacketConn(N.NewThreadSafePacketConn(
packetaddr.NewConn(&vlessPacketConn{
Conn: c, rAddr: metadata.UDPAddr(),
}, M.SocksaddrFromNet(metadata.UDPAddr())),
), v), nil
}
return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
}
// SupportUOT implements C.ProxyAdapter
func (v *Vless) SupportUOT() bool {
return true
}
func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
var addrType byte
var addr []byte
switch metadata.AddrType() {
case socks5.AtypIPv4:
addrType = vless.AtypIPv4
addr = make([]byte, net.IPv4len)
copy(addr[:], metadata.DstIP.AsSlice())
case socks5.AtypIPv6:
addrType = vless.AtypIPv6
addr = make([]byte, net.IPv6len)
copy(addr[:], metadata.DstIP.AsSlice())
case socks5.AtypDomainName:
addrType = vless.AtypDomainName
addr = make([]byte, len(metadata.Host)+1)
addr[0] = byte(len(metadata.Host))
copy(addr[1:], metadata.Host)
}
return &vless.DstAddr{
UDP: metadata.NetWork == C.UDP,
AddrType: addrType,
Addr: addr,
Port: metadata.DstPort,
Mux: metadata.NetWork == C.UDP && xudp,
}
}
type vlessPacketConn struct {
net.Conn
rAddr net.Addr
remain int
mux sync.Mutex
cache [2]byte
}
func (c *vlessPacketConn) writePacket(payload []byte) (int, error) {
binary.BigEndian.PutUint16(c.cache[:], uint16(len(payload)))
if _, err := c.Conn.Write(c.cache[:]); err != nil {
return 0, err
}
return c.Conn.Write(payload)
}
func (c *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
total := len(b)
if total == 0 {
return 0, nil
}
if total <= maxLength {
return c.writePacket(b)
}
offset := 0
for offset < total {
cursor := offset + maxLength
if cursor > total {
cursor = total
}
n, err := c.writePacket(b[offset:cursor])
if err != nil {
return offset + n, err
}
offset = cursor
if offset == total {
break
}
}
return total, nil
}
func (c *vlessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
c.mux.Lock()
defer c.mux.Unlock()
if c.remain > 0 {
length := len(b)
if c.remain < length {
length = c.remain
}
n, err := c.Conn.Read(b[:length])
if err != nil {
return 0, c.rAddr, err
}
c.remain -= n
return n, c.rAddr, nil
}
if _, err := c.Conn.Read(b[:2]); err != nil {
return 0, c.rAddr, err
}
total := int(binary.BigEndian.Uint16(b[:2]))
if total == 0 {
return 0, c.rAddr, nil
}
length := len(b)
if length > total {
length = total
}
if _, err := io.ReadFull(c.Conn, b[:length]); err != nil {
return 0, c.rAddr, errors.New("read packet error")
}
c.remain = total - length
return length, c.rAddr, nil
}
func NewVless(option VlessOption) (*Vless, error) {
var addons *vless.Addons
if option.Network != "ws" && len(option.Flow) >= 16 {
option.Flow = option.Flow[:16]
switch option.Flow {
case vless.XRV:
log.Warnln("To use %s, ensure your server is upgrade to Xray-core v1.8.0+", vless.XRV)
addons = &vless.Addons{
Flow: option.Flow,
}
case vless.XRO, vless.XRD, vless.XRS:
log.Fatalln("Legacy XTLS protocol %s is deprecated and no longer supported", option.Flow)
default:
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
}
}
switch option.PacketEncoding {
case "packetaddr", "packet":
option.PacketAddr = true
option.XUDP = false
default: // https://github.com/XTLS/Xray-core/pull/1567#issuecomment-1407305458
if !option.PacketAddr {
option.XUDP = true
}
}
if option.XUDP {
option.PacketAddr = false
}
client, err := vless.NewClient(option.UUID, addons)
if err != nil {
return nil, err
}
v := &Vless{
Base: &Base{
name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Vless,
udp: option.UDP,
xudp: option.XUDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
client: client,
option: &option,
}
v.realityConfig, err = v.option.RealityOpts.Parse()
if err != nil {
return nil, err
}
switch option.Network {
case "h2":
if len(option.HTTP2Opts.Host) == 0 {
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
}
case "grpc":
dialFn := func(network, addr string) (net.Conn, error) {
var err error
var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
if len(v.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(v.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(context.Background(), "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
N.TCPKeepAlive(c)
return c, nil
}
gunConfig := &gun.Config{
ServiceName: v.option.GrpcOpts.GrpcServiceName,
Host: v.option.ServerName,
ClientFingerprint: v.option.ClientFingerprint,
}
if option.ServerName == "" {
gunConfig.Host = v.addr
}
var tlsConfig *tls.Config
if option.TLS {
tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify,
ServerName: v.option.ServerName,
})
if option.ServerName == "" {
host, _, _ := net.SplitHostPort(v.addr)
tlsConfig.ServerName = host
}
}
v.gunTLSConfig = tlsConfig
v.gunConfig = gunConfig
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
}
return v, nil
}

530
adapter/outbound/vmess.go Normal file
View File

@ -0,0 +1,530 @@
package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"sync"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/ca"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/ntp"
"github.com/Dreamacro/clash/transport/gun"
clashVMess "github.com/Dreamacro/clash/transport/vmess"
vmess "github.com/metacubex/sing-vmess"
"github.com/metacubex/sing-vmess/packetaddr"
M "github.com/sagernet/sing/common/metadata"
)
var ErrUDPRemoteAddrMismatch = errors.New("udp packet dropped due to mismatched remote address")
type Vmess struct {
*Base
client *vmess.Client
option *VmessOption
// for gun mux
gunTLSConfig *tls.Config
gunConfig *gun.Config
transport *gun.TransportWrap
realityConfig *tlsC.RealityConfig
}
type VmessOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UUID string `proxy:"uuid"`
AlterID int `proxy:"alterId"`
Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"`
TLS bool `proxy:"tls,omitempty"`
ALPN []string `proxy:"alpn,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
ServerName string `proxy:"servername,omitempty"`
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
PacketAddr bool `proxy:"packet-addr,omitempty"`
XUDP bool `proxy:"xudp,omitempty"`
PacketEncoding string `proxy:"packet-encoding,omitempty"`
GlobalPadding bool `proxy:"global-padding,omitempty"`
AuthenticatedLength bool `proxy:"authenticated-length,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
}
type HTTPOptions struct {
Method string `proxy:"method,omitempty"`
Path []string `proxy:"path,omitempty"`
Headers map[string][]string `proxy:"headers,omitempty"`
}
type HTTP2Options struct {
Host []string `proxy:"host,omitempty"`
Path string `proxy:"path,omitempty"`
}
type GrpcOptions struct {
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
}
type WSOptions struct {
Path string `proxy:"path,omitempty"`
Headers map[string]string `proxy:"headers,omitempty"`
MaxEarlyData int `proxy:"max-early-data,omitempty"`
EarlyDataHeaderName string `proxy:"early-data-header-name,omitempty"`
}
// StreamConnContext implements C.ProxyAdapter
func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
if tlsC.HaveGlobalFingerprint() && (len(v.option.ClientFingerprint) == 0) {
v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
}
switch v.option.Network {
case "ws":
host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &clashVMess.WebsocketConfig{
Host: host,
Port: port,
Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
ClientFingerprint: v.option.ClientFingerprint,
Headers: http.Header{},
}
if len(v.option.WSOpts.Headers) != 0 {
for key, value := range v.option.WSOpts.Headers {
wsOpts.Headers.Add(key, value)
}
}
if v.option.TLS {
wsOpts.TLS = true
tlsConfig := &tls.Config{
ServerName: host,
InsecureSkipVerify: v.option.SkipCertVerify,
NextProtos: []string{"http/1.1"},
}
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
if err != nil {
return nil, err
}
if v.option.ServerName != "" {
wsOpts.TLSConfig.ServerName = v.option.ServerName
} else if host := wsOpts.Headers.Get("Host"); host != "" {
wsOpts.TLSConfig.ServerName = host
}
}
c, err = clashVMess.StreamWebsocketConn(ctx, c, wsOpts)
case "http":
// readability first, so just copy default TLS logic
if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := &clashVMess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig,
NextProtos: v.option.ALPN,
}
if v.option.ServerName != "" {
tlsOpts.Host = v.option.ServerName
}
c, err = clashVMess.StreamTLSConn(ctx, c, tlsOpts)
if err != nil {
return nil, err
}
}
host, _, _ := net.SplitHostPort(v.addr)
httpOpts := &clashVMess.HTTPConfig{
Host: host,
Method: v.option.HTTPOpts.Method,
Path: v.option.HTTPOpts.Path,
Headers: v.option.HTTPOpts.Headers,
}
c = clashVMess.StreamHTTPConn(c, httpOpts)
case "h2":
host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := clashVMess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
NextProtos: []string{"h2"},
ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig,
}
if v.option.ServerName != "" {
tlsOpts.Host = v.option.ServerName
}
c, err = clashVMess.StreamTLSConn(ctx, c, &tlsOpts)
if err != nil {
return nil, err
}
h2Opts := &clashVMess.H2Config{
Hosts: v.option.HTTP2Opts.Host,
Path: v.option.HTTP2Opts.Path,
}
c, err = clashVMess.StreamH2Conn(c, h2Opts)
case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
default:
// handle TLS
if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := &clashVMess.TLSConfig{
Host: host,
SkipCertVerify: v.option.SkipCertVerify,
ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig,
NextProtos: v.option.ALPN,
}
if v.option.ServerName != "" {
tlsOpts.Host = v.option.ServerName
}
c, err = clashVMess.StreamTLSConn(ctx, c, tlsOpts)
}
}
if err != nil {
return nil, err
}
return v.streamConn(c, metadata)
}
func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
if metadata.NetWork == C.UDP {
if v.option.XUDP {
var globalID [8]byte
if metadata.SourceValid() {
globalID = utils.GlobalID(metadata.SourceAddress())
}
if N.NeedHandshake(c) {
conn = v.client.DialEarlyXUDPPacketConn(c,
globalID,
M.SocksaddrFromNet(metadata.UDPAddr()))
} else {
conn, err = v.client.DialXUDPPacketConn(c,
globalID,
M.SocksaddrFromNet(metadata.UDPAddr()))
}
} else if v.option.PacketAddr {
if N.NeedHandshake(c) {
conn = v.client.DialEarlyPacketConn(c,
M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
} else {
conn, err = v.client.DialPacketConn(c,
M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
}
conn = packetaddr.NewBindConn(conn)
} else {
if N.NeedHandshake(c) {
conn = v.client.DialEarlyPacketConn(c,
M.SocksaddrFromNet(metadata.UDPAddr()))
} else {
conn, err = v.client.DialPacketConn(c,
M.SocksaddrFromNet(metadata.UDPAddr()))
}
}
} else {
if N.NeedHandshake(c) {
conn = v.client.DialEarlyConn(c,
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
} else {
conn, err = v.client.DialConn(c,
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
}
}
if err != nil {
conn = nil
}
return
}
// DialContext implements C.ProxyAdapter
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
// gun transport
if v.transport != nil && len(opts) == 0 {
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = v.client.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil {
return nil, err
}
return NewConn(c, v), nil
}
return v.DialContextWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(v.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = v.StreamConnContext(ctx, c, metadata)
return NewConn(c, v), err
}
// ListenPacketContext implements C.ProxyAdapter
func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
var c net.Conn
// gun transport
if v.transport != nil && len(opts) == 0 {
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
}
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = v.streamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err)
}
return v.ListenPacketOnStreamConn(ctx, c, metadata)
}
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if len(v.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
N.TCPKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = v.StreamConnContext(ctx, c, metadata)
if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err)
}
return v.ListenPacketOnStreamConn(ctx, c, metadata)
}
// SupportWithDialer implements C.ProxyAdapter
func (v *Vmess) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vmess) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
if pc, ok := c.(net.PacketConn); ok {
return newPacketConn(N.NewThreadSafePacketConn(pc), v), nil
}
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
}
// SupportUOT implements C.ProxyAdapter
func (v *Vmess) SupportUOT() bool {
return true
}
func NewVmess(option VmessOption) (*Vmess, error) {
security := strings.ToLower(option.Cipher)
var options []vmess.ClientOption
if option.GlobalPadding {
options = append(options, vmess.ClientWithGlobalPadding())
}
if option.AuthenticatedLength {
options = append(options, vmess.ClientWithAuthenticatedLength())
}
options = append(options, vmess.ClientWithTimeFunc(ntp.Now))
client, err := vmess.NewClient(option.UUID, security, option.AlterID, options...)
if err != nil {
return nil, err
}
switch option.PacketEncoding {
case "packetaddr", "packet":
option.PacketAddr = true
case "xudp":
option.XUDP = true
}
if option.XUDP {
option.PacketAddr = false
}
v := &Vmess{
Base: &Base{
name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Vmess,
udp: option.UDP,
xudp: option.XUDP,
tfo: option.TFO,
mpTcp: option.MPTCP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
client: client,
option: &option,
}
switch option.Network {
case "h2":
if len(option.HTTP2Opts.Host) == 0 {
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
}
case "grpc":
dialFn := func(network, addr string) (net.Conn, error) {
var err error
var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
if len(v.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(v.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(context.Background(), "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
N.TCPKeepAlive(c)
return c, nil
}
gunConfig := &gun.Config{
ServiceName: v.option.GrpcOpts.GrpcServiceName,
Host: v.option.ServerName,
ClientFingerprint: v.option.ClientFingerprint,
}
if option.ServerName == "" {
gunConfig.Host = v.addr
}
var tlsConfig *tls.Config
if option.TLS {
tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify,
ServerName: v.option.ServerName,
})
if option.ServerName == "" {
host, _, _ := net.SplitHostPort(v.addr)
tlsConfig.ServerName = host
}
}
v.gunTLSConfig = tlsConfig
v.gunConfig = gunConfig
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
}
v.realityConfig, err = v.option.RealityOpts.Parse()
if err != nil {
return nil, err
}
return v, nil
}
type vmessPacketConn struct {
net.Conn
rAddr net.Addr
access sync.Mutex
}
// WriteTo implments C.PacketConn.WriteTo
// Since VMess doesn't support full cone NAT by design, we verify if addr matches uc.rAddr, and drop the packet if not.
func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
allowedAddr := uc.rAddr
destAddr := addr
if allowedAddr.String() != destAddr.String() {
return 0, ErrUDPRemoteAddrMismatch
}
uc.access.Lock()
defer uc.access.Unlock()
return uc.Conn.Write(b)
}
func (uc *vmessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, err := uc.Conn.Read(b)
return n, uc.rAddr, err
}

View File

@ -0,0 +1,531 @@
package outbound
import (
"context"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"net"
"net/netip"
"runtime"
"strconv"
"strings"
"sync"
CN "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log"
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"
"github.com/sagernet/wireguard-go/device"
)
type WireGuard struct {
*Base
bind *wireguard.ClientBind
device *device.Device
tunDevice wireguard.Device
dialer proxydialer.SingDialer
startOnce sync.Once
startErr error
resolver *dns.Resolver
refP *refProxyAdapter
}
type WireGuardOption struct {
BasicOption
WireGuardPeerOption
Name string `proxy:"name"`
PrivateKey string `proxy:"private-key"`
Workers int `proxy:"workers,omitempty"`
MTU int `proxy:"mtu,omitempty"`
UDP bool `proxy:"udp,omitempty"`
PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
Peers []WireGuardPeerOption `proxy:"peers,omitempty"`
RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"`
Dns []string `proxy:"dns,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"`
AllowedIPs []string `proxy:"allowed-ips,omitempty"`
}
type wgSingErrorHandler struct {
name string
}
var _ E.Handler = (*wgSingErrorHandler)(nil)
func (w wgSingErrorHandler) NewError(ctx context.Context, err error) {
if E.IsClosedOrCanceled(err) {
log.SingLogger.Debug(fmt.Sprintf("[WG](%s) connection closed: %s", w.name, err))
return
}
log.SingLogger.Error(fmt.Sprintf("[WG](%s) %s", w.name, err))
}
type wgNetDialer struct {
tunDevice wireguard.Device
}
var _ dialer.NetDialer = (*wgNetDialer)(nil)
func (d wgNetDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return d.tunDevice.DialContext(ctx, network, M.ParseSocksaddr(address).Unwrap())
}
func (option WireGuardPeerOption) Addr() M.Socksaddr {
return M.ParseSocksaddrHostPort(option.Server, uint16(option.Port))
}
func (option WireGuardPeerOption) Prefixes() ([]netip.Prefix, error) {
localPrefixes := make([]netip.Prefix, 0, 2)
if len(option.Ip) > 0 {
if !strings.Contains(option.Ip, "/") {
option.Ip = option.Ip + "/32"
}
if prefix, err := netip.ParsePrefix(option.Ip); err == nil {
localPrefixes = append(localPrefixes, prefix)
} else {
return nil, E.Cause(err, "ip address parse error")
}
}
if len(option.Ipv6) > 0 {
if !strings.Contains(option.Ipv6, "/") {
option.Ipv6 = option.Ipv6 + "/128"
}
if prefix, err := netip.ParsePrefix(option.Ipv6); err == nil {
localPrefixes = append(localPrefixes, prefix)
} else {
return nil, E.Cause(err, "ipv6 address parse error")
}
}
if len(localPrefixes) == 0 {
return nil, E.New("missing local address")
}
return localPrefixes, nil
}
func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
outbound := &WireGuard{
Base: &Base{
name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.WireGuard,
udp: option.UDP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
dialer: proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()),
}
runtime.SetFinalizer(outbound, closeWireGuard)
var reserved [3]uint8
if len(option.Reserved) > 0 {
if len(option.Reserved) != 3 {
return nil, E.New("invalid reserved value, required 3 bytes, got ", len(option.Reserved))
}
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()
} else {
connectAddr = option.Addr()
}
}
outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, connectAddr, reserved)
var localPrefixes []netip.Prefix
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)
}
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 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
}
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)
}
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"
}
}
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 {
return nil, E.New("missing local address")
}
var err error
outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu))
if err != nil {
return nil, E.Cause(err, "create WireGuard device")
}
outbound.device = device.NewDevice(context.Background(), outbound.tunDevice, outbound.bind, &device.Logger{
Verbosef: func(format string, args ...interface{}) {
log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
},
Errorf: func(format string, args ...interface{}) {
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 {
if !address.Addr().Unmap().Is4() {
has6 = true
break
}
}
refP := &refProxyAdapter{}
outbound.refP = refP
if option.RemoteDnsResolve && len(option.Dns) > 0 {
nss, err := dns.ParseNameServer(option.Dns)
if err != nil {
return nil, err
}
for i := range nss {
nss[i].ProxyAdapter = refP
}
outbound.resolver = dns.NewResolver(dns.Config{
Main: nss,
IPv6: has6,
})
}
return outbound, nil
}
func closeWireGuard(w *WireGuard) {
if w.device != nil {
w.device.Close()
}
_ = common.Close(w.tunDevice)
}
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 !metadata.Resolved() || w.resolver != nil {
r := resolver.DefaultResolver
if w.resolver != nil {
w.refP.SetProxyAdapter(w)
defer w.refP.ClearProxyAdapter()
r = w.resolver
}
options = append(options, dialer.WithResolver(r))
options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice}))
conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress())
} else {
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap())
}
if err != nil {
return nil, err
}
if conn == nil {
return nil, E.New("conn is nil")
}
return NewConn(CN.NewRefConn(conn, w), w), nil
}
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
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 {
return nil, err
}
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
r := resolver.DefaultResolver
if w.resolver != nil {
w.refP.SetProxyAdapter(w)
defer w.refP.ClearProxyAdapter()
r = w.resolver
}
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap())
if err != nil {
return nil, err
}
if pc == nil {
return nil, E.New("packetConn is nil")
}
return newPacketConn(CN.NewRefPacketConn(pc, w), w), nil
}
// IsL3Protocol implements C.ProxyAdapter
func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool {
return true
}
type refProxyAdapter struct {
proxyAdapter C.ProxyAdapter
count int
mutex sync.Mutex
}
func (r *refProxyAdapter) SetProxyAdapter(proxyAdapter C.ProxyAdapter) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.proxyAdapter = proxyAdapter
r.count++
}
func (r *refProxyAdapter) ClearProxyAdapter() {
r.mutex.Lock()
defer r.mutex.Unlock()
r.count--
if r.count == 0 {
r.proxyAdapter = nil
}
}
func (r *refProxyAdapter) Name() string {
if r.proxyAdapter != nil {
return r.proxyAdapter.Name()
}
return ""
}
func (r *refProxyAdapter) Type() C.AdapterType {
if r.proxyAdapter != nil {
return r.proxyAdapter.Type()
}
return C.AdapterType(0)
}
func (r *refProxyAdapter) Addr() string {
if r.proxyAdapter != nil {
return r.proxyAdapter.Addr()
}
return ""
}
func (r *refProxyAdapter) SupportUDP() bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportUDP()
}
return false
}
func (r *refProxyAdapter) SupportXUDP() bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportXUDP()
}
return false
}
func (r *refProxyAdapter) SupportTFO() bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportTFO()
}
return false
}
func (r *refProxyAdapter) MarshalJSON() ([]byte, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.MarshalJSON()
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.StreamConnContext(ctx, c, metadata)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.DialContext(ctx, metadata, opts...)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) SupportUOT() bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportUOT()
}
return false
}
func (r *refProxyAdapter) SupportWithDialer() C.NetWork {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportWithDialer()
}
return C.InvalidNet
}
func (r *refProxyAdapter) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.Conn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.DialContextWithDialer(ctx, dialer, metadata)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.PacketConn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.ListenPacketWithDialer(ctx, dialer, metadata)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) IsL3Protocol(metadata *C.Metadata) bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.IsL3Protocol(metadata)
}
return false
}
func (r *refProxyAdapter) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
if r.proxyAdapter != nil {
return r.proxyAdapter.Unwrap(metadata, touch)
}
return nil
}
var _ C.ProxyAdapter = (*refProxyAdapter)(nil)

View File

@ -0,0 +1,171 @@
package outboundgroup
import (
"context"
"encoding/json"
"errors"
"time"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/callback"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
type Fallback struct {
*GroupBase
disableUDP bool
testUrl string
selected string
expectedStatus string
}
func (f *Fallback) Now() string {
proxy := f.findAliveProxy(false)
return proxy.Name()
}
// DialContext implements C.ProxyAdapter
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
proxy := f.findAliveProxy(true)
c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
if err == nil {
c.AppendToChains(f)
} else {
f.onDialFailed(proxy.Type(), err)
}
if N.NeedHandshake(c) {
c = callback.NewFirstWriteCallBackConn(c, func(err error) {
if err == nil {
f.onDialSuccess()
} else {
f.onDialFailed(proxy.Type(), err)
}
})
}
return c, err
}
// ListenPacketContext implements C.ProxyAdapter
func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
proxy := f.findAliveProxy(true)
pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...)
if err == nil {
pc.AppendToChains(f)
}
return pc, err
}
// SupportUDP implements C.ProxyAdapter
func (f *Fallback) SupportUDP() bool {
if f.disableUDP {
return false
}
proxy := f.findAliveProxy(false)
return proxy.SupportUDP()
}
// IsL3Protocol implements C.ProxyAdapter
func (f *Fallback) IsL3Protocol(metadata *C.Metadata) bool {
return f.findAliveProxy(false).IsL3Protocol(metadata)
}
// MarshalJSON implements C.ProxyAdapter
func (f *Fallback) MarshalJSON() ([]byte, error) {
all := []string{}
for _, proxy := range f.GetProxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]any{
"type": f.Type().String(),
"now": f.Now(),
"all": all,
"testUrl": f.testUrl,
"expected": f.expectedStatus,
})
}
// Unwrap implements C.ProxyAdapter
func (f *Fallback) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
proxy := f.findAliveProxy(touch)
return proxy
}
func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
proxies := f.GetProxies(touch)
for _, proxy := range proxies {
if len(f.selected) == 0 {
// if proxy.Alive() {
if proxy.AliveForTestUrl(f.testUrl) {
return proxy
}
} else {
if proxy.Name() == f.selected {
// if proxy.Alive() {
if proxy.AliveForTestUrl(f.testUrl) {
return proxy
} else {
f.selected = ""
}
}
}
}
return proxies[0]
}
func (f *Fallback) Set(name string) error {
var p C.Proxy
for _, proxy := range f.GetProxies(false) {
if proxy.Name() == name {
p = proxy
break
}
}
if p == nil {
return errors.New("proxy not exist")
}
f.selected = name
// if !p.Alive() {
if !p.AliveForTestUrl(f.testUrl) {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
defer cancel()
expectedStatus, _ := utils.NewIntRanges[uint16](f.expectedStatus)
_, _ = p.URLTest(ctx, f.testUrl, expectedStatus, C.ExtraHistory)
}
return nil
}
func (f *Fallback) ForceSet(name string) {
f.selected = name
}
func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
return &Fallback{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
Name: option.Name,
Type: C.Fallback,
Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
option.ExcludeFilter,
option.ExcludeType,
providers,
}),
disableUDP: option.DisableUDP,
testUrl: option.URL,
expectedStatus: option.ExpectedStatus,
}
}

View File

@ -0,0 +1,294 @@
package outboundgroup
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
types "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
"github.com/dlclark/regexp2"
)
type GroupBase struct {
*outbound.Base
filterRegs []*regexp2.Regexp
excludeFilterReg *regexp2.Regexp
excludeTypeArray []string
providers []provider.ProxyProvider
failedTestMux sync.Mutex
failedTimes int
failedTime time.Time
failedTesting atomic.Bool
proxies [][]C.Proxy
versions []atomic.Uint32
}
type GroupBaseOption struct {
outbound.BaseOption
filter string
excludeFilter string
excludeType string
providers []provider.ProxyProvider
}
func NewGroupBase(opt GroupBaseOption) *GroupBase {
var excludeFilterReg *regexp2.Regexp
if opt.excludeFilter != "" {
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, 0)
}
var excludeTypeArray []string
if opt.excludeType != "" {
excludeTypeArray = strings.Split(opt.excludeType, "|")
}
var filterRegs []*regexp2.Regexp
if opt.filter != "" {
for _, filter := range strings.Split(opt.filter, "`") {
filterReg := regexp2.MustCompile(filter, 0)
filterRegs = append(filterRegs, filterReg)
}
}
gb := &GroupBase{
Base: outbound.NewBase(opt.BaseOption),
filterRegs: filterRegs,
excludeFilterReg: excludeFilterReg,
excludeTypeArray: excludeTypeArray,
providers: opt.providers,
failedTesting: atomic.NewBool(false),
}
gb.proxies = make([][]C.Proxy, len(opt.providers))
gb.versions = make([]atomic.Uint32, len(opt.providers))
return gb
}
func (gb *GroupBase) Touch() {
for _, pd := range gb.providers {
pd.Touch()
}
}
func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
var proxies []C.Proxy
if len(gb.filterRegs) == 0 {
for _, pd := range gb.providers {
if touch {
pd.Touch()
}
proxies = append(proxies, pd.Proxies()...)
}
} else {
for i, pd := range gb.providers {
if touch {
pd.Touch()
}
if pd.VehicleType() == types.Compatible {
gb.versions[i].Store(pd.Version())
gb.proxies[i] = pd.Proxies()
continue
}
version := gb.versions[i].Load()
if version != pd.Version() && gb.versions[i].CompareAndSwap(version, pd.Version()) {
var (
proxies []C.Proxy
newProxies []C.Proxy
)
proxies = pd.Proxies()
proxiesSet := map[string]struct{}{}
for _, filterReg := range gb.filterRegs {
for _, p := range proxies {
name := p.Name()
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
if _, ok := proxiesSet[name]; !ok {
proxiesSet[name] = struct{}{}
newProxies = append(newProxies, p)
}
}
}
}
gb.proxies[i] = newProxies
}
}
for _, p := range gb.proxies {
proxies = append(proxies, p...)
}
}
if len(gb.providers) > 1 && len(gb.filterRegs) > 1 {
var newProxies []C.Proxy
proxiesSet := map[string]struct{}{}
for _, filterReg := range gb.filterRegs {
for _, p := range proxies {
name := p.Name()
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
if _, ok := proxiesSet[name]; !ok {
proxiesSet[name] = struct{}{}
newProxies = append(newProxies, p)
}
}
}
}
for _, p := range proxies { // add not matched proxies at the end
name := p.Name()
if _, ok := proxiesSet[name]; !ok {
proxiesSet[name] = struct{}{}
newProxies = append(newProxies, p)
}
}
proxies = newProxies
}
if gb.excludeTypeArray != nil {
var newProxies []C.Proxy
for _, p := range proxies {
mType := p.Type().String()
flag := false
for i := range gb.excludeTypeArray {
if strings.EqualFold(mType, gb.excludeTypeArray[i]) {
flag = true
break
}
}
if flag {
continue
}
newProxies = append(newProxies, p)
}
proxies = newProxies
}
if gb.excludeFilterReg != nil {
var newProxies []C.Proxy
for _, p := range proxies {
name := p.Name()
if mat, _ := gb.excludeFilterReg.FindStringMatch(name); mat != nil {
continue
}
newProxies = append(newProxies, p)
}
proxies = newProxies
}
if len(proxies) == 0 {
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
}
return proxies
}
func (gb *GroupBase) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (map[string]uint16, error) {
var wg sync.WaitGroup
var lock sync.Mutex
mp := map[string]uint16{}
proxies := gb.GetProxies(false)
for _, proxy := range proxies {
proxy := proxy
wg.Add(1)
go func() {
delay, err := proxy.URLTest(ctx, url, expectedStatus, C.DropHistory)
if err == nil {
lock.Lock()
mp[proxy.Name()] = delay
lock.Unlock()
}
wg.Done()
}()
}
wg.Wait()
if len(mp) == 0 {
return mp, fmt.Errorf("get delay: all proxies timeout")
} else {
return mp, nil
}
}
func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) {
if adapterType == C.Direct || adapterType == C.Compatible || adapterType == C.Reject || adapterType == C.Pass {
return
}
if strings.Contains(err.Error(), "connection refused") {
go gb.healthCheck()
return
}
go func() {
gb.failedTestMux.Lock()
defer gb.failedTestMux.Unlock()
gb.failedTimes++
if gb.failedTimes == 1 {
log.Debugln("ProxyGroup: %s first failed", gb.Name())
gb.failedTime = time.Now()
} else {
if time.Since(gb.failedTime) > gb.failedTimeoutInterval() {
gb.failedTimes = 0
return
}
log.Debugln("ProxyGroup: %s failed count: %d", gb.Name(), gb.failedTimes)
if gb.failedTimes >= gb.maxFailedTimes() {
log.Warnln("because %s failed multiple times, active health check", gb.Name())
gb.healthCheck()
}
}
}()
}
func (gb *GroupBase) healthCheck() {
if gb.failedTesting.Load() {
return
}
gb.failedTesting.Store(true)
wg := sync.WaitGroup{}
for _, proxyProvider := range gb.providers {
wg.Add(1)
proxyProvider := proxyProvider
go func() {
defer wg.Done()
proxyProvider.HealthCheck()
}()
}
wg.Wait()
gb.failedTesting.Store(false)
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

@ -0,0 +1,276 @@
package outboundgroup
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"sync"
"time"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/callback"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
"golang.org/x/net/publicsuffix"
)
type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy
type LoadBalance struct {
*GroupBase
disableUDP bool
strategyFn strategyFn
testUrl string
expectedStatus string
}
var errStrategy = errors.New("unsupported strategy")
func parseStrategy(config map[string]any) string {
if strategy, ok := config["strategy"].(string); ok {
return strategy
}
return "consistent-hashing"
}
func getKey(metadata *C.Metadata) string {
if metadata == nil {
return ""
}
if metadata.Host != "" {
// ip host
if ip := net.ParseIP(metadata.Host); ip != nil {
return metadata.Host
}
if etld, err := publicsuffix.EffectiveTLDPlusOne(metadata.Host); err == nil {
return etld
}
}
if !metadata.DstIP.IsValid() {
return ""
}
return metadata.DstIP.String()
}
func getKeyWithSrcAndDst(metadata *C.Metadata) string {
dst := getKey(metadata)
src := ""
if metadata != nil {
src = metadata.SrcIP.String()
}
return fmt.Sprintf("%s%s", src, dst)
}
func jumpHash(key uint64, buckets int32) int32 {
var b, j int64
for j < int64(buckets) {
b = j
key = key*2862933555777941757 + 1
j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1)))
}
return int32(b)
}
// DialContext implements C.ProxyAdapter
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
proxy := lb.Unwrap(metadata, true)
c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
if err == nil {
c.AppendToChains(lb)
} else {
lb.onDialFailed(proxy.Type(), err)
}
if N.NeedHandshake(c) {
c = callback.NewFirstWriteCallBackConn(c, func(err error) {
if err == nil {
lb.onDialSuccess()
} else {
lb.onDialFailed(proxy.Type(), err)
}
})
}
return
}
// ListenPacketContext implements C.ProxyAdapter
func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (pc C.PacketConn, err error) {
defer func() {
if err == nil {
pc.AppendToChains(lb)
}
}()
proxy := lb.Unwrap(metadata, true)
return proxy.ListenPacketContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
}
// SupportUDP implements C.ProxyAdapter
func (lb *LoadBalance) SupportUDP() bool {
return !lb.disableUDP
}
// IsL3Protocol implements C.ProxyAdapter
func (lb *LoadBalance) IsL3Protocol(metadata *C.Metadata) bool {
return lb.Unwrap(metadata, false).IsL3Protocol(metadata)
}
func strategyRoundRobin(url string) strategyFn {
idx := 0
idxMutex := sync.Mutex{}
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
idxMutex.Lock()
defer idxMutex.Unlock()
i := 0
length := len(proxies)
if touch {
defer func() {
idx = (idx + i) % length
}()
}
for ; i < length; i++ {
id := (idx + i) % length
proxy := proxies[id]
// if proxy.Alive() {
if proxy.AliveForTestUrl(url) {
i++
return proxy
}
}
return proxies[0]
}
}
func strategyConsistentHashing(url string) strategyFn {
maxRetry := 5
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
key := utils.MapHash(getKey(metadata))
buckets := int32(len(proxies))
for i := 0; i < maxRetry; i, key = i+1, key+1 {
idx := jumpHash(key, buckets)
proxy := proxies[idx]
// if proxy.Alive() {
if proxy.AliveForTestUrl(url) {
return proxy
}
}
// when availability is poor, traverse the entire list to get the available nodes
for _, proxy := range proxies {
// if proxy.Alive() {
if proxy.AliveForTestUrl(url) {
return proxy
}
}
return proxies[0]
}
}
func strategyStickySessions(url string) strategyFn {
ttl := time.Minute * 10
maxRetry := 5
lruCache := cache.New[uint64, int](
cache.WithAge[uint64, int](int64(ttl.Seconds())),
cache.WithSize[uint64, int](1000))
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
key := utils.MapHash(getKeyWithSrcAndDst(metadata))
length := len(proxies)
idx, has := lruCache.Get(key)
if !has {
idx = int(jumpHash(key+uint64(time.Now().UnixNano()), int32(length)))
}
nowIdx := idx
for i := 1; i < maxRetry; i++ {
proxy := proxies[nowIdx]
// if proxy.Alive() {
if proxy.AliveForTestUrl(url) {
if nowIdx != idx {
lruCache.Delete(key)
lruCache.Set(key, nowIdx)
}
return proxy
} else {
nowIdx = int(jumpHash(key+uint64(time.Now().UnixNano()), int32(length)))
}
}
lruCache.Delete(key)
lruCache.Set(key, 0)
return proxies[0]
}
}
// Unwrap implements C.ProxyAdapter
func (lb *LoadBalance) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
proxies := lb.GetProxies(touch)
return lb.strategyFn(proxies, metadata, touch)
}
// MarshalJSON implements C.ProxyAdapter
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range lb.GetProxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]any{
"type": lb.Type().String(),
"all": all,
"testUrl": lb.testUrl,
"expectedStatus": lb.expectedStatus,
})
}
func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvider, strategy string) (lb *LoadBalance, err error) {
var strategyFn strategyFn
switch strategy {
case "consistent-hashing":
strategyFn = strategyConsistentHashing(option.URL)
case "round-robin":
strategyFn = strategyRoundRobin(option.URL)
case "sticky-sessions":
strategyFn = strategyStickySessions(option.URL)
default:
return nil, fmt.Errorf("%w: %s", errStrategy, strategy)
}
return &LoadBalance{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
Name: option.Name,
Type: C.LoadBalance,
Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
option.ExcludeFilter,
option.ExcludeType,
providers,
}),
strategyFn: strategyFn,
disableUDP: option.DisableUDP,
testUrl: option.URL,
expectedStatus: option.ExpectedStatus,
}, nil
}

View File

@ -0,0 +1,181 @@
package outboundgroup
import (
"errors"
"fmt"
"strings"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/adapter/provider"
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
)
var (
errFormat = errors.New("format error")
errType = errors.New("unsupported type")
errMissProxy = errors.New("`use` or `proxies` missing")
errDuplicateProvider = errors.New("duplicate provider name")
)
type GroupCommonOption struct {
outbound.BasicOption
Name string `group:"name"`
Type string `group:"type"`
Proxies []string `group:"proxies,omitempty"`
Use []string `group:"use,omitempty"`
URL string `group:"url,omitempty"`
Interval int `group:"interval,omitempty"`
Lazy bool `group:"lazy,omitempty"`
DisableUDP bool `group:"disable-udp,omitempty"`
Filter string `group:"filter,omitempty"`
ExcludeFilter string `group:"exclude-filter,omitempty"`
ExcludeType string `group:"exclude-type,omitempty"`
ExpectedStatus string `group:"expected-status,omitempty"`
}
func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true})
groupOption := &GroupCommonOption{
Lazy: true,
}
if err := decoder.Decode(config, groupOption); err != nil {
return nil, errFormat
}
if groupOption.Type == "" || groupOption.Name == "" {
return nil, errFormat
}
groupName := groupOption.Name
providers := []types.ProxyProvider{}
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
return nil, fmt.Errorf("%s: %w", groupName, errMissProxy)
}
expectedStatus, err := utils.NewIntRanges[uint16](groupOption.ExpectedStatus)
if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err)
}
status := strings.TrimSpace(groupOption.ExpectedStatus)
if status == "" {
status = "*"
}
groupOption.ExpectedStatus = status
testUrl := groupOption.URL
if len(groupOption.Proxies) != 0 {
ps, err := getProxies(proxyMap, groupOption.Proxies)
if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err)
}
if _, ok := providersMap[groupName]; ok {
return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider)
}
var url string
var interval uint
// select don't need health check
if groupOption.Type != "select" && groupOption.Type != "relay" {
if groupOption.URL == "" {
groupOption.URL = "https://cp.cloudflare.com/generate_204"
}
if groupOption.Interval == 0 {
groupOption.Interval = 300
}
url = groupOption.URL
interval = uint(groupOption.Interval)
}
hc := provider.NewHealthCheck(ps, url, interval, true, expectedStatus)
pd, err := provider.NewCompatibleProvider(groupName, ps, hc)
if err != nil {
return nil, fmt.Errorf("%s: %w", groupName, err)
}
providers = append(providers, pd)
providersMap[groupName] = pd
}
if len(groupOption.Use) != 0 {
list, err := getProviders(providersMap, groupOption.Use)
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":
opts := parseURLTestOption(config)
group = NewURLTest(groupOption, providers, opts...)
case "select":
group = NewSelector(groupOption, providers)
case "fallback":
group = NewFallback(groupOption, providers)
case "load-balance":
strategy := parseStrategy(config)
return NewLoadBalance(groupOption, providers, strategy)
case "relay":
group = NewRelay(groupOption, providers)
default:
return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
}
return group, nil
}
func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
var ps []C.Proxy
for _, name := range list {
p, ok := mapping[name]
if !ok {
return nil, fmt.Errorf("'%s' not found", name)
}
ps = append(ps, p)
}
return ps, nil
}
func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]types.ProxyProvider, error) {
var ps []types.ProxyProvider
for _, name := range list {
p, ok := mapping[name]
if !ok {
return nil, fmt.Errorf("'%s' not found", name)
}
if p.VehicleType() == types.Compatible {
return nil, fmt.Errorf("proxy group %s can't contains in `use`", name)
}
ps = append(ps, p)
}
return ps, nil
}
func addTestUrlToProviders(providers []types.ProxyProvider, url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
if len(providers) == 0 || len(url) == 0 {
return
}
for _, pd := range providers {
pd.RegisterHealthCheckTask(url, expectedStatus, filter, interval)
}
}

View File

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

View File

@ -0,0 +1,161 @@
package outboundgroup
import (
"context"
"encoding/json"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
type Relay struct {
*GroupBase
}
// DialContext implements C.ProxyAdapter
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
proxies, chainProxies := r.proxies(metadata, true)
switch len(proxies) {
case 0:
return outbound.NewDirect().DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
case 1:
return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
}
var d C.Dialer
d = dialer.NewDialer(r.Base.DialOptions(opts...)...)
for _, proxy := range proxies[:len(proxies)-1] {
d = proxydialer.New(proxy, d, false)
}
last := proxies[len(proxies)-1]
conn, err := last.DialContextWithDialer(ctx, d, metadata)
if err != nil {
return nil, err
}
for i := len(chainProxies) - 2; i >= 0; i-- {
conn.AppendToChains(chainProxies[i])
}
conn.AppendToChains(r)
return conn, nil
}
// ListenPacketContext implements C.ProxyAdapter
func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
proxies, chainProxies := r.proxies(metadata, true)
switch len(proxies) {
case 0:
return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
case 1:
return proxies[0].ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
}
var d C.Dialer
d = dialer.NewDialer(r.Base.DialOptions(opts...)...)
for _, proxy := range proxies[:len(proxies)-1] {
d = proxydialer.New(proxy, d, false)
}
last := proxies[len(proxies)-1]
pc, err := last.ListenPacketWithDialer(ctx, d, metadata)
if err != nil {
return nil, err
}
for i := len(chainProxies) - 2; i >= 0; i-- {
pc.AppendToChains(chainProxies[i])
}
pc.AppendToChains(r)
return pc, nil
}
// SupportUDP implements C.ProxyAdapter
func (r *Relay) SupportUDP() bool {
proxies, _ := r.proxies(nil, false)
if len(proxies) == 0 { // C.Direct
return true
}
for i := len(proxies) - 1; i >= 0; i-- {
proxy := proxies[i]
if !proxy.SupportUDP() {
return false
}
if proxy.SupportUOT() {
return true
}
switch proxy.SupportWithDialer() {
case C.ALLNet:
case C.UDP:
default: // C.TCP and C.InvalidNet
return false
}
}
return true
}
// MarshalJSON implements C.ProxyAdapter
func (r *Relay) MarshalJSON() ([]byte, error) {
all := []string{}
for _, proxy := range r.GetProxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]any{
"type": r.Type().String(),
"all": all,
})
}
func (r *Relay) proxies(metadata *C.Metadata, touch bool) ([]C.Proxy, []C.Proxy) {
rawProxies := r.GetProxies(touch)
var proxies []C.Proxy
var chainProxies []C.Proxy
var targetProxies []C.Proxy
for n, proxy := range rawProxies {
proxies = append(proxies, proxy)
chainProxies = append(chainProxies, proxy)
subproxy := proxy.Unwrap(metadata, touch)
for subproxy != nil {
chainProxies = append(chainProxies, subproxy)
proxies[n] = subproxy
subproxy = subproxy.Unwrap(metadata, touch)
}
}
for _, proxy := range proxies {
if proxy.Type() != C.Direct && proxy.Type() != C.Compatible {
targetProxies = append(targetProxies, proxy)
}
}
return targetProxies, chainProxies
}
func (r *Relay) Addr() string {
proxies, _ := r.proxies(nil, false)
return proxies[len(proxies)-1].Addr()
}
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
return &Relay{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
Name: option.Name,
Type: C.Relay,
Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
"",
"",
"",
providers,
}),
}
}

View File

@ -0,0 +1,118 @@
package outboundgroup
import (
"context"
"encoding/json"
"errors"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
type Selector struct {
*GroupBase
disableUDP bool
selected string
}
// DialContext implements C.ProxyAdapter
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
c, err := s.selectedProxy(true).DialContext(ctx, metadata, s.Base.DialOptions(opts...)...)
if err == nil {
c.AppendToChains(s)
}
return c, err
}
// ListenPacketContext implements C.ProxyAdapter
func (s *Selector) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
pc, err := s.selectedProxy(true).ListenPacketContext(ctx, metadata, s.Base.DialOptions(opts...)...)
if err == nil {
pc.AppendToChains(s)
}
return pc, err
}
// SupportUDP implements C.ProxyAdapter
func (s *Selector) SupportUDP() bool {
if s.disableUDP {
return false
}
return s.selectedProxy(false).SupportUDP()
}
// IsL3Protocol implements C.ProxyAdapter
func (s *Selector) IsL3Protocol(metadata *C.Metadata) bool {
return s.selectedProxy(false).IsL3Protocol(metadata)
}
// MarshalJSON implements C.ProxyAdapter
func (s *Selector) MarshalJSON() ([]byte, error) {
all := []string{}
for _, proxy := range s.GetProxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]any{
"type": s.Type().String(),
"now": s.Now(),
"all": all,
})
}
func (s *Selector) Now() string {
return s.selectedProxy(false).Name()
}
func (s *Selector) Set(name string) error {
for _, proxy := range s.GetProxies(false) {
if proxy.Name() == name {
s.selected = name
return nil
}
}
return errors.New("proxy not exist")
}
func (s *Selector) ForceSet(name string) {
s.selected = name
}
// Unwrap implements C.ProxyAdapter
func (s *Selector) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
return s.selectedProxy(touch)
}
func (s *Selector) selectedProxy(touch bool) C.Proxy {
proxies := s.GetProxies(touch)
for _, proxy := range proxies {
if proxy.Name() == s.selected {
return proxy
}
}
return proxies[0]
}
func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector {
return &Selector{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
Name: option.Name,
Type: C.Selector,
Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
option.ExcludeFilter,
option.ExcludeType,
providers,
}),
selected: "COMPATIBLE",
disableUDP: option.DisableUDP,
}
}

View File

@ -0,0 +1,219 @@
package outboundgroup
import (
"context"
"encoding/json"
"errors"
"time"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/callback"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
type urlTestOption func(*URLTest)
func urlTestWithTolerance(tolerance uint16) urlTestOption {
return func(u *URLTest) {
u.tolerance = tolerance
}
}
type URLTest struct {
*GroupBase
selected string
testUrl string
expectedStatus string
tolerance uint16
disableUDP bool
fastNode C.Proxy
fastSingle *singledo.Single[C.Proxy]
}
func (u *URLTest) Now() string {
return u.fast(false).Name()
}
func (u *URLTest) Set(name string) error {
var p C.Proxy
for _, proxy := range u.GetProxies(false) {
if proxy.Name() == name {
p = proxy
break
}
}
if p == nil {
return errors.New("proxy not exist")
}
u.selected = name
u.fast(false)
return nil
}
func (u *URLTest) ForceSet(name string) {
u.selected = name
}
// DialContext implements C.ProxyAdapter
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
proxy := u.fast(true)
c, err = proxy.DialContext(ctx, metadata, u.Base.DialOptions(opts...)...)
if err == nil {
c.AppendToChains(u)
} else {
u.onDialFailed(proxy.Type(), err)
}
if N.NeedHandshake(c) {
c = callback.NewFirstWriteCallBackConn(c, func(err error) {
if err == nil {
u.onDialSuccess()
} else {
u.onDialFailed(proxy.Type(), err)
}
})
}
return c, err
}
// ListenPacketContext implements C.ProxyAdapter
func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
pc, err := u.fast(true).ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...)
if err == nil {
pc.AppendToChains(u)
}
return pc, err
}
// Unwrap implements C.ProxyAdapter
func (u *URLTest) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
return u.fast(touch)
}
func (u *URLTest) fast(touch bool) C.Proxy {
proxies := u.GetProxies(touch)
if u.selected != "" {
for _, proxy := range proxies {
if !proxy.Alive() {
continue
}
if proxy.Name() == u.selected {
u.fastNode = proxy
return proxy
}
}
}
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
fast := proxies[0]
// min := fast.LastDelay()
min := fast.LastDelayForTestUrl(u.testUrl)
fastNotExist := true
for _, proxy := range proxies[1:] {
if u.fastNode != nil && proxy.Name() == u.fastNode.Name() {
fastNotExist = false
}
// if !proxy.Alive() {
if !proxy.AliveForTestUrl(u.testUrl) {
continue
}
// delay := proxy.LastDelay()
delay := proxy.LastDelayForTestUrl(u.testUrl)
if delay < min {
fast = proxy
min = delay
}
}
// tolerance
// if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
if u.fastNode == nil || fastNotExist || !u.fastNode.AliveForTestUrl(u.testUrl) || u.fastNode.LastDelayForTestUrl(u.testUrl) > fast.LastDelayForTestUrl(u.testUrl)+u.tolerance {
u.fastNode = fast
}
return u.fastNode, nil
})
if shared && touch { // a shared fastSingle.Do() may cause providers untouched, so we touch them again
u.Touch()
}
return elm
}
// SupportUDP implements C.ProxyAdapter
func (u *URLTest) SupportUDP() bool {
if u.disableUDP {
return false
}
return u.fast(false).SupportUDP()
}
// IsL3Protocol implements C.ProxyAdapter
func (u *URLTest) IsL3Protocol(metadata *C.Metadata) bool {
return u.fast(false).IsL3Protocol(metadata)
}
// MarshalJSON implements C.ProxyAdapter
func (u *URLTest) MarshalJSON() ([]byte, error) {
all := []string{}
for _, proxy := range u.GetProxies(false) {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]any{
"type": u.Type().String(),
"now": u.Now(),
"all": all,
"testUrl": u.testUrl,
"expected": u.expectedStatus,
})
}
func parseURLTestOption(config map[string]any) []urlTestOption {
opts := []urlTestOption{}
// tolerance
if elm, ok := config["tolerance"]; ok {
if tolerance, ok := elm.(int); ok {
opts = append(opts, urlTestWithTolerance(uint16(tolerance)))
}
}
return opts
}
func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
urlTest := &URLTest{
GroupBase: NewGroupBase(GroupBaseOption{
outbound.BaseOption{
Name: option.Name,
Type: C.URLTest,
Interface: option.Interface,
RoutingMark: option.RoutingMark,
},
option.Filter,
option.ExcludeFilter,
option.ExcludeType,
providers,
}),
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
disableUDP: option.DisableUDP,
testUrl: option.URL,
expectedStatus: option.ExpectedStatus,
}
for _, option := range options {
option(urlTest)
}
return urlTest
}

View File

@ -0,0 +1,6 @@
package outboundgroup
type SelectAble interface {
Set(string) error
ForceSet(name string)
}

153
adapter/parser.go Normal file
View File

@ -0,0 +1,153 @@
package adapter
import (
"fmt"
tlsC "github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant"
)
func ParseProxy(mapping map[string]any) (C.Proxy, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true, KeyReplacer: structure.DefaultKeyReplacer})
proxyType, existType := mapping["type"].(string)
if !existType {
return nil, fmt.Errorf("missing type")
}
var (
proxy C.ProxyAdapter
err error
)
switch proxyType {
case "ss":
ssOption := &outbound.ShadowSocksOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
err = decoder.Decode(mapping, ssOption)
if err != nil {
break
}
proxy, err = outbound.NewShadowSocks(*ssOption)
case "ssr":
ssrOption := &outbound.ShadowSocksROption{}
err = decoder.Decode(mapping, ssrOption)
if err != nil {
break
}
proxy, err = outbound.NewShadowSocksR(*ssrOption)
case "socks5":
socksOption := &outbound.Socks5Option{}
err = decoder.Decode(mapping, socksOption)
if err != nil {
break
}
proxy, err = outbound.NewSocks5(*socksOption)
case "http":
httpOption := &outbound.HttpOption{}
err = decoder.Decode(mapping, httpOption)
if err != nil {
break
}
proxy, err = outbound.NewHttp(*httpOption)
case "vmess":
vmessOption := &outbound.VmessOption{
HTTPOpts: outbound.HTTPOptions{
Method: "GET",
Path: []string{"/"},
},
ClientFingerprint: tlsC.GetGlobalFingerprint(),
}
err = decoder.Decode(mapping, vmessOption)
if err != nil {
break
}
proxy, err = outbound.NewVmess(*vmessOption)
case "vless":
vlessOption := &outbound.VlessOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
err = decoder.Decode(mapping, vlessOption)
if err != nil {
break
}
proxy, err = outbound.NewVless(*vlessOption)
case "snell":
snellOption := &outbound.SnellOption{}
err = decoder.Decode(mapping, snellOption)
if err != nil {
break
}
proxy, err = outbound.NewSnell(*snellOption)
case "trojan":
trojanOption := &outbound.TrojanOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
err = decoder.Decode(mapping, trojanOption)
if err != nil {
break
}
proxy, err = outbound.NewTrojan(*trojanOption)
case "hysteria":
hyOption := &outbound.HysteriaOption{}
err = decoder.Decode(mapping, hyOption)
if err != nil {
break
}
proxy, err = outbound.NewHysteria(*hyOption)
case "hysteria2":
hyOption := &outbound.Hysteria2Option{}
err = decoder.Decode(mapping, hyOption)
if err != nil {
break
}
proxy, err = outbound.NewHysteria2(*hyOption)
case "wireguard":
wgOption := &outbound.WireGuardOption{}
err = decoder.Decode(mapping, wgOption)
if err != nil {
break
}
proxy, err = outbound.NewWireGuard(*wgOption)
case "tuic":
tuicOption := &outbound.TuicOption{}
err = decoder.Decode(mapping, tuicOption)
if err != nil {
break
}
proxy, err = outbound.NewTuic(*tuicOption)
case "direct":
directOption := &outbound.DirectOption{}
err = decoder.Decode(mapping, directOption)
if err != nil {
break
}
proxy = outbound.NewDirectWithOption(*directOption)
case "reject":
rejectOption := &outbound.RejectOption{}
err = decoder.Decode(mapping, rejectOption)
if err != nil {
break
}
proxy = outbound.NewRejectWithOption(*rejectOption)
default:
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
}
if err != nil {
return nil, err
}
if muxMapping, muxExist := mapping["smux"].(map[string]any); muxExist {
muxOption := &outbound.SingMuxOption{}
err = decoder.Decode(muxMapping, muxOption)
if err != nil {
return nil, err
}
if muxOption.Enabled {
proxy, err = outbound.NewSingMux(*muxOption, proxy, proxy.(outbound.ProxyBase))
if err != nil {
return nil, err
}
}
}
return NewProxy(proxy), nil
}

View File

@ -0,0 +1,245 @@
package provider
import (
"context"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/batch"
"github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/dlclark/regexp2"
)
const (
defaultURLTestTimeout = time.Second * 5
defaultURLTestURL = "https://www.gstatic.com/generate_204"
)
type HealthCheckOption struct {
URL string
Interval uint
}
type extraOption struct {
expectedStatus utils.IntRanges[uint16]
filters map[string]struct{}
}
type HealthCheck struct {
url string
extra map[string]*extraOption
mu sync.Mutex
started atomic.Bool
proxies []C.Proxy
interval time.Duration
lazy bool
expectedStatus utils.IntRanges[uint16]
lastTouch atomic.TypedValue[time.Time]
done chan struct{}
singleDo *singledo.Single[struct{}]
}
func (hc *HealthCheck) process() {
if hc.started.Load() {
log.Warnln("Skip start health check timer due to it's started")
return
}
ticker := time.NewTicker(hc.interval)
hc.start()
for {
select {
case <-ticker.C:
lastTouch := hc.lastTouch.Load()
since := time.Since(lastTouch)
if !hc.lazy || since < hc.interval {
hc.check()
} else {
log.Debugln("Skip once health check because we are lazy")
}
case <-hc.done:
ticker.Stop()
hc.stop()
return
}
}
}
func (hc *HealthCheck) setProxy(proxies []C.Proxy) {
hc.proxies = proxies
}
func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
url = strings.TrimSpace(url)
if len(url) == 0 || url == hc.url {
log.Debugln("ignore invalid health check url: %s", url)
return
}
hc.mu.Lock()
defer hc.mu.Unlock()
// if the provider has not set up health checks, then modify it to be the same as the group's interval
if hc.interval == 0 {
hc.interval = time.Duration(interval) * time.Second
}
if hc.extra == nil {
hc.extra = make(map[string]*extraOption)
}
// prioritize the use of previously registered configurations, especially those from provider
if _, ok := hc.extra[url]; ok {
// provider default health check does not set filter
if url != hc.url && len(filter) != 0 {
splitAndAddFiltersToExtra(filter, hc.extra[url])
}
log.Debugln("health check url: %s exists", url)
return
}
// due to the time-consuming nature of health checks, a maximum of defaultMaxTestURLNum URLs can be set for testing
if len(hc.extra) > C.DefaultMaxHealthCheckUrlNum {
log.Debugln("skip add url: %s to health check because it has reached the maximum limit: %d", url, C.DefaultMaxHealthCheckUrlNum)
return
}
option := &extraOption{filters: map[string]struct{}{}, expectedStatus: expectedStatus}
splitAndAddFiltersToExtra(filter, option)
hc.extra[url] = option
if hc.auto() && !hc.started.Load() {
go hc.process()
}
}
func splitAndAddFiltersToExtra(filter string, option *extraOption) {
filter = strings.TrimSpace(filter)
if len(filter) != 0 {
for _, regex := range strings.Split(filter, "`") {
regex = strings.TrimSpace(regex)
if len(regex) != 0 {
option.filters[regex] = struct{}{}
}
}
}
}
func (hc *HealthCheck) auto() bool {
return hc.interval != 0
}
func (hc *HealthCheck) touch() {
hc.lastTouch.Store(time.Now())
}
func (hc *HealthCheck) start() {
hc.started.Store(true)
}
func (hc *HealthCheck) stop() {
hc.started.Store(false)
}
func (hc *HealthCheck) check() {
if len(hc.proxies) == 0 {
return
}
_, _, _ = 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))
// execute default health check
option := &extraOption{filters: nil, expectedStatus: hc.expectedStatus}
hc.execute(b, hc.url, id, option)
// execute extra health check
if len(hc.extra) != 0 {
for url, option := range hc.extra {
hc.execute(b, url, id, option)
}
}
b.Wait()
log.Debugln("Finish A Health Checking {%s}", id)
return struct{}{}, nil
})
}
func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *extraOption) {
url = strings.TrimSpace(url)
if len(url) == 0 {
log.Debugln("Health Check has been skipped due to testUrl is empty, {%s}", uid)
return
}
var filterReg *regexp2.Regexp
var store = C.OriginalHistory
var expectedStatus utils.IntRanges[uint16]
if option != nil {
if url != hc.url {
store = C.ExtraHistory
}
expectedStatus = option.expectedStatus
if len(option.filters) != 0 {
filters := make([]string, 0, len(option.filters))
for filter := range option.filters {
filters = append(filters, filter)
}
filterReg = regexp2.MustCompile(strings.Join(filters, "|"), 0)
}
}
for _, proxy := range hc.proxies {
// skip proxies that do not require health check
if filterReg != nil {
if match, _ := filterReg.FindStringMatch(proxy.Name()); match == nil {
continue
}
}
p := proxy
b.Go(p.Name(), func() (bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
defer cancel()
log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid)
_, _ = p.URLTest(ctx, url, expectedStatus, store)
log.Debugln("Health Checked, proxy: %s, url: %s, alive: %t, delay: %d ms uid: {%s}", p.Name(), url, p.AliveForTestUrl(url), p.LastDelayForTestUrl(url), uid)
return false, nil
})
}
}
func (hc *HealthCheck) close() {
hc.done <- struct{}{}
}
func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, expectedStatus utils.IntRanges[uint16]) *HealthCheck {
if len(url) == 0 {
interval = 0
expectedStatus = nil
url = defaultURLTestURL
}
return &HealthCheck{
proxies: proxies,
url: url,
extra: map[string]*extraOption{},
interval: time.Duration(interval) * time.Second,
lazy: lazy,
expectedStatus: expectedStatus,
done: make(chan struct{}, 1),
singleDo: singledo.NewSingle[struct{}](time.Second),
}
}

View File

@ -0,0 +1,87 @@
package provider
import (
"errors"
"fmt"
"time"
"github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/resource"
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
)
var (
errVehicleType = errors.New("unsupport vehicle type")
errSubPath = errors.New("path is not subpath of home directory")
)
type healthCheckSchema struct {
Enable bool `provider:"enable"`
URL string `provider:"url"`
Interval int `provider:"interval"`
Lazy bool `provider:"lazy,omitempty"`
ExpectedStatus string `provider:"expected-status,omitempty"`
}
type proxyProviderSchema struct {
Type string `provider:"type"`
Path string `provider:"path,omitempty"`
URL string `provider:"url,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"`
}
func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
schema := &proxyProviderSchema{
HealthCheck: healthCheckSchema{
Lazy: true,
},
}
if err := decoder.Decode(mapping, schema); err != nil {
return nil, err
}
expectedStatus, err := utils.NewIntRanges[uint16](schema.HealthCheck.ExpectedStatus)
if err != nil {
return nil, err
}
var hcInterval uint
if schema.HealthCheck.Enable {
hcInterval = uint(schema.HealthCheck.Interval)
}
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy, expectedStatus)
var vehicle types.Vehicle
switch schema.Type {
case "file":
path := C.Path.Resolve(schema.Path)
vehicle = resource.NewFileVehicle(path)
case "http":
if schema.Path != "" {
path := C.Path.Resolve(schema.Path)
vehicle = resource.NewHTTPVehicle(schema.URL, path)
} else {
path := C.Path.GetPathByHash("proxies", schema.URL)
vehicle = resource.NewHTTPVehicle(schema.URL, path)
}
default:
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
}
interval := time.Duration(uint(schema.Interval)) * time.Second
filter := schema.Filter
excludeFilter := schema.ExcludeFilter
excludeType := schema.ExcludeType
dialerProxy := schema.DialerProxy
return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, dialerProxy, vehicle, hc)
}

34
adapter/provider/patch.go Normal file
View File

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

View File

@ -0,0 +1,379 @@
package provider
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"runtime"
"strings"
"time"
"github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/common/convert"
"github.com/Dreamacro/clash/common/utils"
clashHttp "github.com/Dreamacro/clash/component/http"
"github.com/Dreamacro/clash/component/resource"
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel/statistic"
"github.com/dlclark/regexp2"
"gopkg.in/yaml.v3"
)
const (
ReservedName = "default"
)
type ProxySchema struct {
Proxies []map[string]any `yaml:"proxies"`
}
// ProxySetProvider for auto gc
type ProxySetProvider struct {
*proxySetProvider
}
type proxySetProvider struct {
*resource.Fetcher[[]C.Proxy]
proxies []C.Proxy
healthCheck *HealthCheck
version uint32
subscriptionInfo *SubscriptionInfo
}
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"name": pp.Name(),
"type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(),
"testUrl": pp.healthCheck.url,
"updatedAt": pp.UpdatedAt,
"subscriptionInfo": pp.subscriptionInfo,
})
}
func (pp *proxySetProvider) Version() uint32 {
return pp.version
}
func (pp *proxySetProvider) Name() string {
return pp.Fetcher.Name()
}
func (pp *proxySetProvider) HealthCheck() {
pp.healthCheck.check()
}
func (pp *proxySetProvider) Update() error {
elm, same, err := pp.Fetcher.Update()
if err == nil && !same {
pp.OnUpdate(elm)
}
return err
}
func (pp *proxySetProvider) Initial() error {
elm, err := pp.Fetcher.Initial()
if err != nil {
return err
}
pp.OnUpdate(elm)
pp.getSubscriptionInfo()
pp.closeAllConnections()
return nil
}
func (pp *proxySetProvider) Type() types.ProviderType {
return types.Proxy
}
func (pp *proxySetProvider) Proxies() []C.Proxy {
return pp.proxies
}
func (pp *proxySetProvider) Touch() {
pp.healthCheck.touch()
}
func (pp *proxySetProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
pp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
}
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
pp.proxies = proxies
pp.healthCheck.setProxy(proxies)
if pp.healthCheck.auto() {
go pp.healthCheck.check()
}
}
func (pp *proxySetProvider) getSubscriptionInfo() {
if pp.VehicleType() != types.HTTP {
return
}
go func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := clashHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
if err != nil {
return
}
defer resp.Body.Close()
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
if userInfoStr == "" {
resp2, err := clashHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil)
if err != nil {
return
}
defer resp2.Body.Close()
userInfoStr = strings.TrimSpace(resp2.Header.Get("subscription-userinfo"))
if userInfoStr == "" {
return
}
}
pp.subscriptionInfo, err = NewSubscriptionInfo(userInfoStr)
if err != nil {
log.Warnln("[Provider] get subscription-userinfo: %e", err)
}
}()
}
func (pp *proxySetProvider) closeAllConnections() {
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
for _, chain := range c.Chains() {
if chain == pp.Name() {
_ = c.Close()
break
}
}
return true
})
}
func stopProxyProvider(pd *ProxySetProvider) {
pd.healthCheck.close()
_ = pd.Fetcher.Destroy()
}
func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
excludeFilterReg, err := regexp2.Compile(excludeFilter, 0)
if err != nil {
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
}
var excludeTypeArray []string
if excludeType != "" {
excludeTypeArray = strings.Split(excludeType, "|")
}
var filterRegs []*regexp2.Regexp
for _, filter := range strings.Split(filter, "`") {
filterReg, err := regexp2.Compile(filter, 0)
if err != nil {
return nil, fmt.Errorf("invalid filter regex: %w", err)
}
filterRegs = append(filterRegs, filterReg)
}
if hc.auto() {
go hc.process()
}
pd := &proxySetProvider{
proxies: []C.Proxy{},
healthCheck: hc,
}
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy), proxiesOnUpdate(pd))
pd.Fetcher = fetcher
wrapper := &ProxySetProvider{pd}
runtime.SetFinalizer(wrapper, stopProxyProvider)
return wrapper, nil
}
// CompatibleProvider for auto gc
type CompatibleProvider struct {
*compatibleProvider
}
type compatibleProvider struct {
name string
healthCheck *HealthCheck
proxies []C.Proxy
version uint32
}
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"name": cp.Name(),
"type": cp.Type().String(),
"vehicleType": cp.VehicleType().String(),
"proxies": cp.Proxies(),
"testUrl": cp.healthCheck.url,
})
}
func (cp *compatibleProvider) Version() uint32 {
return cp.version
}
func (cp *compatibleProvider) Name() string {
return cp.name
}
func (cp *compatibleProvider) HealthCheck() {
cp.healthCheck.check()
}
func (cp *compatibleProvider) Update() error {
return nil
}
func (cp *compatibleProvider) Initial() error {
return nil
}
func (cp *compatibleProvider) VehicleType() types.VehicleType {
return types.Compatible
}
func (cp *compatibleProvider) Type() types.ProviderType {
return types.Proxy
}
func (cp *compatibleProvider) Proxies() []C.Proxy {
return cp.proxies
}
func (cp *compatibleProvider) Touch() {
cp.healthCheck.touch()
}
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 NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
if len(proxies) == 0 {
return nil, errors.New("provider need one proxy at least")
}
if hc.auto() {
go hc.process()
}
pd := &compatibleProvider{
name: name,
proxies: proxies,
healthCheck: hc,
}
wrapper := &CompatibleProvider{pd}
runtime.SetFinalizer(wrapper, stopCompatibleProvider)
return wrapper, nil
}
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
return func(elm []C.Proxy) {
pd.setProxies(elm)
pd.version += 1
pd.getSubscriptionInfo()
}
}
func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp, dialerProxy string) resource.Parser[[]C.Proxy] {
return func(buf []byte) ([]C.Proxy, error) {
schema := &ProxySchema{}
if err := yaml.Unmarshal(buf, schema); err != nil {
proxies, err1 := convert.ConvertsV2Ray(buf)
if err1 != nil {
return nil, fmt.Errorf("%w, %w", err, err1)
}
schema.Proxies = proxies
}
if schema.Proxies == nil {
return nil, errors.New("file must have a `proxies` field")
}
proxies := []C.Proxy{}
proxiesSet := map[string]struct{}{}
for _, filterReg := range filterRegs {
for idx, mapping := range schema.Proxies {
if nil != excludeTypeArray && len(excludeTypeArray) > 0 {
mType, ok := mapping["type"]
if !ok {
continue
}
pType, ok := mType.(string)
if !ok {
continue
}
flag := false
for i := range excludeTypeArray {
if strings.EqualFold(pType, excludeTypeArray[i]) {
flag = true
break
}
}
if flag {
continue
}
}
mName, ok := mapping["name"]
if !ok {
continue
}
name, ok := mName.(string)
if !ok {
continue
}
if len(excludeFilter) > 0 {
if mat, _ := excludeFilterReg.FindStringMatch(name); mat != nil {
continue
}
}
if len(filter) > 0 {
if mat, _ := filterReg.FindStringMatch(name); mat == nil {
continue
}
}
if _, ok := proxiesSet[name]; ok {
continue
}
if len(dialerProxy) > 0 {
mapping["dialer-proxy"] = dialerProxy
}
proxy, err := adapter.ParseProxy(mapping)
if err != nil {
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
}
proxiesSet[name] = struct{}{}
proxies = append(proxies, proxy)
}
}
if len(proxies) == 0 {
if len(filter) > 0 {
return nil, errors.New("doesn't match any proxy, please check your filter")
}
return nil, errors.New("file doesn't have any proxy")
}
return proxies, nil
}
}

View File

@ -0,0 +1,35 @@
package provider
import (
"strconv"
"strings"
)
type SubscriptionInfo struct {
Upload int64
Download int64
Total int64
Expire int64
}
func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo, err error) {
userinfo = strings.ToLower(userinfo)
userinfo = strings.ReplaceAll(userinfo, " ", "")
si = new(SubscriptionInfo)
for _, field := range strings.Split(userinfo, ";") {
switch name, value, _ := strings.Cut(field, "="); name {
case "upload":
si.Upload, err = strconv.ParseInt(value, 10, 64)
case "download":
si.Download, err = strconv.ParseInt(value, 10, 64)
case "total":
si.Total, err = strconv.ParseInt(value, 10, 64)
case "expire":
si.Expire, err = strconv.ParseInt(value, 10, 64)
}
if err != nil {
return
}
}
return
}

View File

@ -1,60 +0,0 @@
package inbound
import (
"net"
"net/http"
"strings"
C "github.com/Dreamacro/clash/constant"
)
// HTTPAdapter is a adapter for HTTP connection
type HTTPAdapter struct {
net.Conn
metadata *C.Metadata
R *http.Request
}
// Metadata return destination metadata
func (h *HTTPAdapter) Metadata() *C.Metadata {
return h.metadata
}
// NewHTTP is HTTPAdapter generator
func NewHTTP(request *http.Request, conn net.Conn) *HTTPAdapter {
metadata := parseHTTPAddr(request)
metadata.Type = C.HTTP
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return &HTTPAdapter{
metadata: metadata,
R: request,
Conn: conn,
}
}
// RemoveHopByHopHeaders remove hop-by-hop header
func RemoveHopByHopHeaders(header http.Header) {
// Strip hop-by-hop header based on RFC:
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
// https://www.mnot.net/blog/2011/07/11/what_proxies_must_do
header.Del("Proxy-Connection")
header.Del("Proxy-Authenticate")
header.Del("Proxy-Authorization")
header.Del("TE")
header.Del("Trailers")
header.Del("Transfer-Encoding")
header.Del("Upgrade")
connections := header.Get("Connection")
header.Del("Connection")
if len(connections) == 0 {
return
}
for _, h := range strings.Split(connections, ",") {
header.Del(strings.TrimSpace(h))
}
}

View File

@ -1,22 +0,0 @@
package inbound
import (
"net"
"net/http"
C "github.com/Dreamacro/clash/constant"
)
// NewHTTPS is HTTPAdapter generator
func NewHTTPS(request *http.Request, conn net.Conn) *SocketAdapter {
metadata := parseHTTPAddr(request)
metadata.Type = C.HTTPCONNECT
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return &SocketAdapter{
metadata: metadata,
Conn: conn,
}
}

View File

@ -1,33 +0,0 @@
package inbound
import (
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
)
// PacketAdapter is a UDP Packet adapter for socks/redir/tun
type PacketAdapter struct {
C.UDPPacket
metadata *C.Metadata
}
// Metadata returns destination metadata
func (s *PacketAdapter) Metadata() *C.Metadata {
return s.metadata
}
// NewPacket is PacketAdapter generator
func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, netType C.NetWork) *PacketAdapter {
metadata := parseSocksAddr(target)
metadata.NetWork = netType
metadata.Type = source
if ip, port, err := parseAddr(packet.LocalAddr().String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return &PacketAdapter{
UDPPacket: packet,
metadata: metadata,
}
}

View File

@ -1,35 +0,0 @@
package inbound
import (
"net"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
)
// SocketAdapter is a adapter for socks and redir connection
type SocketAdapter struct {
net.Conn
metadata *C.Metadata
}
// Metadata return destination metadata
func (s *SocketAdapter) Metadata() *C.Metadata {
return s.metadata
}
// NewSocket is SocketAdapter generator
func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, netType C.NetWork) *SocketAdapter {
metadata := parseSocksAddr(target)
metadata.NetWork = netType
metadata.Type = source
if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
return &SocketAdapter{
Conn: conn,
metadata: metadata,
}
}

View File

@ -1,71 +0,0 @@
package inbound
import (
"net"
"net/http"
"strconv"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
)
func parseSocksAddr(target socks5.Addr) *C.Metadata {
metadata := &C.Metadata{
AddrType: int(target[0]),
}
switch target[0] {
case socks5.AtypDomainName:
metadata.Host = string(target[2 : 2+target[1]])
metadata.DstPort = strconv.Itoa((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
case socks5.AtypIPv4:
ip := net.IP(target[1 : 1+net.IPv4len])
metadata.DstIP = ip
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
case socks5.AtypIPv6:
ip := net.IP(target[1 : 1+net.IPv6len])
metadata.DstIP = ip
metadata.DstPort = strconv.Itoa((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
}
return metadata
}
func parseHTTPAddr(request *http.Request) *C.Metadata {
host := request.URL.Hostname()
port := request.URL.Port()
if port == "" {
port = "80"
}
metadata := &C.Metadata{
NetWork: C.TCP,
AddrType: C.AtypDomainName,
Host: host,
DstIP: nil,
DstPort: port,
}
ip := net.ParseIP(host)
if ip != nil {
switch {
case ip.To4() == nil:
metadata.AddrType = C.AtypIPv6
default:
metadata.AddrType = C.AtypIPv4
}
metadata.DstIP = ip
}
return metadata
}
func parseAddr(addr string) (net.IP, string, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, "", err
}
ip := net.ParseIP(host)
return ip, port, nil
}

View File

@ -1,204 +0,0 @@
package outbound
import (
"context"
"encoding/json"
"errors"
"net"
"net/http"
"time"
"github.com/Dreamacro/clash/common/queue"
C "github.com/Dreamacro/clash/constant"
)
var (
defaultURLTestTimeout = time.Second * 5
)
type Base struct {
name string
tp C.AdapterType
udp bool
}
func (b *Base) Name() string {
return b.name
}
func (b *Base) Type() C.AdapterType {
return b.tp
}
func (b *Base) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
return nil, nil, errors.New("no support")
}
func (b *Base) SupportUDP() bool {
return b.udp
}
func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": b.Type().String(),
})
}
func NewBase(name string, tp C.AdapterType, udp bool) *Base {
return &Base{name, tp, udp}
}
type conn struct {
net.Conn
chain C.Chain
}
func (c *conn) Chains() C.Chain {
return c.chain
}
func (c *conn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name())
}
func newConn(c net.Conn, a C.ProxyAdapter) C.Conn {
return &conn{c, []string{a.Name()}}
}
type packetConn struct {
net.PacketConn
chain C.Chain
}
func (c *packetConn) Chains() C.Chain {
return c.chain
}
func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name())
}
func newPacketConn(c net.PacketConn, a C.ProxyAdapter) C.PacketConn {
return &packetConn{c, []string{a.Name()}}
}
type Proxy struct {
C.ProxyAdapter
history *queue.Queue
alive bool
}
func (p *Proxy) Alive() bool {
return p.alive
}
func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
return p.DialContext(ctx, metadata)
}
func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
conn, err := p.ProxyAdapter.DialContext(ctx, metadata)
if err != nil {
p.alive = false
}
return conn, err
}
func (p *Proxy) DelayHistory() []C.DelayHistory {
queue := p.history.Copy()
histories := []C.DelayHistory{}
for _, item := range queue {
histories = append(histories, item.(C.DelayHistory))
}
return histories
}
// LastDelay return last history record. if proxy is not alive, return the max value of uint16.
func (p *Proxy) LastDelay() (delay uint16) {
var max uint16 = 0xffff
if !p.alive {
return max
}
last := p.history.Last()
if last == nil {
return max
}
history := last.(C.DelayHistory)
if history.Delay == 0 {
return max
}
return history.Delay
}
func (p *Proxy) MarshalJSON() ([]byte, error) {
inner, err := p.ProxyAdapter.MarshalJSON()
if err != nil {
return inner, err
}
mapping := map[string]interface{}{}
json.Unmarshal(inner, &mapping)
mapping["history"] = p.DelayHistory()
mapping["name"] = p.Name()
return json.Marshal(mapping)
}
// URLTest get the delay for the specified URL
func (p *Proxy) URLTest(ctx context.Context, url string) (t uint16, err error) {
defer func() {
p.alive = err == nil
record := C.DelayHistory{Time: time.Now()}
if err == nil {
record.Delay = t
}
p.history.Put(record)
if p.history.Len() > 10 {
p.history.Pop()
}
}()
addr, err := urlToMetadata(url)
if err != nil {
return
}
start := time.Now()
instance, err := p.DialContext(ctx, &addr)
if err != nil {
return
}
defer instance.Close()
req, err := http.NewRequest(http.MethodHead, url, nil)
if err != nil {
return
}
req = req.WithContext(ctx)
transport := &http.Transport{
Dial: func(string, string) (net.Conn, error) {
return instance, nil
},
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := http.Client{Transport: transport}
resp, err := client.Do(req)
if err != nil {
return
}
resp.Body.Close()
t = uint16(time.Since(start) / time.Millisecond)
return
}
func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{adapter, queue.New(10), true}
}

View File

@ -1,49 +0,0 @@
package outbound
import (
"context"
"net"
C "github.com/Dreamacro/clash/constant"
)
type Direct struct {
*Base
}
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
address := net.JoinHostPort(metadata.Host, metadata.DstPort)
if metadata.DstIP != nil {
address = net.JoinHostPort(metadata.DstIP.String(), metadata.DstPort)
}
c, err := dialContext(ctx, "tcp", address)
if err != nil {
return nil, err
}
tcpKeepAlive(c)
return newConn(c, d), nil
}
func (d *Direct) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
pc, err := net.ListenPacket("udp", "")
if err != nil {
return nil, nil, err
}
addr, err := resolveUDPAddr("udp", metadata.RemoteAddress())
if err != nil {
return nil, nil, err
}
return newPacketConn(pc, d), addr, nil
}
func NewDirect() *Direct {
return &Direct{
Base: &Base{
name: "DIRECT",
tp: C.Direct,
udp: true,
},
}
}

View File

@ -1,122 +0,0 @@
package outbound
import (
"bufio"
"context"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strconv"
C "github.com/Dreamacro/clash/constant"
)
type Http struct {
*Base
addr string
user string
pass string
tlsConfig *tls.Config
}
type HttpOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UserName string `proxy:"username,omitempty"`
Password string `proxy:"password,omitempty"`
TLS bool `proxy:"tls,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
}
func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", h.addr)
if err == nil && h.tlsConfig != nil {
cc := tls.Client(c, h.tlsConfig)
err = cc.Handshake()
c = cc
}
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
}
tcpKeepAlive(c)
if err := h.shakeHand(metadata, c); err != nil {
return nil, err
}
return newConn(c, h), nil
}
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
addr := metadata.RemoteAddress()
req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{
Host: addr,
},
Host: addr,
Header: http.Header{
"Proxy-Connection": []string{"Keep-Alive"},
},
}
if h.user != "" && h.pass != "" {
auth := h.user + ":" + h.pass
req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
}
if err := req.Write(rw); err != nil {
return err
}
resp, err := http.ReadResponse(bufio.NewReader(rw), req)
if err != nil {
return err
}
if resp.StatusCode == http.StatusOK {
return nil
}
if resp.StatusCode == http.StatusProxyAuthRequired {
return errors.New("HTTP need auth")
}
if resp.StatusCode == http.StatusMethodNotAllowed {
return errors.New("CONNECT method not allowed by proxy")
}
if resp.StatusCode >= http.StatusInternalServerError {
return errors.New(resp.Status)
}
return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode)
}
func NewHttp(option HttpOption) *Http {
var tlsConfig *tls.Config
if option.TLS {
tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
ServerName: option.Server,
}
}
return &Http{
Base: &Base{
name: option.Name,
tp: C.Http,
},
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
user: option.UserName,
pass: option.Password,
tlsConfig: tlsConfig,
}
}

View File

@ -1,64 +0,0 @@
package outbound
import (
"fmt"
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant"
)
func ParseProxy(mapping map[string]interface{}) (C.Proxy, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
proxyType, existType := mapping["type"].(string)
if !existType {
return nil, fmt.Errorf("Missing type")
}
var proxy C.ProxyAdapter
err := fmt.Errorf("Cannot parse")
switch proxyType {
case "ss":
ssOption := &ShadowSocksOption{}
err = decoder.Decode(mapping, ssOption)
if err != nil {
break
}
proxy, err = NewShadowSocks(*ssOption)
case "socks5":
socksOption := &Socks5Option{}
err = decoder.Decode(mapping, socksOption)
if err != nil {
break
}
proxy = NewSocks5(*socksOption)
case "http":
httpOption := &HttpOption{}
err = decoder.Decode(mapping, httpOption)
if err != nil {
break
}
proxy = NewHttp(*httpOption)
case "vmess":
vmessOption := &VmessOption{}
err = decoder.Decode(mapping, vmessOption)
if err != nil {
break
}
proxy, err = NewVmess(*vmessOption)
case "snell":
snellOption := &SnellOption{}
err = decoder.Decode(mapping, snellOption)
if err != nil {
break
}
proxy, err = NewSnell(*snellOption)
default:
return nil, fmt.Errorf("Unsupport proxy type: %s", proxyType)
}
if err != nil {
return nil, err
}
return NewProxy(proxy), nil
}

View File

@ -1,55 +0,0 @@
package outbound
import (
"context"
"io"
"net"
"time"
C "github.com/Dreamacro/clash/constant"
)
type Reject struct {
*Base
}
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
return newConn(&NopConn{}, r), nil
}
func NewReject() *Reject {
return &Reject{
Base: &Base{
name: "REJECT",
tp: C.Reject,
},
}
}
type NopConn struct{}
func (rw *NopConn) Read(b []byte) (int, error) {
return 0, io.EOF
}
func (rw *NopConn) Write(b []byte) (int, error) {
return 0, io.EOF
}
// Close is fake function for net.Conn
func (rw *NopConn) Close() error { return nil }
// LocalAddr is fake function for net.Conn
func (rw *NopConn) LocalAddr() net.Addr { return nil }
// RemoteAddr is fake function for net.Conn
func (rw *NopConn) RemoteAddr() net.Addr { return nil }
// SetDeadline is fake function for net.Conn
func (rw *NopConn) SetDeadline(time.Time) error { return nil }
// SetReadDeadline is fake function for net.Conn
func (rw *NopConn) SetReadDeadline(time.Time) error { return nil }
// SetWriteDeadline is fake function for net.Conn
func (rw *NopConn) SetWriteDeadline(time.Time) error { return nil }

View File

@ -1,213 +0,0 @@
package outbound
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net"
"strconv"
"github.com/Dreamacro/clash/common/structure"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
"github.com/Dreamacro/clash/component/socks5"
v2rayObfs "github.com/Dreamacro/clash/component/v2ray-plugin"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/go-shadowsocks2/core"
)
type ShadowSocks struct {
*Base
server string
cipher core.Cipher
// obfs
obfsMode string
obfsOption *simpleObfsOption
v2rayOption *v2rayObfs.Option
}
type ShadowSocksOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"`
Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]interface{} `proxy:"plugin-opts,omitempty"`
// deprecated when bump to 1.0
Obfs string `proxy:"obfs,omitempty"`
ObfsHost string `proxy:"obfs-host,omitempty"`
}
type simpleObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
}
type v2rayObfsOption struct {
Mode string `obfs:"mode"`
Host string `obfs:"host,omitempty"`
Path string `obfs:"path,omitempty"`
TLS bool `obfs:"tls,omitempty"`
Headers map[string]string `obfs:"headers,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Mux bool `obfs:"mux,omitempty"`
}
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", ss.server)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.server, err)
}
tcpKeepAlive(c)
switch ss.obfsMode {
case "tls":
c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
case "http":
_, port, _ := net.SplitHostPort(ss.server)
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
case "websocket":
var err error
c, err = v2rayObfs.NewV2rayObfs(c, ss.v2rayOption)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.server, err)
}
}
c = ss.cipher.StreamConn(c)
_, err = c.Write(serializesSocksAddr(metadata))
return newConn(c, ss), err
}
func (ss *ShadowSocks) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
pc, err := net.ListenPacket("udp", "")
if err != nil {
return nil, nil, err
}
addr, err := resolveUDPAddr("udp", ss.server)
if err != nil {
return nil, nil, err
}
targetAddr := socks5.ParseAddr(metadata.RemoteAddress())
if targetAddr == nil {
return nil, nil, fmt.Errorf("parse address %s error: %s", metadata.String(), metadata.DstPort)
}
pc = ss.cipher.PacketConn(pc)
return newPacketConn(&ssUDPConn{PacketConn: pc, rAddr: targetAddr}, ss), addr, nil
}
func (ss *ShadowSocks) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{
"type": ss.Type().String(),
})
}
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
cipher := option.Cipher
password := option.Password
ciph, err := core.PickCipher(cipher, nil, password)
if err != nil {
return nil, fmt.Errorf("ss %s initialize error: %w", server, err)
}
var v2rayOption *v2rayObfs.Option
var obfsOption *simpleObfsOption
obfsMode := ""
// forward compatibility before 1.0
if option.Obfs != "" {
obfsMode = option.Obfs
obfsOption = &simpleObfsOption{
Host: "bing.com",
}
if option.ObfsHost != "" {
obfsOption.Host = option.ObfsHost
}
}
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
if option.Plugin == "obfs" {
opts := simpleObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize obfs error: %w", server, err)
}
if opts.Mode != "tls" && opts.Mode != "http" {
return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode)
}
obfsMode = opts.Mode
obfsOption = &opts
} else if option.Plugin == "v2ray-plugin" {
opts := v2rayObfsOption{Host: "bing.com", Mux: true}
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", server, err)
}
if opts.Mode != "websocket" {
return nil, fmt.Errorf("ss %s obfs mode error: %s", server, opts.Mode)
}
obfsMode = opts.Mode
var tlsConfig *tls.Config
if opts.TLS {
tlsConfig = &tls.Config{
ServerName: opts.Host,
InsecureSkipVerify: opts.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
}
}
v2rayOption = &v2rayObfs.Option{
Host: opts.Host,
Path: opts.Path,
Headers: opts.Headers,
TLSConfig: tlsConfig,
Mux: opts.Mux,
}
}
return &ShadowSocks{
Base: &Base{
name: option.Name,
tp: C.Shadowsocks,
udp: option.UDP,
},
server: server,
cipher: ciph,
obfsMode: obfsMode,
v2rayOption: v2rayOption,
obfsOption: obfsOption,
}, nil
}
type ssUDPConn struct {
net.PacketConn
rAddr socks5.Addr
}
func (uc *ssUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(uc.rAddr, b)
if err != nil {
return
}
return uc.PacketConn.WriteTo(packet[3:], addr)
}
func (uc *ssUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, _, e := uc.PacketConn.ReadFrom(b)
addr := socks5.SplitAddr(b[:n])
var from net.Addr
if e == nil {
// Get the source IP/Port of packet.
from = addr.UDPAddr()
}
copy(b, b[len(addr):])
return n - len(addr), from, e
}

View File

@ -1,72 +0,0 @@
package outbound
import (
"context"
"fmt"
"net"
"strconv"
"github.com/Dreamacro/clash/common/structure"
obfs "github.com/Dreamacro/clash/component/simple-obfs"
"github.com/Dreamacro/clash/component/snell"
C "github.com/Dreamacro/clash/constant"
)
type Snell struct {
*Base
server string
psk []byte
obfsOption *simpleObfsOption
}
type SnellOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Psk string `proxy:"psk"`
ObfsOpts map[string]interface{} `proxy:"obfs-opts,omitempty"`
}
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", s.server)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.server, err)
}
tcpKeepAlive(c)
switch s.obfsOption.Mode {
case "tls":
c = obfs.NewTLSObfs(c, s.obfsOption.Host)
case "http":
_, port, _ := net.SplitHostPort(s.server)
c = obfs.NewHTTPObfs(c, s.obfsOption.Host, port)
}
c = snell.StreamConn(c, s.psk)
port, _ := strconv.Atoi(metadata.DstPort)
err = snell.WriteHeader(c, metadata.String(), uint(port))
return newConn(c, s), err
}
func NewSnell(option SnellOption) (*Snell, error) {
server := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
psk := []byte(option.Psk)
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
obfsOption := &simpleObfsOption{Host: "bing.com"}
if err := decoder.Decode(option.ObfsOpts, obfsOption); err != nil {
return nil, fmt.Errorf("snell %s initialize obfs error: %w", server, err)
}
if obfsOption.Mode != "tls" && obfsOption.Mode != "http" {
return nil, fmt.Errorf("snell %s obfs mode error: %s", server, obfsOption.Mode)
}
return &Snell{
Base: &Base{
name: option.Name,
tp: C.Snell,
},
server: server,
psk: psk,
obfsOption: obfsOption,
}, nil
}

View File

@ -1,182 +0,0 @@
package outbound
import (
"context"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net"
"strconv"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
)
type Socks5 struct {
*Base
addr string
user string
pass string
tls bool
skipCertVerify bool
tlsConfig *tls.Config
}
type Socks5Option struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UserName string `proxy:"username,omitempty"`
Password string `proxy:"password,omitempty"`
TLS bool `proxy:"tls,omitempty"`
UDP bool `proxy:"udp,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
}
func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", ss.addr)
if err == nil && ss.tls {
cc := tls.Client(c, ss.tlsConfig)
err = cc.Handshake()
c = cc
}
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
tcpKeepAlive(c)
var user *socks5.User
if ss.user != "" {
user = &socks5.User{
Username: ss.user,
Password: ss.pass,
}
}
if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
return nil, err
}
return newConn(c, ss), nil
}
func (ss *Socks5) DialUDP(metadata *C.Metadata) (_ C.PacketConn, _ net.Addr, err error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
c, err := dialContext(ctx, "tcp", ss.addr)
if err != nil {
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
return
}
if ss.tls {
cc := tls.Client(c, ss.tlsConfig)
err = cc.Handshake()
c = cc
}
defer func() {
if err != nil {
c.Close()
}
}()
tcpKeepAlive(c)
var user *socks5.User
if ss.user != "" {
user = &socks5.User{
Username: ss.user,
Password: ss.pass,
}
}
bindAddr, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user)
if err != nil {
err = fmt.Errorf("client hanshake error: %w", err)
return
}
addr, err := net.ResolveUDPAddr("udp", bindAddr.String())
if err != nil {
return
}
targetAddr := socks5.ParseAddr(metadata.RemoteAddress())
if targetAddr == nil {
return nil, nil, fmt.Errorf("parse address %s error: %s", metadata.String(), metadata.DstPort)
}
pc, err := net.ListenPacket("udp", "")
if err != nil {
return
}
go func() {
io.Copy(ioutil.Discard, c)
c.Close()
// A UDP association terminates when the TCP connection that the UDP
// ASSOCIATE request arrived on terminates. RFC1928
pc.Close()
}()
return newPacketConn(&socksUDPConn{PacketConn: pc, rAddr: targetAddr, tcpConn: c}, ss), addr, nil
}
func NewSocks5(option Socks5Option) *Socks5 {
var tlsConfig *tls.Config
if option.TLS {
tlsConfig = &tls.Config{
InsecureSkipVerify: option.SkipCertVerify,
ClientSessionCache: getClientSessionCache(),
ServerName: option.Server,
}
}
return &Socks5{
Base: &Base{
name: option.Name,
tp: C.Socks5,
udp: option.UDP,
},
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
user: option.UserName,
pass: option.Password,
tls: option.TLS,
skipCertVerify: option.SkipCertVerify,
tlsConfig: tlsConfig,
}
}
type socksUDPConn struct {
net.PacketConn
rAddr socks5.Addr
tcpConn net.Conn
}
func (uc *socksUDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
packet, err := socks5.EncodeUDPPacket(uc.rAddr, b)
if err != nil {
return
}
return uc.PacketConn.WriteTo(packet, addr)
}
func (uc *socksUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, a, e := uc.PacketConn.ReadFrom(b)
if e != nil {
return 0, nil, e
}
addr, payload, err := socks5.DecodeUDPPacket(b)
if err != nil {
return 0, nil, err
}
// due to DecodeUDPPacket is mutable, record addr length
addrLength := len(addr)
copy(b, payload)
return n - addrLength - 3, a, nil
}
func (uc *socksUDPConn) Close() error {
uc.tcpConn.Close()
return uc.PacketConn.Close()
}

View File

@ -1,180 +0,0 @@
package outbound
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"net"
"net/url"
"strconv"
"sync"
"time"
"github.com/Dreamacro/clash/component/socks5"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
)
const (
tcpTimeout = 5 * time.Second
)
var (
globalClientSessionCache tls.ClientSessionCache
once sync.Once
)
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
u, err := url.Parse(rawURL)
if err != nil {
return
}
port := u.Port()
if port == "" {
switch u.Scheme {
case "https":
port = "443"
case "http":
port = "80"
default:
err = fmt.Errorf("%s scheme not Support", rawURL)
return
}
}
addr = C.Metadata{
AddrType: C.AtypDomainName,
Host: u.Hostname(),
DstIP: nil,
DstPort: port,
}
return
}
func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok {
tcp.SetKeepAlive(true)
tcp.SetKeepAlivePeriod(30 * time.Second)
}
}
func getClientSessionCache() tls.ClientSessionCache {
once.Do(func() {
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
})
return globalClientSessionCache
}
func serializesSocksAddr(metadata *C.Metadata) []byte {
var buf [][]byte
aType := uint8(metadata.AddrType)
p, _ := strconv.Atoi(metadata.DstPort)
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
switch metadata.AddrType {
case socks5.AtypDomainName:
len := uint8(len(metadata.Host))
host := []byte(metadata.Host)
buf = [][]byte{{aType, len}, host, port}
case socks5.AtypIPv4:
host := metadata.DstIP.To4()
buf = [][]byte{{aType}, host, port}
case socks5.AtypIPv6:
host := metadata.DstIP.To16()
buf = [][]byte{{aType}, host, port}
}
return bytes.Join(buf, nil)
}
func dialContext(ctx context.Context, network, address string) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
returned := make(chan struct{})
defer close(returned)
type dialResult struct {
net.Conn
error
resolved bool
ipv6 bool
done bool
}
results := make(chan dialResult)
var primary, fallback dialResult
startRacer := func(ctx context.Context, host string, ipv6 bool) {
dialer := net.Dialer{}
result := dialResult{ipv6: ipv6, done: true}
defer func() {
select {
case results <- result:
case <-returned:
if result.Conn != nil {
result.Conn.Close()
}
}
}()
var ip net.IP
if ipv6 {
ip, result.error = dns.ResolveIPv6(host)
} else {
ip, result.error = dns.ResolveIPv4(host)
}
if result.error != nil {
return
}
result.resolved = true
if ipv6 {
result.Conn, result.error = dialer.DialContext(ctx, "tcp6", net.JoinHostPort(ip.String(), port))
} else {
result.Conn, result.error = dialer.DialContext(ctx, "tcp4", net.JoinHostPort(ip.String(), port))
}
}
go startRacer(ctx, host, false)
go startRacer(ctx, host, true)
for {
select {
case res := <-results:
if res.error == nil {
return res.Conn, nil
}
if !res.ipv6 {
primary = res
} else {
fallback = res
}
if primary.done && fallback.done {
if primary.resolved {
return nil, primary.error
} else if fallback.resolved {
return nil, fallback.error
} else {
return nil, primary.error
}
}
}
}
}
func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
ip, err := dns.ResolveIP(host)
if err != nil {
return nil, err
}
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
}

View File

@ -1,129 +0,0 @@
package outbound
import (
"context"
"fmt"
"net"
"strconv"
"strings"
"github.com/Dreamacro/clash/component/vmess"
C "github.com/Dreamacro/clash/constant"
)
type Vmess struct {
*Base
server string
client *vmess.Client
}
type VmessOption struct {
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
UUID string `proxy:"uuid"`
AlterID int `proxy:"alterId"`
Cipher string `proxy:"cipher"`
TLS bool `proxy:"tls,omitempty"`
UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"`
WSPath string `proxy:"ws-path,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
}
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := dialContext(ctx, "tcp", v.server)
if err != nil {
return nil, fmt.Errorf("%s connect error", v.server)
}
tcpKeepAlive(c)
c, err = v.client.New(c, parseVmessAddr(metadata))
return newConn(c, v), err
}
func (v *Vmess) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
ctx, cancel := context.WithTimeout(context.Background(), tcpTimeout)
defer cancel()
c, err := dialContext(ctx, "tcp", v.server)
if err != nil {
return nil, nil, fmt.Errorf("%s connect error", v.server)
}
tcpKeepAlive(c)
c, err = v.client.New(c, parseVmessAddr(metadata))
if err != nil {
return nil, nil, fmt.Errorf("new vmess client error: %v", err)
}
return newPacketConn(&vmessUDPConn{Conn: c}, v), c.RemoteAddr(), nil
}
func NewVmess(option VmessOption) (*Vmess, error) {
security := strings.ToLower(option.Cipher)
client, err := vmess.NewClient(vmess.Config{
UUID: option.UUID,
AlterID: uint16(option.AlterID),
Security: security,
TLS: option.TLS,
HostName: option.Server,
Port: strconv.Itoa(option.Port),
NetWork: option.Network,
WebSocketPath: option.WSPath,
WebSocketHeaders: option.WSHeaders,
SkipCertVerify: option.SkipCertVerify,
SessionCache: getClientSessionCache(),
})
if err != nil {
return nil, err
}
return &Vmess{
Base: &Base{
name: option.Name,
tp: C.Vmess,
udp: true,
},
server: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
client: client,
}, nil
}
func parseVmessAddr(metadata *C.Metadata) *vmess.DstAddr {
var addrType byte
var addr []byte
switch metadata.AddrType {
case C.AtypIPv4:
addrType = byte(vmess.AtypIPv4)
addr = make([]byte, net.IPv4len)
copy(addr[:], metadata.DstIP.To4())
case C.AtypIPv6:
addrType = byte(vmess.AtypIPv6)
addr = make([]byte, net.IPv6len)
copy(addr[:], metadata.DstIP.To16())
case C.AtypDomainName:
addrType = byte(vmess.AtypDomainName)
addr = make([]byte, len(metadata.Host)+1)
addr[0] = byte(len(metadata.Host))
copy(addr[1:], []byte(metadata.Host))
}
port, _ := strconv.Atoi(metadata.DstPort)
return &vmess.DstAddr{
UDP: metadata.NetWork == C.UDP,
AddrType: addrType,
Addr: addr,
Port: uint(port),
}
}
type vmessUDPConn struct {
net.Conn
}
func (uc *vmessUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
return uc.Conn.Write(b)
}
func (uc *vmessUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
n, err := uc.Conn.Read(b)
return n, uc.RemoteAddr(), err
}

View File

@ -1,20 +0,0 @@
package outboundgroup
import (
"time"
"github.com/Dreamacro/clash/adapters/provider"
C "github.com/Dreamacro/clash/constant"
)
const (
defaultGetProxiesDuration = time.Second * 5
)
func getProvidersProxies(providers []provider.ProxyProvider) []C.Proxy {
proxies := []C.Proxy{}
for _, provider := range providers {
proxies = append(proxies, provider.Proxies()...)
}
return proxies
}

View File

@ -1,85 +0,0 @@
package outboundgroup
import (
"context"
"encoding/json"
"net"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant"
)
type Fallback struct {
*outbound.Base
single *singledo.Single
providers []provider.ProxyProvider
}
func (f *Fallback) Now() string {
proxy := f.findAliveProxy()
return proxy.Name()
}
func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
proxy := f.findAliveProxy()
c, err := proxy.DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(f)
}
return c, err
}
func (f *Fallback) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
proxy := f.findAliveProxy()
pc, addr, err := proxy.DialUDP(metadata)
if err == nil {
pc.AppendToChains(f)
}
return pc, addr, err
}
func (f *Fallback) SupportUDP() bool {
proxy := f.findAliveProxy()
return proxy.SupportUDP()
}
func (f *Fallback) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range f.proxies() {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": f.Type().String(),
"now": f.Now(),
"all": all,
})
}
func (f *Fallback) proxies() []C.Proxy {
elm, _, _ := f.single.Do(func() (interface{}, error) {
return getProvidersProxies(f.providers), nil
})
return elm.([]C.Proxy)
}
func (f *Fallback) findAliveProxy() C.Proxy {
proxies := f.proxies()
for _, proxy := range proxies {
if proxy.Alive() {
return proxy
}
}
return f.proxies()[0]
}
func NewFallback(name string, providers []provider.ProxyProvider) *Fallback {
return &Fallback{
Base: outbound.NewBase(name, C.Fallback, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
}
}

View File

@ -1,128 +0,0 @@
package outboundgroup
import (
"context"
"encoding/json"
"net"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/murmur3"
"github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant"
"golang.org/x/net/publicsuffix"
)
type LoadBalance struct {
*outbound.Base
single *singledo.Single
maxRetry int
providers []provider.ProxyProvider
}
func getKey(metadata *C.Metadata) string {
if metadata.Host != "" {
// ip host
if ip := net.ParseIP(metadata.Host); ip != nil {
return metadata.Host
}
if etld, err := publicsuffix.EffectiveTLDPlusOne(metadata.Host); err == nil {
return etld
}
}
if metadata.DstIP == nil {
return ""
}
return metadata.DstIP.String()
}
func jumpHash(key uint64, buckets int32) int32 {
var b, j int64
for j < int64(buckets) {
b = j
key = key*2862933555777941757 + 1
j = int64(float64(b+1) * (float64(int64(1)<<31) / float64((key>>33)+1)))
}
return int32(b)
}
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
defer func() {
if err == nil {
c.AppendToChains(lb)
}
}()
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
proxies := lb.proxies()
buckets := int32(len(proxies))
for i := 0; i < lb.maxRetry; i, key = i+1, key+1 {
idx := jumpHash(key, buckets)
proxy := proxies[idx]
if proxy.Alive() {
c, err = proxy.DialContext(ctx, metadata)
return
}
}
c, err = proxies[0].DialContext(ctx, metadata)
return
}
func (lb *LoadBalance) DialUDP(metadata *C.Metadata) (pc C.PacketConn, addr net.Addr, err error) {
defer func() {
if err == nil {
pc.AppendToChains(lb)
}
}()
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
proxies := lb.proxies()
buckets := int32(len(proxies))
for i := 0; i < lb.maxRetry; i, key = i+1, key+1 {
idx := jumpHash(key, buckets)
proxy := proxies[idx]
if proxy.Alive() {
return proxy.DialUDP(metadata)
}
}
return proxies[0].DialUDP(metadata)
}
func (lb *LoadBalance) SupportUDP() bool {
return true
}
func (lb *LoadBalance) proxies() []C.Proxy {
elm, _, _ := lb.single.Do(func() (interface{}, error) {
return getProvidersProxies(lb.providers), nil
})
return elm.([]C.Proxy)
}
func (lb *LoadBalance) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range lb.proxies() {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": lb.Type().String(),
"all": all,
})
}
func NewLoadBalance(name string, providers []provider.ProxyProvider) *LoadBalance {
return &LoadBalance{
Base: outbound.NewBase(name, C.LoadBalance, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
maxRetry: 3,
providers: providers,
}
}

View File

@ -1,144 +0,0 @@
package outboundgroup
import (
"errors"
"fmt"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant"
)
var (
errFormat = errors.New("format error")
errType = errors.New("unsupport type")
errMissUse = errors.New("`use` field should not be empty")
errMissProxy = errors.New("`use` or `proxies` missing")
errMissHealthCheck = errors.New("`url` or `interval` missing")
errDuplicateProvider = errors.New("`duplicate provider name")
)
type GroupCommonOption struct {
Name string `group:"name"`
Type string `group:"type"`
Proxies []string `group:"proxies,omitempty"`
Use []string `group:"use,omitempty"`
URL string `group:"url,omitempty"`
Interval int `group:"interval,omitempty"`
}
func ParseProxyGroup(config map[string]interface{}, proxyMap map[string]C.Proxy, providersMap map[string]provider.ProxyProvider) (C.ProxyAdapter, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true})
groupOption := &GroupCommonOption{}
if err := decoder.Decode(config, groupOption); err != nil {
return nil, errFormat
}
if groupOption.Type == "" || groupOption.Name == "" {
return nil, errFormat
}
groupName := groupOption.Name
providers := []provider.ProxyProvider{}
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
return nil, errMissProxy
}
if len(groupOption.Proxies) != 0 {
ps, err := getProxies(proxyMap, groupOption.Proxies)
if err != nil {
return nil, err
}
// if Use not empty, drop health check options
if len(groupOption.Use) != 0 {
hc := provider.NewHealthCheck(ps, "", 0)
pd, err := provider.NewCompatibleProvier(groupName, ps, hc)
if err != nil {
return nil, err
}
providers = append(providers, pd)
} else {
// select don't need health check
if groupOption.Type == "select" {
hc := provider.NewHealthCheck(ps, "", 0)
pd, err := provider.NewCompatibleProvier(groupName, ps, hc)
if err != nil {
return nil, err
}
providers = append(providers, pd)
providersMap[groupName] = pd
} else {
if groupOption.URL == "" || groupOption.Interval == 0 {
return nil, errMissHealthCheck
}
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval))
pd, err := provider.NewCompatibleProvier(groupName, ps, hc)
if err != nil {
return nil, err
}
providers = append(providers, pd)
providersMap[groupName] = pd
}
}
}
if len(groupOption.Use) != 0 {
list, err := getProviders(providersMap, groupOption.Use)
if err != nil {
return nil, err
}
providers = append(providers, list...)
}
var group C.ProxyAdapter
switch groupOption.Type {
case "url-test":
group = NewURLTest(groupName, providers)
case "select":
group = NewSelector(groupName, providers)
case "fallback":
group = NewFallback(groupName, providers)
case "load-balance":
group = NewLoadBalance(groupName, providers)
default:
return nil, fmt.Errorf("%w: %s", errType, groupOption.Type)
}
return group, nil
}
func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) {
var ps []C.Proxy
for _, name := range list {
p, ok := mapping[name]
if !ok {
return nil, fmt.Errorf("'%s' not found", name)
}
ps = append(ps, p)
}
return ps, nil
}
func getProviders(mapping map[string]provider.ProxyProvider, list []string) ([]provider.ProxyProvider, error) {
var ps []provider.ProxyProvider
for _, name := range list {
p, ok := mapping[name]
if !ok {
return nil, fmt.Errorf("'%s' not found", name)
}
if p.VehicleType() == provider.Compatible {
return nil, fmt.Errorf("proxy group %s can't contains in `use`", name)
}
ps = append(ps, p)
}
return ps, nil
}

View File

@ -1,86 +0,0 @@
package outboundgroup
import (
"context"
"encoding/json"
"errors"
"net"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant"
)
type Selector struct {
*outbound.Base
single *singledo.Single
selected C.Proxy
providers []provider.ProxyProvider
}
func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
c, err := s.selected.DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(s)
}
return c, err
}
func (s *Selector) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
pc, addr, err := s.selected.DialUDP(metadata)
if err == nil {
pc.AppendToChains(s)
}
return pc, addr, err
}
func (s *Selector) SupportUDP() bool {
return s.selected.SupportUDP()
}
func (s *Selector) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range s.proxies() {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": s.Type().String(),
"now": s.Now(),
"all": all,
})
}
func (s *Selector) Now() string {
return s.selected.Name()
}
func (s *Selector) Set(name string) error {
for _, proxy := range s.proxies() {
if proxy.Name() == name {
s.selected = proxy
return nil
}
}
return errors.New("Proxy does not exist")
}
func (s *Selector) proxies() []C.Proxy {
elm, _, _ := s.single.Do(func() (interface{}, error) {
return getProvidersProxies(s.providers), nil
})
return elm.([]C.Proxy)
}
func NewSelector(name string, providers []provider.ProxyProvider) *Selector {
selected := providers[0].Proxies()[0]
return &Selector{
Base: outbound.NewBase(name, C.Selector, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
providers: providers,
selected: selected,
}
}

View File

@ -1,95 +0,0 @@
package outboundgroup
import (
"context"
"encoding/json"
"net"
"time"
"github.com/Dreamacro/clash/adapters/outbound"
"github.com/Dreamacro/clash/adapters/provider"
"github.com/Dreamacro/clash/common/singledo"
C "github.com/Dreamacro/clash/constant"
)
type URLTest struct {
*outbound.Base
single *singledo.Single
fastSingle *singledo.Single
providers []provider.ProxyProvider
}
func (u *URLTest) Now() string {
return u.fast().Name()
}
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) {
c, err = u.fast().DialContext(ctx, metadata)
if err == nil {
c.AppendToChains(u)
}
return c, err
}
func (u *URLTest) DialUDP(metadata *C.Metadata) (C.PacketConn, net.Addr, error) {
pc, addr, err := u.fast().DialUDP(metadata)
if err == nil {
pc.AppendToChains(u)
}
return pc, addr, err
}
func (u *URLTest) proxies() []C.Proxy {
elm, _, _ := u.single.Do(func() (interface{}, error) {
return getProvidersProxies(u.providers), nil
})
return elm.([]C.Proxy)
}
func (u *URLTest) fast() C.Proxy {
elm, _, _ := u.fastSingle.Do(func() (interface{}, error) {
proxies := u.proxies()
fast := proxies[0]
min := fast.LastDelay()
for _, proxy := range proxies[1:] {
if !proxy.Alive() {
continue
}
delay := proxy.LastDelay()
if delay < min {
fast = proxy
min = delay
}
}
return fast, nil
})
return elm.(C.Proxy)
}
func (u *URLTest) SupportUDP() bool {
return u.fast().SupportUDP()
}
func (u *URLTest) MarshalJSON() ([]byte, error) {
var all []string
for _, proxy := range u.proxies() {
all = append(all, proxy.Name())
}
return json.Marshal(map[string]interface{}{
"type": u.Type().String(),
"now": u.Now(),
"all": all,
})
}
func NewURLTest(name string, providers []provider.ProxyProvider) *URLTest {
return &URLTest{
Base: outbound.NewBase(name, C.URLTest, false),
single: singledo.NewSingle(defaultGetProxiesDuration),
fastSingle: singledo.NewSingle(time.Second * 10),
providers: providers,
}
}

View File

@ -1,70 +0,0 @@
package provider
import (
"context"
"time"
C "github.com/Dreamacro/clash/constant"
)
const (
defaultURLTestTimeout = time.Second * 5
)
type HealthCheckOption struct {
URL string
Interval uint
}
type HealthCheck struct {
url string
proxies []C.Proxy
interval uint
done chan struct{}
}
func (hc *HealthCheck) process() {
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
go hc.check()
for {
select {
case <-ticker.C:
hc.check()
case <-hc.done:
ticker.Stop()
return
}
}
}
func (hc *HealthCheck) setProxy(proxies []C.Proxy) {
hc.proxies = proxies
}
func (hc *HealthCheck) auto() bool {
return hc.interval != 0
}
func (hc *HealthCheck) check() {
ctx, cancel := context.WithTimeout(context.Background(), defaultURLTestTimeout)
for _, proxy := range hc.proxies {
go proxy.URLTest(ctx, hc.url)
}
<-ctx.Done()
cancel()
}
func (hc *HealthCheck) close() {
hc.done <- struct{}{}
}
func NewHealthCheck(proxies []C.Proxy, url string, interval uint) *HealthCheck {
return &HealthCheck{
proxies: proxies,
url: url,
interval: interval,
done: make(chan struct{}, 1),
}
}

View File

@ -1,58 +0,0 @@
package provider
import (
"errors"
"fmt"
"time"
"github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant"
)
var (
errVehicleType = errors.New("unsupport vehicle type")
)
type healthCheckSchema struct {
Enable bool `provider:"enable"`
URL string `provider:"url"`
Interval int `provider:"interval"`
}
type proxyProviderSchema struct {
Type string `provider:"type"`
Path string `provider:"path"`
URL string `provider:"url,omitempty"`
Interval int `provider:"interval,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
}
func ParseProxyProvider(name string, mapping map[string]interface{}) (ProxyProvider, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
schema := &proxyProviderSchema{}
if err := decoder.Decode(mapping, schema); err != nil {
return nil, err
}
var hcInterval uint = 0
if schema.HealthCheck.Enable {
hcInterval = uint(schema.HealthCheck.Interval)
}
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval)
path := C.Path.Reslove(schema.Path)
var vehicle Vehicle
switch schema.Type {
case "file":
vehicle = NewFileVehicle(path)
case "http":
vehicle = NewHTTPVehicle(schema.URL, path)
default:
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
}
interval := time.Duration(uint(schema.Interval)) * time.Second
return NewProxySetProvider(name, interval, vehicle, hc), nil
}

View File

@ -1,318 +0,0 @@
package provider
import (
"bytes"
"crypto/md5"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"time"
"github.com/Dreamacro/clash/adapters/outbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"gopkg.in/yaml.v2"
)
const (
ReservedName = "default"
fileMode = 0666
)
// Provider Type
const (
Proxy ProviderType = iota
Rule
)
// ProviderType defined
type ProviderType int
func (pt ProviderType) String() string {
switch pt {
case Proxy:
return "Proxy"
case Rule:
return "Rule"
default:
return "Unknown"
}
}
// Provider interface
type Provider interface {
Name() string
VehicleType() VehicleType
Type() ProviderType
Initial() error
Reload() error
Destroy() error
}
// ProxyProvider interface
type ProxyProvider interface {
Provider
Proxies() []C.Proxy
HealthCheck()
Update() error
}
type ProxySchema struct {
Proxies []map[string]interface{} `yaml:"proxies"`
}
type ProxySetProvider struct {
name string
vehicle Vehicle
hash [16]byte
proxies []C.Proxy
healthCheck *HealthCheck
ticker *time.Ticker
updatedAt *time.Time
}
func (pp *ProxySetProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": pp.Name(),
"type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(),
"updatedAt": pp.updatedAt,
})
}
func (pp *ProxySetProvider) Name() string {
return pp.name
}
func (pp *ProxySetProvider) Reload() error {
return nil
}
func (pp *ProxySetProvider) HealthCheck() {
pp.healthCheck.check()
}
func (pp *ProxySetProvider) Update() error {
return pp.pull()
}
func (pp *ProxySetProvider) Destroy() error {
pp.healthCheck.close()
if pp.ticker != nil {
pp.ticker.Stop()
}
return nil
}
func (pp *ProxySetProvider) Initial() error {
var buf []byte
var err error
if stat, err := os.Stat(pp.vehicle.Path()); err == nil {
buf, err = ioutil.ReadFile(pp.vehicle.Path())
modTime := stat.ModTime()
pp.updatedAt = &modTime
} else {
buf, err = pp.vehicle.Read()
}
if err != nil {
return err
}
proxies, err := pp.parse(buf)
if err != nil {
return err
}
if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil {
return err
}
pp.hash = md5.Sum(buf)
pp.setProxies(proxies)
// pull proxies automatically
if pp.ticker != nil {
go pp.pullLoop()
}
return nil
}
func (pp *ProxySetProvider) VehicleType() VehicleType {
return pp.vehicle.Type()
}
func (pp *ProxySetProvider) Type() ProviderType {
return Proxy
}
func (pp *ProxySetProvider) Proxies() []C.Proxy {
return pp.proxies
}
func (pp *ProxySetProvider) pullLoop() {
for range pp.ticker.C {
if err := pp.pull(); err != nil {
log.Warnln("[Provider] %s pull error: %s", pp.Name(), err.Error())
}
}
}
func (pp *ProxySetProvider) pull() error {
buf, err := pp.vehicle.Read()
if err != nil {
return err
}
now := time.Now()
hash := md5.Sum(buf)
if bytes.Equal(pp.hash[:], hash[:]) {
log.Debugln("[Provider] %s's proxies doesn't change", pp.Name())
pp.updatedAt = &now
return nil
}
proxies, err := pp.parse(buf)
if err != nil {
return err
}
log.Infoln("[Provider] %s's proxies update", pp.Name())
if err := ioutil.WriteFile(pp.vehicle.Path(), buf, fileMode); err != nil {
return err
}
pp.updatedAt = &now
pp.hash = hash
pp.setProxies(proxies)
return nil
}
func (pp *ProxySetProvider) parse(buf []byte) ([]C.Proxy, error) {
schema := &ProxySchema{}
if err := yaml.Unmarshal(buf, schema); err != nil {
return nil, err
}
if schema.Proxies == nil {
return nil, errors.New("File must have a `proxies` field")
}
proxies := []C.Proxy{}
for idx, mapping := range schema.Proxies {
proxy, err := outbound.ParseProxy(mapping)
if err != nil {
return nil, fmt.Errorf("Proxy %d error: %w", idx, err)
}
proxies = append(proxies, proxy)
}
if len(proxies) == 0 {
return nil, errors.New("File doesn't have any valid proxy")
}
return proxies, nil
}
func (pp *ProxySetProvider) setProxies(proxies []C.Proxy) {
pp.proxies = proxies
pp.healthCheck.setProxy(proxies)
go pp.healthCheck.check()
}
func NewProxySetProvider(name string, interval time.Duration, vehicle Vehicle, hc *HealthCheck) *ProxySetProvider {
var ticker *time.Ticker
if interval != 0 {
ticker = time.NewTicker(interval)
}
if hc.auto() {
go hc.process()
}
return &ProxySetProvider{
name: name,
vehicle: vehicle,
proxies: []C.Proxy{},
healthCheck: hc,
ticker: ticker,
}
}
type CompatibleProvier struct {
name string
healthCheck *HealthCheck
proxies []C.Proxy
}
func (cp *CompatibleProvier) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": cp.Name(),
"type": cp.Type().String(),
"vehicleType": cp.VehicleType().String(),
"proxies": cp.Proxies(),
})
}
func (cp *CompatibleProvier) Name() string {
return cp.name
}
func (cp *CompatibleProvier) Reload() error {
return nil
}
func (cp *CompatibleProvier) Destroy() error {
cp.healthCheck.close()
return nil
}
func (cp *CompatibleProvier) HealthCheck() {
cp.healthCheck.check()
}
func (cp *CompatibleProvier) Update() error {
return nil
}
func (cp *CompatibleProvier) Initial() error {
return nil
}
func (cp *CompatibleProvier) VehicleType() VehicleType {
return Compatible
}
func (cp *CompatibleProvier) Type() ProviderType {
return Proxy
}
func (cp *CompatibleProvier) Proxies() []C.Proxy {
return cp.proxies
}
func NewCompatibleProvier(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvier, error) {
if len(proxies) == 0 {
return nil, errors.New("Provider need one proxy at least")
}
if hc.auto() {
go hc.process()
}
return &CompatibleProvier{
name: name,
proxies: proxies,
healthCheck: hc,
}, nil
}

View File

@ -1,109 +0,0 @@
package provider
import (
"context"
"io/ioutil"
"net/http"
"time"
)
// Vehicle Type
const (
File VehicleType = iota
HTTP
Compatible
)
// VehicleType defined
type VehicleType int
func (v VehicleType) String() string {
switch v {
case File:
return "File"
case HTTP:
return "HTTP"
case Compatible:
return "Compatible"
default:
return "Unknown"
}
}
type Vehicle interface {
Read() ([]byte, error)
Path() string
Type() VehicleType
}
type FileVehicle struct {
path string
}
func (f *FileVehicle) Type() VehicleType {
return File
}
func (f *FileVehicle) Path() string {
return f.path
}
func (f *FileVehicle) Read() ([]byte, error) {
return ioutil.ReadFile(f.path)
}
func NewFileVehicle(path string) *FileVehicle {
return &FileVehicle{path: path}
}
type HTTPVehicle struct {
url string
path string
}
func (h *HTTPVehicle) Type() VehicleType {
return HTTP
}
func (h *HTTPVehicle) Path() string {
return h.path
}
func (h *HTTPVehicle) Read() ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
req, err := http.NewRequest(http.MethodGet, h.url, nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
transport := &http.Transport{
// from http.DefaultTransport
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
client := http.Client{Transport: transport}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
buf, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if err := ioutil.WriteFile(h.path, buf, fileMode); err != nil {
return nil, err
}
return buf, nil
}
func NewHTTPVehicle(url string, path string) *HTTPVehicle {
return &HTTPVehicle{url, path}
}

28
check_amd64.sh Normal file
View File

@ -0,0 +1,28 @@
#!/bin/sh
flags=$(grep '^flags\b' </proc/cpuinfo | head -n 1)
flags=" ${flags#*:} "
has_flags () {
for flag; do
case "$flags" in
*" $flag "*) :;;
*) return 1;;
esac
done
}
determine_level () {
level=0
has_flags lm cmov cx8 fpu fxsr mmx syscall sse2 || return 0
level=1
has_flags cx16 lahf_lm popcnt sse4_1 sse4_2 ssse3 || return 0
level=2
has_flags avx avx2 bmi1 bmi2 f16c fma abm movbe xsave || return 0
level=3
has_flags avx512f avx512bw avx512cd avx512dq avx512vl || return 0
level=4
}
determine_level
echo "Your CPU supports amd64-v$level"
return $level

198
common/atomic/type.go Normal file
View File

@ -0,0 +1,198 @@
package atomic
import (
"encoding/json"
"fmt"
"strconv"
"sync/atomic"
)
type Bool struct {
atomic.Bool
}
func NewBool(val bool) (i Bool) {
i.Store(val)
return
}
func (i *Bool) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Load())
}
func (i *Bool) UnmarshalJSON(b []byte) error {
var v bool
if err := json.Unmarshal(b, &v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Bool) String() string {
v := i.Load()
return strconv.FormatBool(v)
}
type Pointer[T any] struct {
atomic.Pointer[T]
}
func NewPointer[T any](v *T) (p Pointer[T]) {
if v != nil {
p.Store(v)
}
return
}
func (p *Pointer[T]) MarshalJSON() ([]byte, error) {
return json.Marshal(p.Load())
}
func (p *Pointer[T]) UnmarshalJSON(b []byte) error {
var v *T
if err := json.Unmarshal(b, &v); err != nil {
return err
}
p.Store(v)
return nil
}
func (p *Pointer[T]) String() string {
return fmt.Sprint(p.Load())
}
type Int32 struct {
atomic.Int32
}
func NewInt32(val int32) (i Int32) {
i.Store(val)
return
}
func (i *Int32) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Load())
}
func (i *Int32) UnmarshalJSON(b []byte) error {
var v int32
if err := json.Unmarshal(b, &v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Int32) String() string {
v := i.Load()
return strconv.FormatInt(int64(v), 10)
}
type Int64 struct {
atomic.Int64
}
func NewInt64(val int64) (i Int64) {
i.Store(val)
return
}
func (i *Int64) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Load())
}
func (i *Int64) UnmarshalJSON(b []byte) error {
var v int64
if err := json.Unmarshal(b, &v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Int64) String() string {
v := i.Load()
return strconv.FormatInt(int64(v), 10)
}
type Uint32 struct {
atomic.Uint32
}
func NewUint32(val uint32) (i Uint32) {
i.Store(val)
return
}
func (i *Uint32) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Load())
}
func (i *Uint32) UnmarshalJSON(b []byte) error {
var v uint32
if err := json.Unmarshal(b, &v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Uint32) String() string {
v := i.Load()
return strconv.FormatUint(uint64(v), 10)
}
type Uint64 struct {
atomic.Uint64
}
func NewUint64(val uint64) (i Uint64) {
i.Store(val)
return
}
func (i *Uint64) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Load())
}
func (i *Uint64) UnmarshalJSON(b []byte) error {
var v uint64
if err := json.Unmarshal(b, &v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Uint64) String() string {
v := i.Load()
return strconv.FormatUint(uint64(v), 10)
}
type Uintptr struct {
atomic.Uintptr
}
func NewUintptr(val uintptr) (i Uintptr) {
i.Store(val)
return
}
func (i *Uintptr) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Load())
}
func (i *Uintptr) UnmarshalJSON(b []byte) error {
var v uintptr
if err := json.Unmarshal(b, &v); err != nil {
return err
}
i.Store(v)
return nil
}
func (i *Uintptr) String() string {
v := i.Load()
return strconv.FormatUint(uint64(v), 10)
}

57
common/atomic/value.go Normal file
View File

@ -0,0 +1,57 @@
package atomic
import (
"encoding/json"
"sync/atomic"
)
func DefaultValue[T any]() T {
var defaultValue T
return defaultValue
}
type TypedValue[T any] struct {
value atomic.Value
}
func (t *TypedValue[T]) Load() T {
value := t.value.Load()
if value == nil {
return DefaultValue[T]()
}
return value.(T)
}
func (t *TypedValue[T]) Store(value T) {
t.value.Store(value)
}
func (t *TypedValue[T]) Swap(new T) T {
old := t.value.Swap(new)
if old == nil {
return DefaultValue[T]()
}
return old.(T)
}
func (t *TypedValue[T]) CompareAndSwap(old, new T) bool {
return t.value.CompareAndSwap(old, new)
}
func (t *TypedValue[T]) MarshalJSON() ([]byte, error) {
return json.Marshal(t.Load())
}
func (t *TypedValue[T]) UnmarshalJSON(b []byte) error {
var v T
if err := json.Unmarshal(b, &v); err != nil {
return err
}
t.Store(v)
return nil
}
func NewTypedValue[T any](t T) (v TypedValue[T]) {
v.Store(t)
return
}

105
common/batch/batch.go Normal file
View File

@ -0,0 +1,105 @@
package batch
import (
"context"
"sync"
)
type Option[T any] func(b *Batch[T])
type Result[T any] struct {
Value T
Err error
}
type Error struct {
Key string
Err error
}
func WithConcurrencyNum[T any](n int) Option[T] {
return func(b *Batch[T]) {
q := make(chan struct{}, n)
for i := 0; i < n; i++ {
q <- struct{}{}
}
b.queue = q
}
}
// Batch similar to errgroup, but can control the maximum number of concurrent
type Batch[T any] struct {
result map[string]Result[T]
queue chan struct{}
wg sync.WaitGroup
mux sync.Mutex
err *Error
once sync.Once
cancel func()
}
func (b *Batch[T]) Go(key string, fn func() (T, error)) {
b.wg.Add(1)
go func() {
defer b.wg.Done()
if b.queue != nil {
<-b.queue
defer func() {
b.queue <- struct{}{}
}()
}
value, err := fn()
if err != nil {
b.once.Do(func() {
b.err = &Error{key, err}
if b.cancel != nil {
b.cancel()
}
})
}
ret := Result[T]{value, err}
b.mux.Lock()
defer b.mux.Unlock()
b.result[key] = ret
}()
}
func (b *Batch[T]) Wait() *Error {
b.wg.Wait()
if b.cancel != nil {
b.cancel()
}
return b.err
}
func (b *Batch[T]) WaitAndGetResult() (map[string]Result[T], *Error) {
err := b.Wait()
return b.Result(), err
}
func (b *Batch[T]) Result() map[string]Result[T] {
b.mux.Lock()
defer b.mux.Unlock()
copyM := map[string]Result[T]{}
for k, v := range b.result {
copyM[k] = v
}
return copyM
}
func New[T any](ctx context.Context, opts ...Option[T]) (*Batch[T], context.Context) {
ctx, cancel := context.WithCancel(ctx)
b := &Batch[T]{
result: map[string]Result[T]{},
}
for _, o := range opts {
o(b)
}
b.cancel = cancel
return b, ctx
}

View File

@ -0,0 +1,83 @@
package batch
import (
"context"
"errors"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestBatch(t *testing.T) {
b, _ := New[string](context.Background())
now := time.Now()
b.Go("foo", func() (string, error) {
time.Sleep(time.Millisecond * 100)
return "foo", nil
})
b.Go("bar", func() (string, error) {
time.Sleep(time.Millisecond * 150)
return "bar", nil
})
result, err := b.WaitAndGetResult()
assert.Nil(t, err)
duration := time.Since(now)
assert.Less(t, duration, time.Millisecond*200)
assert.Equal(t, 2, len(result))
for k, v := range result {
assert.NoError(t, v.Err)
assert.Equal(t, k, v.Value)
}
}
func TestBatchWithConcurrencyNum(t *testing.T) {
b, _ := New[string](
context.Background(),
WithConcurrencyNum[string](3),
)
now := time.Now()
for i := 0; i < 7; i++ {
idx := i
b.Go(strconv.Itoa(idx), func() (string, error) {
time.Sleep(time.Millisecond * 100)
return strconv.Itoa(idx), nil
})
}
result, _ := b.WaitAndGetResult()
duration := time.Since(now)
assert.Greater(t, duration, time.Millisecond*260)
assert.Equal(t, 7, len(result))
for k, v := range result {
assert.NoError(t, v.Err)
assert.Equal(t, k, v.Value)
}
}
func TestBatchContext(t *testing.T) {
b, ctx := New[string](context.Background())
b.Go("error", func() (string, error) {
time.Sleep(time.Millisecond * 100)
return "", errors.New("test error")
})
b.Go("ctx", func() (string, error) {
<-ctx.Done()
return "", ctx.Err()
})
result, err := b.WaitAndGetResult()
assert.NotNil(t, err)
assert.Equal(t, "error", err.Key)
assert.Equal(t, ctx.Err(), result["ctx"].Err)
}

21
common/buf/sing.go Normal file
View File

@ -0,0 +1,21 @@
package buf
import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
)
const BufferSize = buf.BufferSize
type Buffer = buf.Buffer
var New = buf.New
var NewPacket = buf.NewPacket
var NewSize = buf.NewSize
var With = buf.With
var As = buf.As
var (
Must = common.Must
Error = common.Error
)

106
common/cache/cache.go vendored
View File

@ -1,106 +0,0 @@
package cache
import (
"runtime"
"sync"
"time"
)
// Cache store element with a expired time
type Cache struct {
*cache
}
type cache struct {
mapping sync.Map
janitor *janitor
}
type element struct {
Expired time.Time
Payload interface{}
}
// Put element in Cache with its ttl
func (c *cache) Put(key interface{}, payload interface{}, ttl time.Duration) {
c.mapping.Store(key, &element{
Payload: payload,
Expired: time.Now().Add(ttl),
})
}
// Get element in Cache, and drop when it expired
func (c *cache) Get(key interface{}) interface{} {
item, exist := c.mapping.Load(key)
if !exist {
return nil
}
elm := item.(*element)
// expired
if time.Since(elm.Expired) > 0 {
c.mapping.Delete(key)
return nil
}
return elm.Payload
}
// GetWithExpire element in Cache with Expire Time
func (c *cache) GetWithExpire(key interface{}) (payload interface{}, expired time.Time) {
item, exist := c.mapping.Load(key)
if !exist {
return
}
elm := item.(*element)
// expired
if time.Since(elm.Expired) > 0 {
c.mapping.Delete(key)
return
}
return elm.Payload, elm.Expired
}
func (c *cache) cleanup() {
c.mapping.Range(func(k, v interface{}) bool {
key := k.(string)
elm := v.(*element)
if time.Since(elm.Expired) > 0 {
c.mapping.Delete(key)
}
return true
})
}
type janitor struct {
interval time.Duration
stop chan struct{}
}
func (j *janitor) process(c *cache) {
ticker := time.NewTicker(j.interval)
for {
select {
case <-ticker.C:
c.cleanup()
case <-j.stop:
ticker.Stop()
return
}
}
}
func stopJanitor(c *Cache) {
c.janitor.stop <- struct{}{}
}
// New return *Cache
func New(interval time.Duration) *Cache {
j := &janitor{
interval: interval,
stop: make(chan struct{}),
}
c := &cache{janitor: j}
go j.process(c)
C := &Cache{c}
runtime.SetFinalizer(C, stopJanitor)
return C
}

View File

@ -1,70 +0,0 @@
package cache
import (
"runtime"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestCache_Basic(t *testing.T) {
interval := 200 * time.Millisecond
ttl := 20 * time.Millisecond
c := New(interval)
c.Put("int", 1, ttl)
c.Put("string", "a", ttl)
i := c.Get("int")
assert.Equal(t, i.(int), 1, "should recv 1")
s := c.Get("string")
assert.Equal(t, s.(string), "a", "should recv 'a'")
}
func TestCache_TTL(t *testing.T) {
interval := 200 * time.Millisecond
ttl := 20 * time.Millisecond
now := time.Now()
c := New(interval)
c.Put("int", 1, ttl)
c.Put("int2", 2, ttl)
i := c.Get("int")
_, expired := c.GetWithExpire("int2")
assert.Equal(t, i.(int), 1, "should recv 1")
assert.True(t, now.Before(expired))
time.Sleep(ttl * 2)
i = c.Get("int")
j, _ := c.GetWithExpire("int2")
assert.Nil(t, i, "should recv nil")
assert.Nil(t, j, "should recv nil")
}
func TestCache_AutoCleanup(t *testing.T) {
interval := 10 * time.Millisecond
ttl := 15 * time.Millisecond
c := New(interval)
c.Put("int", 1, ttl)
time.Sleep(ttl * 2)
i := c.Get("int")
j, _ := c.GetWithExpire("int")
assert.Nil(t, i, "should recv nil")
assert.Nil(t, j, "should recv nil")
}
func TestCache_AutoGC(t *testing.T) {
sign := make(chan struct{})
go func() {
interval := 10 * time.Millisecond
ttl := 15 * time.Millisecond
c := New(interval)
c.Put("int", 1, ttl)
sign <- struct{}{}
}()
<-sign
runtime.GC()
}

View File

@ -3,63 +3,75 @@ package cache
// Modified by https://github.com/die-net/lrucache
import (
"container/list"
"sync"
"time"
"github.com/Dreamacro/clash/common/generics/list"
"github.com/samber/lo"
)
// Option is part of Functional Options Pattern
type Option func(*LruCache)
type Option[K comparable, V any] func(*LruCache[K, V])
// EvictCallback is used to get a callback when a cache entry is evicted
type EvictCallback func(key interface{}, value interface{})
type EvictCallback[K comparable, V any] func(key K, value V)
// WithEvict set the evict callback
func WithEvict(cb EvictCallback) Option {
return func(l *LruCache) {
func WithEvict[K comparable, V any](cb EvictCallback[K, V]) Option[K, V] {
return func(l *LruCache[K, V]) {
l.onEvict = cb
}
}
// WithUpdateAgeOnGet update expires when Get element
func WithUpdateAgeOnGet() Option {
return func(l *LruCache) {
func WithUpdateAgeOnGet[K comparable, V any]() Option[K, V] {
return func(l *LruCache[K, V]) {
l.updateAgeOnGet = true
}
}
// WithAge defined element max age (second)
func WithAge(maxAge int64) Option {
return func(l *LruCache) {
func WithAge[K comparable, V any](maxAge int64) Option[K, V] {
return func(l *LruCache[K, V]) {
l.maxAge = maxAge
}
}
// WithSize defined max length of LruCache
func WithSize(maxSize int) Option {
return func(l *LruCache) {
func WithSize[K comparable, V any](maxSize int) Option[K, V] {
return func(l *LruCache[K, V]) {
l.maxSize = maxSize
}
}
// WithStale decide whether Stale return is enabled.
// If this feature is enabled, element will not get Evicted according to `WithAge`.
func WithStale[K comparable, V any](stale bool) Option[K, V] {
return func(l *LruCache[K, V]) {
l.staleReturn = stale
}
}
// LruCache is a thread-safe, in-memory lru-cache that evicts the
// least recently used entries from memory when (if set) the entries are
// older than maxAge (in seconds). Use the New constructor to create one.
type LruCache struct {
type LruCache[K comparable, V any] struct {
maxAge int64
maxSize int
mu sync.Mutex
cache map[interface{}]*list.Element
lru *list.List // Front is least-recent
cache map[K]*list.Element[*entry[K, V]]
lru *list.List[*entry[K, V]] // Front is least-recent
updateAgeOnGet bool
onEvict EvictCallback
staleReturn bool
onEvict EvictCallback[K, V]
}
// NewLRUCache creates an LruCache
func NewLRUCache(options ...Option) *LruCache {
lc := &LruCache{
lru: list.New(),
cache: make(map[interface{}]*list.Element),
// New creates an LruCache
func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
lc := &LruCache[K, V]{
lru: list.New[*entry[K, V]](),
cache: make(map[K]*list.Element[*entry[K, V]]),
}
for _, option := range options {
@ -69,36 +81,54 @@ func NewLRUCache(options ...Option) *LruCache {
return lc
}
// Get returns the interface{} representation of a cached response and a bool
// Get returns the any representation of a cached response and a bool
// set to true if the key was found.
func (c *LruCache) Get(key interface{}) (interface{}, bool) {
func (c *LruCache[K, V]) Get(key K) (V, bool) {
c.mu.Lock()
defer c.mu.Unlock()
le, ok := c.cache[key]
if !ok {
return nil, false
el := c.get(key)
if el == nil {
return lo.Empty[V](), false
}
if c.maxAge > 0 && le.Value.(*entry).expires <= time.Now().Unix() {
c.deleteElement(le)
c.maybeDeleteOldest()
return nil, false
}
c.lru.MoveToBack(le)
entry := le.Value.(*entry)
if c.maxAge > 0 && c.updateAgeOnGet {
entry.expires = time.Now().Unix() + c.maxAge
}
value := entry.value
value := el.value
return value, true
}
func (c *LruCache[K, V]) GetOrStore(key K, constructor func() V) (V, bool) {
c.mu.Lock()
defer c.mu.Unlock()
el := c.get(key)
if el == nil {
value := constructor()
c.set(key, value)
return value, false
}
value := el.value
return value, true
}
// GetWithExpire returns the any representation of a cached response,
// a time.Time Give expected expires,
// and a bool set to true if the key was found.
// This method will NOT check the maxAge of element and will NOT update the expires.
func (c *LruCache[K, V]) GetWithExpire(key K) (V, time.Time, bool) {
c.mu.Lock()
defer c.mu.Unlock()
el := c.get(key)
if el == nil {
return lo.Empty[V](), time.Time{}, false
}
return el.value, time.Unix(el.expires, 0), true
}
// Exist returns if key exist in cache but not put item to the head of linked list
func (c *LruCache) Exist(key interface{}) bool {
func (c *LruCache[K, V]) Exist(key K) bool {
c.mu.Lock()
defer c.mu.Unlock()
@ -106,27 +136,43 @@ func (c *LruCache) Exist(key interface{}) bool {
return ok
}
// Set stores the interface{} representation of a response for a given key.
func (c *LruCache) Set(key interface{}, value interface{}) {
// Set stores the any representation of a response for a given key.
func (c *LruCache[K, V]) Set(key K, value V) {
c.mu.Lock()
defer c.mu.Unlock()
c.set(key, value)
}
func (c *LruCache[K, V]) set(key K, value V) {
expires := int64(0)
if c.maxAge > 0 {
expires = time.Now().Unix() + c.maxAge
}
c.setWithExpire(key, value, time.Unix(expires, 0))
}
// SetWithExpire stores the any representation of a response for a given key and given expires.
// The expires time will round to second.
func (c *LruCache[K, V]) SetWithExpire(key K, value V, expires time.Time) {
c.mu.Lock()
defer c.mu.Unlock()
c.setWithExpire(key, value, expires)
}
func (c *LruCache[K, V]) setWithExpire(key K, value V, expires time.Time) {
if le, ok := c.cache[key]; ok {
c.lru.MoveToBack(le)
e := le.Value.(*entry)
e := le.Value
e.value = value
e.expires = expires
e.expires = expires.Unix()
} else {
e := &entry{key: key, value: value, expires: expires}
e := &entry[K, V]{key: key, value: value, expires: expires.Unix()}
c.cache[key] = c.lru.PushBack(e)
if c.maxSize > 0 {
if len := c.lru.Len(); len > c.maxSize {
if elLen := c.lru.Len(); elLen > c.maxSize {
c.deleteElement(c.lru.Front())
}
}
@ -135,37 +181,83 @@ func (c *LruCache) Set(key interface{}, value interface{}) {
c.maybeDeleteOldest()
}
// Delete removes the value associated with a key.
func (c *LruCache) Delete(key string) {
// CloneTo clone and overwrite elements to another LruCache
func (c *LruCache[K, V]) CloneTo(n *LruCache[K, V]) {
c.mu.Lock()
defer c.mu.Unlock()
n.mu.Lock()
defer n.mu.Unlock()
n.lru = list.New[*entry[K, V]]()
n.cache = make(map[K]*list.Element[*entry[K, V]])
for e := c.lru.Front(); e != nil; e = e.Next() {
elm := e.Value
n.cache[elm.key] = n.lru.PushBack(elm)
}
}
func (c *LruCache[K, V]) get(key K) *entry[K, V] {
le, ok := c.cache[key]
if !ok {
return nil
}
if !c.staleReturn && c.maxAge > 0 && le.Value.expires <= time.Now().Unix() {
c.deleteElement(le)
c.maybeDeleteOldest()
return nil
}
c.lru.MoveToBack(le)
el := le.Value
if c.maxAge > 0 && c.updateAgeOnGet {
el.expires = time.Now().Unix() + c.maxAge
}
return el
}
// Delete removes the value associated with a key.
func (c *LruCache[K, V]) Delete(key K) {
c.mu.Lock()
defer c.mu.Unlock()
if le, ok := c.cache[key]; ok {
c.deleteElement(le)
}
c.mu.Unlock()
}
func (c *LruCache) maybeDeleteOldest() {
if c.maxAge > 0 {
func (c *LruCache[K, V]) maybeDeleteOldest() {
if !c.staleReturn && c.maxAge > 0 {
now := time.Now().Unix()
for le := c.lru.Front(); le != nil && le.Value.(*entry).expires <= now; le = c.lru.Front() {
for le := c.lru.Front(); le != nil && le.Value.expires <= now; le = c.lru.Front() {
c.deleteElement(le)
}
}
}
func (c *LruCache) deleteElement(le *list.Element) {
func (c *LruCache[K, V]) deleteElement(le *list.Element[*entry[K, V]]) {
c.lru.Remove(le)
e := le.Value.(*entry)
e := le.Value
delete(c.cache, e.key)
if c.onEvict != nil {
c.onEvict(e.key, e.value)
}
}
type entry struct {
key interface{}
value interface{}
func (c *LruCache[K, V]) Clear() error {
c.mu.Lock()
defer c.mu.Unlock()
c.cache = make(map[K]*list.Element[*entry[K, V]])
return nil
}
type entry[K comparable, V any] struct {
key K
value V
expires int64
}

View File

@ -19,7 +19,7 @@ var entries = []struct {
}
func TestLRUCache(t *testing.T) {
c := NewLRUCache()
c := New[string, string]()
for _, e := range entries {
c.Set(e.key, e.value)
@ -32,7 +32,7 @@ func TestLRUCache(t *testing.T) {
for _, e := range entries {
value, ok := c.Get(e.key)
if assert.True(t, ok) {
assert.Equal(t, e.value, value.(string))
assert.Equal(t, e.value, value)
}
}
@ -45,25 +45,25 @@ func TestLRUCache(t *testing.T) {
}
func TestLRUMaxAge(t *testing.T) {
c := NewLRUCache(WithAge(86400))
c := New[string, string](WithAge[string, string](86400))
now := time.Now().Unix()
expected := now + 86400
// Add one expired entry
c.Set("foo", "bar")
c.lru.Back().Value.(*entry).expires = now
c.lru.Back().Value.expires = now
// Reset
c.Set("foo", "bar")
e := c.lru.Back().Value.(*entry)
e := c.lru.Back().Value
assert.True(t, e.expires >= now)
c.lru.Back().Value.(*entry).expires = now
c.lru.Back().Value.expires = now
// Set a few and verify expiration times
for _, s := range entries {
c.Set(s.key, s.value)
e := c.lru.Back().Value.(*entry)
e := c.lru.Back().Value
assert.True(t, e.expires >= expected && e.expires <= expected+10)
}
@ -77,7 +77,7 @@ func TestLRUMaxAge(t *testing.T) {
for _, s := range entries {
le, ok := c.cache[s.key]
if assert.True(t, ok) {
le.Value.(*entry).expires = now
le.Value.expires = now
}
}
@ -88,22 +88,22 @@ func TestLRUMaxAge(t *testing.T) {
}
func TestLRUpdateOnGet(t *testing.T) {
c := NewLRUCache(WithAge(86400), WithUpdateAgeOnGet())
c := New[string, string](WithAge[string, string](86400), WithUpdateAgeOnGet[string, string]())
now := time.Now().Unix()
expires := now + 86400/2
// Add one expired entry
c.Set("foo", "bar")
c.lru.Back().Value.(*entry).expires = expires
c.lru.Back().Value.expires = expires
_, ok := c.Get("foo")
assert.True(t, ok)
assert.True(t, c.lru.Back().Value.(*entry).expires > expires)
assert.True(t, c.lru.Back().Value.expires > expires)
}
func TestMaxSize(t *testing.T) {
c := NewLRUCache(WithSize(2))
c := New[string, string](WithSize[string, string](2))
// Add one expired entry
c.Set("foo", "bar")
_, ok := c.Get("foo")
@ -117,7 +117,7 @@ func TestMaxSize(t *testing.T) {
}
func TestExist(t *testing.T) {
c := NewLRUCache(WithSize(1))
c := New[int, int](WithSize[int, int](1))
c.Set(1, 2)
assert.True(t, c.Exist(1))
c.Set(2, 3)
@ -126,13 +126,59 @@ func TestExist(t *testing.T) {
func TestEvict(t *testing.T) {
temp := 0
evict := func(key interface{}, value interface{}) {
temp = key.(int) + value.(int)
evict := func(key int, value int) {
temp = key + value
}
c := NewLRUCache(WithEvict(evict), WithSize(1))
c := New[int, int](WithEvict[int, int](evict), WithSize[int, int](1))
c.Set(1, 2)
c.Set(2, 3)
assert.Equal(t, temp, 3)
}
func TestSetWithExpire(t *testing.T) {
c := New[int, *struct{}](WithAge[int, *struct{}](1))
now := time.Now().Unix()
tenSecBefore := time.Unix(now-10, 0)
c.SetWithExpire(1, &struct{}{}, tenSecBefore)
// res is expected not to exist, and expires should be empty time.Time
res, expires, exist := c.GetWithExpire(1)
assert.True(t, nil == res)
assert.Equal(t, time.Time{}, expires)
assert.Equal(t, false, exist)
}
func TestStale(t *testing.T) {
c := New[int, int](WithAge[int, int](1), WithStale[int, int](true))
now := time.Now().Unix()
tenSecBefore := time.Unix(now-10, 0)
c.SetWithExpire(1, 2, tenSecBefore)
res, expires, exist := c.GetWithExpire(1)
assert.Equal(t, 2, res)
assert.Equal(t, tenSecBefore, expires)
assert.Equal(t, true, exist)
}
func TestCloneTo(t *testing.T) {
o := New[string, int](WithSize[string, int](10))
o.Set("1", 1)
o.Set("2", 2)
n := New[string, int](WithSize[string, int](2))
n.Set("3", 3)
n.Set("4", 4)
o.CloneTo(n)
assert.False(t, n.Exist("3"))
assert.True(t, n.Exist("1"))
n.Set("5", 5)
assert.False(t, n.Exist("1"))
}

View File

@ -0,0 +1,55 @@
package callback
import (
"github.com/Dreamacro/clash/common/buf"
N "github.com/Dreamacro/clash/common/net"
C "github.com/Dreamacro/clash/constant"
)
type firstWriteCallBackConn struct {
C.Conn
callback func(error)
written bool
}
func (c *firstWriteCallBackConn) Write(b []byte) (n int, err error) {
defer func() {
if !c.written {
c.written = true
c.callback(err)
}
}()
return c.Conn.Write(b)
}
func (c *firstWriteCallBackConn) WriteBuffer(buffer *buf.Buffer) (err error) {
defer func() {
if !c.written {
c.written = true
c.callback(err)
}
}()
return c.Conn.WriteBuffer(buffer)
}
func (c *firstWriteCallBackConn) Upstream() any {
return c.Conn
}
func (c *firstWriteCallBackConn) WriterReplaceable() bool {
return c.written
}
func (c *firstWriteCallBackConn) ReaderReplaceable() bool {
return true
}
var _ N.ExtendedConn = (*firstWriteCallBackConn)(nil)
func NewFirstWriteCallBackConn(c C.Conn, callback func(error)) C.Conn {
return &firstWriteCallBackConn{
Conn: c,
callback: callback,
written: false,
}
}

36
common/cmd/cmd.go Normal file
View File

@ -0,0 +1,36 @@
package cmd
import (
"fmt"
"os/exec"
"strings"
)
func ExecCmd(cmdStr string) (string, error) {
args := splitArgs(cmdStr)
var cmd *exec.Cmd
if len(args) == 1 {
cmd = exec.Command(args[0])
} else {
cmd = exec.Command(args[0], args[1:]...)
}
prepareBackgroundCommand(cmd)
out, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("%v, %s", err, string(out))
}
return string(out), nil
}
func splitArgs(cmd string) []string {
args := strings.Split(cmd, " ")
// use in pipeline
if len(args) > 2 && strings.ContainsAny(cmd, "|") {
suffix := strings.Join(args[2:], " ")
args = append(args[:2], suffix)
}
return args
}

11
common/cmd/cmd_other.go Normal file
View File

@ -0,0 +1,11 @@
//go:build !windows
package cmd
import (
"os/exec"
)
func prepareBackgroundCommand(cmd *exec.Cmd) {
}

40
common/cmd/cmd_test.go Normal file
View File

@ -0,0 +1,40 @@
package cmd
import (
"runtime"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSplitArgs(t *testing.T) {
args := splitArgs("ls")
args1 := splitArgs("ls -la")
args2 := splitArgs("bash -c ls")
args3 := splitArgs("bash -c ls -lahF | grep 'cmd'")
assert.Equal(t, 1, len(args))
assert.Equal(t, 2, len(args1))
assert.Equal(t, 3, len(args2))
assert.Equal(t, 3, len(args3))
}
func TestExecCmd(t *testing.T) {
if runtime.GOOS == "windows" {
_, err := ExecCmd("cmd -c 'dir'")
assert.Nil(t, err)
return
}
_, err := ExecCmd("ls")
_, err1 := ExecCmd("ls -la")
_, err2 := ExecCmd("bash -c ls")
_, err3 := ExecCmd("bash -c ls -la")
_, err4 := ExecCmd("bash -c ls -la | grep 'cmd'")
assert.Nil(t, err)
assert.Nil(t, err1)
assert.Nil(t, err2)
assert.Nil(t, err3)
assert.Nil(t, err4)
}

12
common/cmd/cmd_windows.go Normal file
View File

@ -0,0 +1,12 @@
//go:build windows
package cmd
import (
"os/exec"
"syscall"
)
func prepareBackgroundCommand(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
}

View File

@ -0,0 +1,56 @@
package collections
import "sync"
type (
stack struct {
top *node
length int
lock *sync.RWMutex
}
node struct {
value interface{}
prev *node
}
)
// NewStack Create a new stack
func NewStack() *stack {
return &stack{nil, 0, &sync.RWMutex{}}
}
// Len Return the number of items in the stack
func (this *stack) Len() int {
return this.length
}
// Peek View the top item on the stack
func (this *stack) Peek() interface{} {
if this.length == 0 {
return nil
}
return this.top.value
}
// Pop the top item of the stack and return it
func (this *stack) Pop() interface{} {
this.lock.Lock()
defer this.lock.Unlock()
if this.length == 0 {
return nil
}
n := this.top
this.top = n.prev
this.length--
return n.value
}
// Push a value onto the top of the stack
func (this *stack) Push(value interface{}) {
this.lock.Lock()
defer this.lock.Unlock()
n := &node{value, this.top}
this.top = n
this.length++
}

45
common/convert/base64.go Normal file
View File

@ -0,0 +1,45 @@
package convert
import (
"encoding/base64"
"strings"
)
var (
encRaw = base64.RawStdEncoding
enc = base64.StdEncoding
)
// DecodeBase64 try to decode content from the given bytes,
// which can be in base64.RawStdEncoding, base64.StdEncoding or just plaintext.
func DecodeBase64(buf []byte) []byte {
result, err := tryDecodeBase64(buf)
if err != nil {
return buf
}
return result
}
func tryDecodeBase64(buf []byte) ([]byte, error) {
dBuf := make([]byte, encRaw.DecodedLen(len(buf)))
n, err := encRaw.Decode(dBuf, buf)
if err != nil {
n, err = enc.Decode(dBuf, buf)
if err != nil {
return nil, err
}
}
return dBuf[:n], nil
}
func urlSafe(data string) string {
return strings.NewReplacer("+", "-", "/", "_").Replace(data)
}
func decodeUrlSafe(data string) string {
dcBuf, err := base64.RawURLEncoding.DecodeString(data)
if err != nil {
return ""
}
return string(dcBuf)
}

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