Compare commits

...

35 Commits

Author SHA1 Message Date
39d524dc18 Chore: update dependencies 2022-05-29 00:45:29 +08:00
0be8fc387a Chore: change GEO databases source 2022-05-29 00:45:13 +08:00
985dc99b5d Refactor: use native Win32 API to detect interface changed on Windows 2022-05-28 09:50:09 +08:00
67905bcf7e Feature: make wintun driver embed 2022-05-27 09:20:46 +08:00
b37e1fb2b9 Chore: yaml bump version from v2 to v3 2022-05-27 09:08:30 +08:00
22449da5d3 Fix: cache cleanup panic 2022-05-25 02:00:24 +08:00
6ad2cde909 Feature: support relay Socks5 UDP
supports relaying of all UDP traffic except the HTTP outbound.
2022-05-25 01:39:58 +08:00
68cf94a866 Chore: test cases 2022-05-25 01:36:27 +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
fe44a762c2 Chore: update dependencies 2022-05-22 05:32:36 +08:00
ce1014eae3 Feature: support relay UDP traffic 2022-05-22 05:32:15 +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
622b10d34d Chore: adjust iptables 2022-05-21 09:35:02 +08:00
88b5741ad8 Fix: addrToMetadata err should be nil 2022-05-21 08:19:33 +08:00
d11d28c358 Feature: add force-cert-verify to general config
force verify TLS Certificate, prevent machine-in-the-middle attacks.
2022-05-19 04:27:22 +08:00
03499fcea6 Refactor: fetcher by generics 2022-05-19 04:27:22 +08:00
f788411154 Refactor: use raw proxy adapter to get proxy connection by dns client 2022-05-18 20:35:59 +08:00
3d2b4b1f3a Refactor: get default route interface by syscall on darwin 2022-05-18 05:58:58 +08:00
5642d9c98e Fix: should flush interface cache by switch network 2022-05-18 04:45:19 +08:00
7a406b991e Fix: module clash-test 2022-05-18 04:08:35 +08:00
8603ac40a1 Chore: make linter happy 2022-05-17 19:58:33 +08:00
34eeb58bfa Chore: update dependencies 2022-05-16 02:24:05 +08:00
3d25f16b3b Feature: make tls sni sniffing switch config 2022-05-16 01:43:24 +08:00
891a56fd99 Feature: apply destination IP to tracker by Direct outbound for fake-ip mode 2022-05-16 01:43:24 +08:00
ffbdcfcbfd Feature: add update GEO databases to rest api 2022-05-16 01:43:23 +08:00
72b9b829e9 Fix: set mitm outbound 2022-05-16 01:43:23 +08:00
8b3e42bf19 Refactor: tun config 2022-05-16 01:43:23 +08:00
e92bea8401 Chore: merge branch 'ogn-dev' into with-tun 2022-05-16 01:41:02 +08:00
b384449717 Fix: fix upgrade header detect (#2134) 2022-05-15 09:12:53 +08:00
53c83118bc Chore: merge branch 'ogn-dev' into with-tun 2022-05-14 02:29:50 +08:00
da7ffc0da9 Fix: add length check for ssr auth_aes128_sha1 (#2129) 2022-05-13 11:21:39 +08:00
ace84ff548 Chore: code style 2022-05-09 08:10:20 +08:00
80 changed files with 2168 additions and 2138 deletions

View File

@ -3,8 +3,7 @@ on:
push:
branches:
- rm
tags:
- '*'
jobs:
build:

100
README.md
View File

@ -36,6 +36,13 @@
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
## Advanced usage for this branch
### General configuration
```yaml
sniffing: true # Sniff TLS SNI
force-cert-verify: true # force verify TLS Certificate, prevent machine-in-the-middle attacks
```
### MITM configuration
A root CA certificate is required, the
MITM proxy server will generate a CA certificate file and a CA private key file in your Clash home directory, you can use your own certificate replace it.
@ -102,19 +109,59 @@ Use `curl -X POST controllerip:port/cache/fakeip/flush` to flush persistence fak
```
### TUN configuration
Supports macOS, Linux and Windows.
Simply add the following to the main configuration:
#### NOTE:
> auto-route and auto-detect-interface only available on macOS, Windows and Linux, receive IPv4 traffic
On Windows, you should download the [Wintun](https://www.wintun.net) driver and copy `wintun.dll` into the system32 directory.
```yaml
# Enable the TUN listener
tun:
enable: true
stack: gvisor # System or gVisor
stack: system # or gvisor
# device: tun://utun8 # or fd://xxx, it's optional
dns-hijack:
- 0.0.0.0:53 # hijack all public
# dns-hijack:
# - 8.8.8.8:53
# - tcp://8.8.8.8:53
# - any:53
# - tcp://any:53
auto-route: true # auto set global route
auto-detect-interface: true # conflict with interface-name
```
or
```yaml
interface-name: en0
tun:
enable: true
stack: system # or gvisor
# dns-hijack:
# - 8.8.8.8:53
# - tcp://8.8.8.8:53
auto-route: true # auto set global route
```
It's recommended to use fake-ip mode for the DNS server.
Clash needs elevated permission to create TUN device:
```sh
$ sudo ./clash
```
Then manually create the default route and DNS server. If your device already has some TUN device, Clash TUN might not work. In this case, fake-ip-filter may helpful.
Enjoy! :)
#### For Windows:
```yaml
tun:
enable: true
stack: gvisor # or system
dns-hijack:
- 198.18.0.2:53 # when `fake-ip-range` is 198.18.0.1/16, should hijack 198.18.0.2:53
auto-route: true # auto set global route for Windows
# It is recommended to use `interface-name`
auto-detect-interface: true # auto detect interface, conflict with `interface-name`
```
Finally, open the Clash
### Rules configuration
- Support rule `GEOSITE`.
- Support rule `USER-AGENT`.
@ -167,6 +214,8 @@ Support outbound protocol `VLESS`.
Support `Trojan` with XTLS.
Support relay `UDP` traffic.
Currently XTLS only supports TCP transport.
```yaml
proxies:
@ -204,6 +253,25 @@ proxies:
# udp: true
# sni: example.com # aka server name
# skip-cert-verify: true
proxy-groups:
# Relay chains the proxies. proxies shall not contain a relay.
# Support relay UDP traffic.
# Traffic: clash <-> ss1 <-> trojan <-> vmess <-> ss2 <-> Internet
- name: "relay-udp-over-tcp"
type: relay
proxies:
- ss1
- trojan
- vmess
- ss2
- name: "relay-raw-udp"
type: relay
proxies:
- ss1
- ss2
- ss3
```
### IPTABLES configuration
@ -220,7 +288,7 @@ iptables:
Run Clash as a daemon.
Create the systemd configuration file at /etc/systemd/system/clash.service:
```shell
```sh
[Unit]
Description=Clash daemon, A rule-based proxy in Go.
After=network.target
@ -235,22 +303,23 @@ ExecStart=/usr/local/bin/clash -d /etc/clash
WantedBy=multi-user.target
```
Launch clashd on system startup with:
```shell
```sh
$ systemctl enable clash
```
Launch clashd immediately with:
```shell
```sh
$ systemctl start clash
```
### Display Process name
To display process name online by click [https://yaling888.github.io/yacd/](https://yaling888.github.io/yacd/).
To display process name online by click [http://yacd.clash-plus.cf](http://yacd.clash-plus.cf) for local API by Safari or [https://yacd.clash-plus.cf](https://yacd.clash-plus.cf) for local API by Chrome.
You can download the [Dashboard](https://github.com/yaling888/yacd/archive/gh-pages.zip) into Clash home directory:
```shell
cd ~/.config/clash
curl -LJ https://github.com/yaling888/yacd/archive/gh-pages.zip -o dashboard.zip
unzip dashboard.zip
```sh
$ cd ~/.config/clash
$ curl -LJ https://github.com/yaling888/yacd/archive/gh-pages.zip -o yacd-gh-pages.zip
$ unzip yacd-gh-pages.zip
$ mv yacd-gh-pages dashboard
```
Add to config file:
@ -260,6 +329,9 @@ external-ui: dashboard
```
Open [http://127.0.0.1:9090/ui/](http://127.0.0.1:9090/ui/) by web browser.
## Plus Pro Release
[Release](https://github.com/yaling888/clash/releases/tag/plus)
## Development
If you want to build an application that uses clash as a library, check out the the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)

View File

@ -3,14 +3,12 @@ package adapter
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"net/netip"
"net/url"
"time"
_ "unsafe"
"github.com/Dreamacro/clash/common/queue"
"github.com/Dreamacro/clash/component/dialer"
@ -19,9 +17,6 @@ import (
"go.uber.org/atomic"
)
//go:linkname errCanceled net.errCanceled
var errCanceled error
type Proxy struct {
C.ProxyAdapter
history *queue.Queue[C.DelayHistory]
@ -43,7 +38,7 @@ func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) {
// 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...)
p.alive.Store(err == nil || errors.Is(err, errCanceled))
p.alive.Store(err == nil)
return conn, err
}

View File

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"errors"
"io"
"net"
"github.com/Dreamacro/clash/component/dialer"
@ -30,12 +31,17 @@ func (b *Base) Type() C.AdapterType {
}
// StreamConn implements C.ProxyAdapter
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
func (b *Base) StreamConn(c net.Conn, _ *C.Metadata) (net.Conn, error) {
return c, errors.New("no support")
}
// StreamPacketConn implements C.ProxyAdapter
func (b *Base) StreamPacketConn(c net.Conn, _ *C.Metadata) (net.Conn, error) {
return c, errors.New("no support")
}
// ListenPacketContext implements C.ProxyAdapter
func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
func (b *Base) ListenPacketContext(_ context.Context, _ *C.Metadata, _ ...dialer.Option) (C.PacketConn, error) {
return nil, errors.New("no support")
}
@ -57,7 +63,7 @@ func (b *Base) Addr() string {
}
// Unwrap implements C.ProxyAdapter
func (b *Base) Unwrap(metadata *C.Metadata) C.Proxy {
func (b *Base) Unwrap(_ *C.Metadata) C.Proxy {
return nil
}
@ -133,6 +139,40 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name())
}
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
func NewPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
return &packetConn{pc, []string{a.Name()}}
}
type wrapConn struct {
net.PacketConn
}
func (*wrapConn) Read([]byte) (int, error) {
return 0, io.EOF
}
func (*wrapConn) Write([]byte) (int, error) {
return 0, io.EOF
}
func (*wrapConn) RemoteAddr() net.Addr {
return nil
}
func WrapConn(packetConn net.PacketConn) net.Conn {
return &wrapConn{
PacketConn: packetConn,
}
}
func IsPacketConn(c net.Conn) bool {
if _, ok := c.(net.PacketConn); !ok {
return false
}
if ua, ok := c.LocalAddr().(*net.UnixAddr); ok {
return ua.Net == "unixgram"
}
return true
}

View File

@ -3,6 +3,7 @@ package outbound
import (
"context"
"net"
"net/netip"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
@ -19,18 +20,26 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
if err != nil {
return nil, err
}
tcpKeepAlive(c)
if !metadata.DstIP.IsValid() && c.RemoteAddr() != nil {
if h, _, err := net.SplitHostPort(c.RemoteAddr().String()); err == nil {
metadata.DstIP = netip.MustParseAddr(h)
}
}
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) {
func (d *Direct) ListenPacketContext(ctx context.Context, _ *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
opts = append(opts, dialer.WithDirect())
pc, err := dialer.ListenPacket(ctx, "udp", "", d.Base.DialOptions(opts...)...)
if err != nil {
return nil, err
}
return newPacketConn(&directPacketConn{pc}, d), nil
return NewPacketConn(&directPacketConn{pc}, d), nil
}
type directPacketConn struct {

View File

@ -48,7 +48,7 @@ func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
// 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
return NewPacketConn(&nopPacketConn{}, r), nil
}
func NewReject() *Reject {

View File

@ -74,6 +74,21 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
return c, err
}
// StreamPacketConn implements C.ProxyAdapter
func (ss *ShadowSocks) StreamPacketConn(c net.Conn, _ *C.Metadata) (net.Conn, error) {
if !IsPacketConn(c) {
return c, fmt.Errorf("%s connect error: can not convert net.Conn to net.PacketConn", ss.addr)
}
addr, err := resolveUDPAddr("udp", ss.addr)
if err != nil {
return c, err
}
pc := ss.cipher.PacketConn(c.(net.PacketConn))
return WrapConn(&ssPacketConn{PacketConn: pc, rAddr: addr}), nil
}
// DialContext implements C.ProxyAdapter
func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
@ -95,14 +110,13 @@ func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Meta
return nil, err
}
addr, err := resolveUDPAddr("udp", ss.addr)
c, err := ss.StreamPacketConn(WrapConn(pc), metadata)
if err != nil {
pc.Close()
_ = pc.Close()
return nil, err
}
pc = ss.cipher.PacketConn(pc)
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ss), nil
return NewPacketConn(c.(net.PacketConn), ss), nil
}
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {

View File

@ -59,6 +59,22 @@ func (ssr *ShadowSocksR) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn,
return c, err
}
// StreamPacketConn implements C.ProxyAdapter
func (ssr *ShadowSocksR) StreamPacketConn(c net.Conn, _ *C.Metadata) (net.Conn, error) {
if !IsPacketConn(c) {
return c, fmt.Errorf("%s connect error: can not convert net.Conn to net.PacketConn", ssr.addr)
}
addr, err := resolveUDPAddr("udp", ssr.addr)
if err != nil {
return c, err
}
pc := ssr.cipher.PacketConn(c.(net.PacketConn))
pc = ssr.protocol.PacketConn(pc)
return WrapConn(&ssPacketConn{PacketConn: pc, rAddr: addr}), nil
}
// DialContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ssr.addr, ssr.Base.DialOptions(opts...)...)
@ -80,15 +96,13 @@ func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Me
return nil, err
}
addr, err := resolveUDPAddr("udp", ssr.addr)
c, err := ssr.StreamPacketConn(WrapConn(pc), metadata)
if err != nil {
pc.Close()
_ = pc.Close()
return nil, err
}
pc = ssr.cipher.PacketConn(pc)
pc = ssr.protocol.PacketConn(pc)
return newPacketConn(&ssPacketConn{PacketConn: pc, rAddr: addr}, ssr), nil
return NewPacketConn(c.(net.PacketConn), ssr), nil
}
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {

View File

@ -58,6 +58,18 @@ func (s *Snell) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, err
}
// StreamPacketConn implements C.ProxyAdapter
func (s *Snell) StreamPacketConn(c net.Conn, _ *C.Metadata) (net.Conn, error) {
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
err := snell.WriteUDPHeader(c, s.version)
if err != nil {
return c, err
}
return WrapConn(snell.PacketConn(c)), nil
}
// 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 {
@ -68,7 +80,7 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
port, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
if err = snell.WriteHeader(c, metadata.String(), uint(port), s.version); err != nil {
c.Close()
_ = c.Close()
return nil, err
}
return NewConn(c, s), err
@ -93,15 +105,14 @@ func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
return nil, err
}
tcpKeepAlive(c)
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
err = snell.WriteUDPHeader(c, s.version)
pc, err := s.StreamPacketConn(c, metadata)
if err != nil {
_ = c.Close()
return nil, err
}
pc := snell.PacketConn(c)
return newPacketConn(pc, s), nil
return NewPacketConn(pc.(net.PacketConn), s), nil
}
func NewSnell(option SnellOption) (*Snell, error) {

View File

@ -37,12 +37,59 @@ type Socks5Option struct {
// StreamConn implements C.ProxyAdapter
func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
c, _, err = ss.streamConn(c, metadata)
return c, err
}
func (ss *Socks5) StreamSocks5PacketConn(c net.Conn, pc net.PacketConn, metadata *C.Metadata) (net.PacketConn, error) {
if c == nil {
return pc, fmt.Errorf("%s connect error: parameter net.Conn is nil", ss.addr)
}
if pc == nil {
return pc, fmt.Errorf("%s connect error: parameter net.PacketConn is nil", ss.addr)
}
cc, bindAddr, err := ss.streamConn(c, metadata)
if err != nil {
return pc, err
}
c = cc
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()
}()
// Support unspecified UDP bind address.
bindUDPAddr := bindAddr.UDPAddr()
if bindUDPAddr == nil {
return pc, errors.New("invalid UDP bind address")
} else if bindUDPAddr.IP.IsUnspecified() {
serverAddr, err := resolveUDPAddr("udp", ss.Addr())
if err != nil {
return pc, err
}
bindUDPAddr.IP = serverAddr.IP
}
return &socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, nil
}
func (ss *Socks5) streamConn(c net.Conn, metadata *C.Metadata) (_ net.Conn, bindAddr socks5.Addr, err error) {
if 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)
return c, nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
}
@ -53,10 +100,14 @@ func (ss *Socks5) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
Password: ss.pass,
}
}
if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
return nil, err
if metadata.NetWork == C.UDP {
bindAddr, err = socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdUDPAssociate, user)
} else {
bindAddr, err = socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user)
}
return c, nil
return c, bindAddr, err
}
// DialContext implements C.ProxyAdapter
@ -81,61 +132,24 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
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
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
}
defer safeConnClose(c, err)
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
}
pc, err := dialer.ListenPacket(ctx, "udp", "", ss.Base.DialOptions(opts...)...)
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()
}()
tcpKeepAlive(c)
// Support unspecified UDP bind address.
bindUDPAddr := bindAddr.UDPAddr()
if bindUDPAddr == nil {
err = errors.New("invalid UDP bind address")
pc, err = ss.StreamSocks5PacketConn(c, pc, metadata)
if err != nil {
return
} else if bindUDPAddr.IP.IsUnspecified() {
serverAddr, err := resolveUDPAddr("udp", ss.Addr())
if err != nil {
return nil, err
}
bindUDPAddr.IP = serverAddr.IP
}
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil
return NewPacketConn(pc, ss), nil
}
func NewSocks5(option Socks5Option) *Socks5 {
@ -199,6 +213,6 @@ func (uc *socksPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
}
func (uc *socksPacketConn) Close() error {
uc.tcpConn.Close()
_ = uc.tcpConn.Close()
return uc.PacketConn.Close()
}

View File

@ -72,8 +72,7 @@ func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
return t.instance.StreamConn(c)
}
// StreamConn implements C.ProxyAdapter
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
func (t *Trojan) trojanStream(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
if t.transport != nil {
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
@ -85,39 +84,63 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
c, err = t.instance.PresetXTLSConn(c)
c, err = t.instance.PrepareXTLSConn(c)
if err != nil {
return nil, err
return c, 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
}
// StreamConn implements C.ProxyAdapter
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return t.trojanStream(c, metadata)
}
// StreamPacketConn implements C.ProxyAdapter
func (t *Trojan) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
c, err = t.trojanStream(c, metadata)
if err != nil {
return c, err
}
pc := t.instance.PacketConn(c)
return WrapConn(pc), nil
}
// DialContext implements C.ProxyAdapter
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
var c net.Conn
// gun transport
if t.transport != nil && len(opts) == 0 {
c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig)
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
if err != nil {
return nil, err
}
c, err = t.instance.PresetXTLSConn(c)
defer safeConnClose(c, err)
c, err = t.instance.PrepareXTLSConn(c)
if err != nil {
c.Close()
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
}
c, err := dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...)
c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
@ -137,33 +160,44 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
var c net.Conn
// grpc transport
// gun 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)
return nil, err
}
defer safeConnClose(c, err)
} else {
c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...)
c, err = t.instance.PrepareXTLSConn(c)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
return nil, err
}
defer safeConnClose(c, err)
tcpKeepAlive(c)
c, err = t.plainStream(c)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
if err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)); err != nil {
return nil, err
}
pc := t.instance.PacketConn(c)
return NewPacketConn(pc, t), nil
}
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
c, err = dialer.DialContext(ctx, "tcp", t.addr, t.Base.DialOptions(opts...)...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = t.StreamPacketConn(c, metadata)
if err != nil {
return nil, err
}
pc := t.instance.PacketConn(c)
return newPacketConn(pc, t), err
return NewPacketConn(c.(net.PacketConn), t), nil
}
func NewTrojan(option TrojanOption) (*Trojan, error) {

View File

@ -52,7 +52,7 @@ func resolveUDPAddr(network, address string) (*net.UDPAddr, error) {
}
func safeConnClose(c net.Conn, err error) {
if err != nil {
if err != nil && c != nil {
_ = c.Close()
}
}

View File

@ -58,6 +58,7 @@ type VlessOption struct {
ServerName string `proxy:"servername,omitempty"`
}
// StreamConn implements C.ProxyAdapter
func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
switch v.option.Network {
@ -147,6 +148,26 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return v.client.StreamConn(c, parseVlessAddr(metadata))
}
// StreamPacketConn implements C.ProxyAdapter
func (v *Vless) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
var err error
c, err = v.StreamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err)
}
return WrapConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}), nil
}
func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) {
host, _, _ := net.SplitHostPort(v.addr)
@ -219,18 +240,18 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
// 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 needs a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(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 {
// vless use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
@ -238,22 +259,27 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
defer safeConnClose(c, err)
c, err = v.client.StreamConn(c, parseVlessAddr(metadata))
} else {
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
return nil, fmt.Errorf("new vless client error: %v", err)
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = v.StreamConn(c, metadata)
return NewPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
}
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = v.StreamPacketConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("new vless client error: %v", err)
}
return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
return NewPacketConn(c.(net.PacketConn), v), nil
}
func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr {
@ -292,7 +318,7 @@ type vlessPacketConn struct {
mux sync.Mutex
}
func (vc *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
func (vc *vlessPacketConn) WriteTo(b []byte, _ net.Addr) (int, error) {
total := len(b)
if total == 0 {
return 0, nil

View File

@ -3,7 +3,6 @@ package outbound
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
@ -195,6 +194,26 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return v.client.StreamConn(c, parseVmessAddr(metadata))
}
// StreamPacketConn implements C.ProxyAdapter
func (v *Vmess) StreamPacketConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
return c, fmt.Errorf("can't resolve ip: %w", err)
}
metadata.DstIP = ip
}
var err error
c, err = v.StreamConn(c, metadata)
if err != nil {
return c, fmt.Errorf("new vmess client error: %v", err)
}
return WrapConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}), nil
}
// DialContext implements C.ProxyAdapter
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
// gun transport
@ -226,18 +245,18 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
// 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 needs a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(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 {
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
return nil, fmt.Errorf("can't resolve ip: %w", err)
}
metadata.DstIP = ip
}
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil {
return nil, err
@ -245,22 +264,27 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
defer safeConnClose(c, err)
c, err = v.client.StreamConn(c, parseVmessAddr(metadata))
} else {
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
return nil, fmt.Errorf("new vmess client error: %v", err)
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = v.StreamConn(c, metadata)
return NewPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
}
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
defer safeConnClose(c, err)
c, err = v.StreamPacketConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err)
}
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
return NewPacketConn(c.(net.PacketConn), v), nil
}
func NewVmess(option VmessOption) (*Vmess, error) {
@ -368,7 +392,7 @@ type vmessPacketConn struct {
rAddr net.Addr
}
func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
func (uc *vmessPacketConn) WriteTo(b []byte, _ net.Addr) (int, error) {
return uc.Conn.Write(b)
}

View File

@ -4,10 +4,14 @@ import (
"context"
"encoding/json"
"fmt"
"net"
"net/netip"
"github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
)
@ -34,30 +38,12 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
}
first := proxies[0]
last := proxies[len(proxies)-1]
c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
c, err := r.streamContext(ctx, proxies, r.Base.DialOptions(opts...)...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
tcpKeepAlive(c)
var currentMeta *C.Metadata
for _, proxy := range proxies[1:] {
currentMeta, err = addrToMetadata(proxy.Addr())
if err != nil {
return nil, err
}
c, err = first.StreamConn(c, currentMeta)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
first = proxy
return nil, err
}
last := proxies[len(proxies)-1]
c, err = last.StreamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
@ -66,6 +52,147 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
return outbound.NewConn(c, r), nil
}
// ListenPacketContext implements C.ProxyAdapter
func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
var proxies []C.Proxy
for _, proxy := range r.proxies(metadata, true) {
if proxy.Type() != C.Direct {
proxies = append(proxies, proxy)
}
}
length := len(proxies)
switch length {
case 0:
return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
case 1:
proxy := proxies[0]
if !proxy.SupportUDP() {
return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported", proxy.Addr(), proxy.Name())
}
return proxy.ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
}
var (
firstIndex = 0
nextIndex = 1
lastUDPOverTCPIndex = -1
rawUDPRelay = false
first = proxies[firstIndex]
last = proxies[length-1]
c net.Conn
cc net.Conn
err error
currentMeta *C.Metadata
)
if !last.SupportUDP() {
return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported in relay chains", last.Addr(), last.Name())
}
rawUDPRelay, lastUDPOverTCPIndex = isRawUDPRelay(proxies)
if first.Type() == C.Socks5 {
cc1, err1 := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
if err1 != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
cc = cc1
tcpKeepAlive(cc)
var pc net.PacketConn
pc, err = dialer.ListenPacket(ctx, "udp", "", r.Base.DialOptions(opts...)...)
c = outbound.WrapConn(pc)
} else if rawUDPRelay {
var pc net.PacketConn
pc, err = dialer.ListenPacket(ctx, "udp", "", r.Base.DialOptions(opts...)...)
c = outbound.WrapConn(pc)
} else {
firstIndex = lastUDPOverTCPIndex
nextIndex = firstIndex + 1
first = proxies[firstIndex]
c, err = r.streamContext(ctx, proxies[:nextIndex], r.Base.DialOptions(opts...)...)
}
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
if nextIndex < length {
for i, proxy := range proxies[nextIndex:] { // raw udp in loop
currentMeta, err = addrToMetadata(proxy.Addr())
if err != nil {
return nil, err
}
currentMeta.NetWork = C.UDP
if !isRawUDP(first) && !first.SupportUDP() {
return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported in relay chains", first.Addr(), first.Name())
}
if needResolveIP(first, currentMeta) {
var ip netip.Addr
ip, err = resolver.ResolveProxyServerHost(currentMeta.Host)
if err != nil {
return nil, fmt.Errorf("can't resolve ip: %w", err)
}
currentMeta.DstIP = ip
}
if cc != nil { // socks5
c, err = streamSocks5PacketConn(first, cc, c, currentMeta)
cc = nil
} else {
c, err = first.StreamPacketConn(c, currentMeta)
}
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
if proxy.Type() == C.Socks5 {
endIndex := nextIndex + i + 1
cc, err = r.streamContext(ctx, proxies[:endIndex], r.Base.DialOptions(opts...)...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
}
first = proxy
}
}
if cc != nil {
c, err = streamSocks5PacketConn(last, cc, c, metadata)
} else {
c, err = last.StreamPacketConn(c, metadata)
}
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
}
return outbound.NewPacketConn(c.(net.PacketConn), r), nil
}
// SupportUDP implements C.ProxyAdapter
func (r *Relay) SupportUDP() bool {
proxies := r.rawProxies(true)
l := len(proxies)
if l == 0 {
return true
}
last := proxies[l-1]
return isRawUDP(last) || last.SupportUDP()
}
// MarshalJSON implements C.ProxyAdapter
func (r *Relay) MarshalJSON() ([]byte, error) {
var all []string
@ -100,6 +227,83 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy {
return proxies
}
func (r *Relay) streamContext(ctx context.Context, proxies []C.Proxy, opts ...dialer.Option) (net.Conn, error) {
first := proxies[0]
c, err := dialer.DialContext(ctx, "tcp", first.Addr(), opts...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
tcpKeepAlive(c)
if len(proxies) > 1 {
var currentMeta *C.Metadata
for _, proxy := range proxies[1:] {
currentMeta, err = addrToMetadata(proxy.Addr())
if err != nil {
return nil, err
}
c, err = first.StreamConn(c, currentMeta)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
}
first = proxy
}
}
return c, nil
}
func streamSocks5PacketConn(proxy C.Proxy, cc, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
pc, err := proxy.(*adapter.Proxy).ProxyAdapter.(*outbound.Socks5).StreamSocks5PacketConn(cc, c.(net.PacketConn), metadata)
return outbound.WrapConn(pc), err
}
func isRawUDPRelay(proxies []C.Proxy) (bool, int) {
var (
lastIndex = len(proxies) - 1
last = proxies[lastIndex]
isLastRawUDP = isRawUDP(last)
isUDPOverTCP = false
lastUDPOverTCPIndex = -1
)
for i := lastIndex; i >= 0; i-- {
p := proxies[i]
isUDPOverTCP = isUDPOverTCP || !isRawUDP(p)
if isLastRawUDP && isUDPOverTCP && lastUDPOverTCPIndex == -1 {
lastUDPOverTCPIndex = i
}
}
if !isLastRawUDP {
lastUDPOverTCPIndex = lastIndex
}
return !isUDPOverTCP, lastUDPOverTCPIndex
}
func isRawUDP(proxy C.ProxyAdapter) bool {
if proxy.Type() == C.Shadowsocks || proxy.Type() == C.ShadowsocksR || proxy.Type() == C.Socks5 {
return true
}
return false
}
func needResolveIP(proxy C.ProxyAdapter, metadata *C.Metadata) bool {
if metadata.Resolved() {
return false
}
if proxy.Type() != C.Vmess && proxy.Type() != C.Vless {
return false
}
return true
}
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
return &Relay{
Base: outbound.NewBase(outbound.BaseOption{

View File

@ -24,7 +24,7 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
DstIP: netip.Addr{},
DstPort: port,
}
return
return addr, nil
} else if ip.Is4() {
addr = &C.Metadata{
AddrType: C.AtypIPv4,

View File

@ -8,7 +8,7 @@ import (
C "github.com/Dreamacro/clash/constant"
)
func ParseProxy(mapping map[string]any) (C.Proxy, error) {
func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true})
proxyType, existType := mapping["type"].(string)
if !existType {
@ -40,6 +40,9 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
if err != nil {
break
}
if forceCertVerify {
socksOption.SkipCertVerify = false
}
proxy = outbound.NewSocks5(*socksOption)
case "http":
httpOption := &outbound.HttpOption{}
@ -47,6 +50,9 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
if err != nil {
break
}
if forceCertVerify {
httpOption.SkipCertVerify = false
}
proxy = outbound.NewHttp(*httpOption)
case "vmess":
vmessOption := &outbound.VmessOption{
@ -59,6 +65,9 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
if err != nil {
break
}
if forceCertVerify {
vmessOption.SkipCertVerify = false
}
proxy, err = outbound.NewVmess(*vmessOption)
case "vless":
vlessOption := &outbound.VlessOption{}
@ -66,6 +75,9 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
if err != nil {
break
}
if forceCertVerify {
vlessOption.SkipCertVerify = false
}
proxy, err = outbound.NewVless(*vlessOption)
case "snell":
snellOption := &outbound.SnellOption{}
@ -80,6 +92,9 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
if err != nil {
break
}
if forceCertVerify {
trojanOption.SkipCertVerify = false
}
proxy, err = outbound.NewTrojan(*trojanOption)
default:
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)

View File

@ -16,28 +16,28 @@ var (
dirMode os.FileMode = 0o755
)
type parser = func([]byte) (any, error)
type parser[V any] func([]byte) (V, error)
type fetcher struct {
type fetcher[V any] struct {
name string
vehicle types.Vehicle
updatedAt *time.Time
ticker *time.Ticker
done chan struct{}
hash [16]byte
parser parser
onUpdate func(any)
parser parser[V]
onUpdate func(V)
}
func (f *fetcher) Name() string {
func (f *fetcher[V]) Name() string {
return f.name
}
func (f *fetcher) VehicleType() types.VehicleType {
func (f *fetcher[V]) VehicleType() types.VehicleType {
return f.vehicle.Type()
}
func (f *fetcher) Initial() (any, error) {
func (f *fetcher[V]) Initial() (V, error) {
var (
buf []byte
err error
@ -53,24 +53,24 @@ func (f *fetcher) Initial() (any, error) {
}
if err != nil {
return nil, err
return getZero[V](), err
}
proxies, err := f.parser(buf)
if err != nil {
if !isLocal {
return nil, err
return getZero[V](), err
}
// parse local file error, fallback to remote
buf, err = f.vehicle.Read()
if err != nil {
return nil, err
return getZero[V](), err
}
proxies, err = f.parser(buf)
if err != nil {
return nil, err
return getZero[V](), err
}
isLocal = false
@ -78,7 +78,7 @@ func (f *fetcher) Initial() (any, error) {
if f.vehicle.Type() != types.File && !isLocal {
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return nil, err
return getZero[V](), err
}
}
@ -92,28 +92,28 @@ func (f *fetcher) Initial() (any, error) {
return proxies, nil
}
func (f *fetcher) Update() (any, bool, error) {
func (f *fetcher[V]) Update() (V, bool, error) {
buf, err := f.vehicle.Read()
if err != nil {
return nil, false, err
return getZero[V](), false, err
}
now := time.Now()
hash := md5.Sum(buf)
if bytes.Equal(f.hash[:], hash[:]) {
f.updatedAt = &now
os.Chtimes(f.vehicle.Path(), now, now)
return nil, true, nil
_ = os.Chtimes(f.vehicle.Path(), now, now)
return getZero[V](), true, nil
}
proxies, err := f.parser(buf)
if err != nil {
return nil, false, err
return getZero[V](), false, err
}
if f.vehicle.Type() != types.File {
if err := safeWrite(f.vehicle.Path(), buf); err != nil {
return nil, false, err
return getZero[V](), false, err
}
}
@ -123,14 +123,14 @@ func (f *fetcher) Update() (any, bool, error) {
return proxies, false, nil
}
func (f *fetcher) Destroy() error {
func (f *fetcher[V]) Destroy() error {
if f.ticker != nil {
f.done <- struct{}{}
}
return nil
}
func (f *fetcher) pullLoop() {
func (f *fetcher[V]) pullLoop() {
for {
select {
case <-f.ticker.C:
@ -168,13 +168,13 @@ func safeWrite(path string, buf []byte) error {
return os.WriteFile(path, buf, fileMode)
}
func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, parser parser, onUpdate func(any)) *fetcher {
func newFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser parser[V], onUpdate func(V)) *fetcher[V] {
var ticker *time.Ticker
if interval != 0 {
ticker = time.NewTicker(interval)
}
return &fetcher{
return &fetcher[V]{
name: name,
ticker: ticker,
vehicle: vehicle,
@ -183,3 +183,8 @@ func newFetcher(name string, interval time.Duration, vehicle types.Vehicle, pars
onUpdate: onUpdate,
}
}
func getZero[V any]() V {
var result V
return result
}

View File

@ -20,15 +20,16 @@ type healthCheckSchema struct {
}
type proxyProviderSchema struct {
Type string `provider:"type"`
Path string `provider:"path"`
URL string `provider:"url,omitempty"`
Interval int `provider:"interval,omitempty"`
Filter string `provider:"filter,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
Type string `provider:"type"`
Path string `provider:"path"`
URL string `provider:"url,omitempty"`
Interval int `provider:"interval,omitempty"`
Filter string `provider:"filter,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
ForceCertVerify bool `provider:"force-cert-verify,omitempty"`
}
func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) {
func ParseProxyProvider(name string, mapping map[string]any, forceCertVerify bool) (types.ProxyProvider, error) {
decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
schema := &proxyProviderSchema{
@ -36,6 +37,11 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
Lazy: true,
},
}
if forceCertVerify {
schema.ForceCertVerify = true
}
if err := decoder.Decode(mapping, schema); err != nil {
return nil, err
}
@ -60,5 +66,5 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
interval := time.Duration(uint(schema.Interval)) * time.Second
filter := schema.Filter
return NewProxySetProvider(name, interval, filter, vehicle, hc)
return NewProxySetProvider(name, interval, filter, vehicle, hc, schema.ForceCertVerify)
}

View File

@ -12,7 +12,7 @@ import (
C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
const (
@ -23,13 +23,13 @@ type ProxySchema struct {
Proxies []map[string]any `yaml:"proxies"`
}
// for auto gc
// ProxySetProvider for auto gc
type ProxySetProvider struct {
*proxySetProvider
}
type proxySetProvider struct {
*fetcher
*fetcher[[]C.Proxy]
proxies []C.Proxy
healthCheck *HealthCheck
}
@ -93,10 +93,10 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
func stopProxyProvider(pd *ProxySetProvider) {
pd.healthCheck.close()
pd.fetcher.Destroy()
_ = pd.fetcher.Destroy()
}
func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
func NewProxySetProvider(name string, interval time.Duration, filter string, vehicle types.Vehicle, hc *HealthCheck, forceCertVerify bool) (*ProxySetProvider, error) {
filterReg, err := regexp.Compile(filter)
if err != nil {
return nil, fmt.Errorf("invalid filter regex: %w", err)
@ -111,45 +111,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
healthCheck: hc,
}
onUpdate := func(elm any) {
ret := elm.([]C.Proxy)
pd.setProxies(ret)
}
proxiesParseAndFilter := func(buf []byte) (any, 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 {
if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) {
continue
}
proxy, err := adapter.ParseProxy(mapping)
if err != nil {
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
}
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
}
fetcher := newFetcher(name, interval, vehicle, proxiesParseAndFilter, onUpdate)
fetcher := newFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, filterReg, forceCertVerify), proxiesOnUpdate(pd))
pd.fetcher = fetcher
wrapper := &ProxySetProvider{pd}
@ -157,7 +119,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, veh
return wrapper, nil
}
// for auto gc
// CompatibleProvider for auto gc
type CompatibleProvider struct {
*compatibleProvider
}
@ -233,3 +195,44 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
runtime.SetFinalizer(wrapper, stopCompatibleProvider)
return wrapper, nil
}
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
return func(elm []C.Proxy) {
pd.setProxies(elm)
}
}
func proxiesParseAndFilter(filter string, filterReg *regexp.Regexp, forceCertVerify bool) parser[[]C.Proxy] {
return func(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 {
if name, ok := mapping["name"]; ok && len(filter) > 0 && !filterReg.MatchString(name.(string)) {
continue
}
proxy, err := adapter.ParseProxy(mapping, forceCertVerify)
if err != nil {
return nil, fmt.Errorf("proxy %d error: %w", idx, err)
}
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

@ -61,7 +61,7 @@ func (c *cache[K, V]) GetWithExpire(key K) (payload V, expired time.Time) {
func (c *cache[K, V]) cleanup() {
c.mapping.Range(func(k, v any) bool {
key := k.(string)
key := k
elm := v.(*element[V])
if time.Since(elm.Expired) > 0 {
c.mapping.Delete(key)

View File

@ -52,8 +52,8 @@ func (alloc *Allocator) Put(buf []byte) error {
return errors.New("allocator Put() incorrect buffer size")
}
//lint:ignore SA6002 ignore temporarily
//nolint
//lint:ignore SA6002 ignore temporarily
alloc.buffers[bits].Put(buf)
return nil
}

View File

@ -25,7 +25,6 @@ type Result[T any] struct {
}
// Do single.Do likes sync.singleFlight
//lint:ignore ST1008 it likes sync.singleFlight
func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) {
s.mux.Lock()
now := time.Now()

View File

@ -29,7 +29,7 @@ import (
R "github.com/Dreamacro/clash/rule"
T "github.com/Dreamacro/clash/tunnel"
"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)
// General config
@ -39,6 +39,7 @@ type General struct {
Mode T.TunnelMode `json:"mode"`
LogLevel log.LogLevel `json:"log-level"`
IPv6 bool `json:"ipv6"`
Sniffing bool `json:"sniffing"`
Interface string `json:"-"`
RoutingMark int `json:"-"`
Tun Tun `json:"tun"`
@ -97,11 +98,12 @@ type Profile struct {
// Tun config
type Tun struct {
Enable bool `yaml:"enable" json:"enable"`
Device string `yaml:"device" json:"device"`
Stack C.TUNStack `yaml:"stack" json:"stack"`
DNSHijack []netip.AddrPort `yaml:"dns-hijack" json:"dns-hijack"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
Enable bool `yaml:"enable" json:"enable"`
Device string `yaml:"device" json:"device"`
Stack C.TUNStack `yaml:"stack" json:"stack"`
DNSHijack []C.DNSUrl `yaml:"dns-hijack" json:"dns-hijack"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
AutoDetectInterface bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
}
// IPTables config
@ -122,7 +124,6 @@ type Experimental struct{}
// Config is clash config manager
type Config struct {
General *General
Tun *Tun
IPTables *IPTables
Mitm *Mitm
DNS *DNS
@ -159,14 +160,6 @@ type RawFallbackFilter struct {
GeoSite []string `yaml:"geosite"`
}
type RawTun struct {
Enable bool `yaml:"enable" json:"enable"`
Device string `yaml:"device" json:"device"`
Stack C.TUNStack `yaml:"stack" json:"stack"`
DNSHijack []string `yaml:"dns-hijack" json:"dns-hijack"`
AutoRoute bool `yaml:"auto-route" json:"auto-route"`
}
type RawMitm struct {
Hosts []string `yaml:"hosts" json:"hosts"`
Rules []string `yaml:"rules" json:"rules"`
@ -190,11 +183,13 @@ type RawConfig struct {
Secret string `yaml:"secret"`
Interface string `yaml:"interface-name"`
RoutingMark int `yaml:"routing-mark"`
Sniffing bool `yaml:"sniffing"`
ForceCertVerify bool `yaml:"force-cert-verify"`
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
Hosts map[string]string `yaml:"hosts"`
DNS RawDNS `yaml:"dns"`
Tun RawTun `yaml:"tun"`
Tun Tun `yaml:"tun"`
IPTables IPTables `yaml:"iptables"`
MITM RawMitm `yaml:"mitm"`
Experimental Experimental `yaml:"experimental"`
@ -217,21 +212,37 @@ func Parse(buf []byte) (*Config, error) {
func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
// config with default value
rawCfg := &RawConfig{
AllowLan: false,
BindAddress: "*",
Mode: T.Rule,
Authentication: []string{},
LogLevel: log.INFO,
Hosts: map[string]string{},
Rule: []string{},
Proxy: []map[string]any{},
ProxyGroup: []map[string]any{},
Tun: RawTun{
Enable: false,
Device: "",
Stack: C.TunGvisor,
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
AutoRoute: true,
AllowLan: false,
Sniffing: false,
ForceCertVerify: false,
BindAddress: "*",
Mode: T.Rule,
Authentication: []string{},
LogLevel: log.INFO,
Hosts: map[string]string{},
Rule: []string{},
Proxy: []map[string]any{},
ProxyGroup: []map[string]any{},
Tun: Tun{
Enable: false,
Device: "",
Stack: C.TunGvisor,
DNSHijack: []C.DNSUrl{ // default hijack all dns lookup
{
Network: "udp",
AddrPort: C.DNSAddrPort{
AddrPort: netip.MustParseAddrPort("0.0.0.0:53"),
},
},
{
Network: "tcp",
AddrPort: C.DNSAddrPort{
AddrPort: netip.MustParseAddrPort("0.0.0.0:53"),
},
},
},
AutoRoute: false,
AutoDetectInterface: false,
},
IPTables: IPTables{
Enable: false,
@ -253,7 +264,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
"223.5.5.5",
},
NameServer: []string{ // default if user not set
"https://doh.pub/dns-query",
"https://120.53.53.53/dns-query",
"tls://223.5.5.5:853",
},
},
@ -286,14 +297,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
}
config.General = general
tunCfg, err := parseTun(rawCfg.Tun, config.General)
if err != nil {
return nil, err
}
config.Tun = tunCfg
dialer.DefaultInterface.Store(config.General.Interface)
proxies, providers, err := parseProxies(rawCfg)
if err != nil {
return nil, err
@ -342,6 +345,19 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
}
}
if cfg.Tun.Enable && cfg.Tun.AutoDetectInterface {
outboundInterface, err := commons.GetAutoDetectInterface()
if err != nil && cfg.Interface == "" {
return nil, fmt.Errorf("get auto detect interface fail: %w", err)
}
if outboundInterface != "" {
cfg.Interface = outboundInterface
}
}
dialer.DefaultInterface.Store(cfg.Interface)
return &General{
Inbound: Inbound{
Port: cfg.Port,
@ -363,6 +379,8 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
IPv6: cfg.IPv6,
Interface: cfg.Interface,
RoutingMark: cfg.RoutingMark,
Sniffing: cfg.Sniffing,
Tun: cfg.Tun,
}, nil
}
@ -372,6 +390,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
proxiesConfig := cfg.Proxy
groupsConfig := cfg.ProxyGroup
providersConfig := cfg.ProxyProvider
forceCertVerify := cfg.ForceCertVerify
var proxyList []string
@ -381,7 +400,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
// parse proxy
for idx, mapping := range proxiesConfig {
proxy, err := adapter.ParseProxy(mapping)
proxy, err := adapter.ParseProxy(mapping, forceCertVerify)
if err != nil {
return nil, nil, fmt.Errorf("proxy %d: %w", idx, err)
}
@ -413,7 +432,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
return nil, nil, fmt.Errorf("can not defined a provider called `%s`", provider.ReservedName)
}
pd, err := provider.ParseProxyProvider(name, mapping)
pd, err := provider.ParseProxyProvider(name, mapping, forceCertVerify)
if err != nil {
return nil, nil, fmt.Errorf("parse proxy provider %s error: %w", name, err)
}
@ -794,7 +813,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr], rules []C.R
}
func parseAuthentication(rawRecords []string) []auth.AuthUser {
users := []auth.AuthUser{}
var users []auth.AuthUser
for _, line := range rawRecords {
if user, pass, found := strings.Cut(line, ":"); found {
users = append(users, auth.AuthUser{User: user, Pass: pass})
@ -803,40 +822,6 @@ func parseAuthentication(rawRecords []string) []auth.AuthUser {
return users
}
func parseTun(rawTun RawTun, general *General) (*Tun, error) {
if (rawTun.Enable || general.TProxyPort != 0) && general.Interface == "" {
autoDetectInterfaceName, err := commons.GetAutoDetectInterface()
if err != nil || autoDetectInterfaceName == "" {
return nil, fmt.Errorf("can not find auto detect interface: %w. you must be detect `interface-name` if tun set to enable or `tproxy-port` isn't zore", err)
}
general.Interface = autoDetectInterfaceName
}
var dnsHijack []netip.AddrPort
for _, d := range rawTun.DNSHijack {
if _, after, ok := strings.Cut(d, "://"); ok {
d = after
}
addrPort, err := netip.ParseAddrPort(d)
if err != nil {
return nil, fmt.Errorf("parse dns-hijack url error: %w", err)
}
dnsHijack = append(dnsHijack, addrPort)
}
return &Tun{
Enable: rawTun.Enable,
Device: rawTun.Device,
Stack: rawTun.Stack,
DNSHijack: dnsHijack,
AutoRoute: rawTun.AutoRoute,
}, nil
}
func parseMitm(rawMitm RawMitm) (*Mitm, error) {
var (
req []C.Rewrite

View File

@ -12,7 +12,7 @@ import (
)
func downloadMMDB(path string) (err error) {
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/Country.mmdb")
resp, err := http.Get("https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb")
if err != nil {
return
}
@ -32,18 +32,18 @@ func initMMDB() error {
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
log.Infoln("Can't find MMDB, start download")
if err := downloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't download MMDB: %s", err.Error())
return fmt.Errorf("can't download MMDB: %w", err)
}
}
if !mmdb.Verify() {
log.Warnln("MMDB invalid, remove and download")
if err := os.Remove(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't remove invalid MMDB: %s", err.Error())
return fmt.Errorf("can't remove invalid MMDB: %w", err)
}
if err := downloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't download MMDB: %s", err.Error())
return fmt.Errorf("can't download MMDB: %w", err)
}
}
@ -51,7 +51,7 @@ func initMMDB() error {
}
func downloadGeoSite(path string) (err error) {
resp, err := http.Get("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat")
resp, err := http.Get("https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat")
if err != nil {
return
}
@ -71,7 +71,7 @@ func initGeoSite() error {
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
log.Infoln("Can't find GeoSite.dat, start download")
if err := downloadGeoSite(C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
return fmt.Errorf("can't download GeoSite.dat: %w", err)
}
log.Infoln("Download GeoSite.dat finish")
}
@ -84,7 +84,7 @@ func Init(dir string) error {
// initial homedir
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(dir, 0o777); err != nil {
return fmt.Errorf("can't create config directory %s: %s", dir, err.Error())
return fmt.Errorf("can't create config directory %s: %w", dir, err)
}
}
@ -93,10 +93,10 @@ func Init(dir string) error {
log.Infoln("Can't find config, create a initial config file")
f, err := os.OpenFile(C.Path.Config(), os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return fmt.Errorf("can't create file %s: %s", C.Path.Config(), err.Error())
return fmt.Errorf("can't create file %s: %w", C.Path.Config(), err)
}
f.Write([]byte(`mixed-port: 7890`))
f.Close()
_, _ = f.Write([]byte(`mixed-port: 7890`))
_ = f.Close()
}
// initial mmdb

69
config/updateGeo.go Normal file
View File

@ -0,0 +1,69 @@
package config
import (
"fmt"
"os"
"runtime"
"github.com/Dreamacro/clash/component/geodata"
_ "github.com/Dreamacro/clash/component/geodata/standard"
C "github.com/Dreamacro/clash/constant"
"github.com/oschwald/geoip2-golang"
)
func UpdateGeoDatabases() error {
var (
tmpMMDB = C.Path.Resolve("temp_country.mmdb")
tmpGeoSite = C.Path.Resolve("temp_geosite.dat")
)
if err := downloadMMDB(tmpMMDB); err != nil {
return fmt.Errorf("can't download MMDB database file: %w", err)
}
if err := verifyMMDB(tmpMMDB); err != nil {
_ = os.Remove(tmpMMDB)
return fmt.Errorf("invalid MMDB database file, %w", err)
}
if err := os.Rename(tmpMMDB, C.Path.MMDB()); err != nil {
return fmt.Errorf("can't rename MMDB database file: %w", err)
}
if err := downloadGeoSite(tmpGeoSite); err != nil {
return fmt.Errorf("can't download GeoSite database file: %w", err)
}
if err := verifyGeoSite(tmpGeoSite); err != nil {
_ = os.Remove(tmpGeoSite)
return fmt.Errorf("invalid GeoSite database file, %w", err)
}
if err := os.Rename(tmpGeoSite, C.Path.GeoSite()); err != nil {
return fmt.Errorf("can't rename GeoSite database file: %w", err)
}
return nil
}
func verifyMMDB(path string) error {
instance, err := geoip2.Open(path)
if err == nil {
_ = instance.Close()
}
return err
}
func verifyGeoSite(path string) error {
geoLoader, err := geodata.GetGeoDataLoader("standard")
if err != nil {
return err
}
_, err = geoLoader.LoadSite(path, "cn")
runtime.GC()
return err
}

View File

@ -93,9 +93,14 @@ type ProxyAdapter interface {
// a new session (if any)
StreamConn(c net.Conn, metadata *Metadata) (net.Conn, error)
// StreamPacketConn wraps a UDP protocol around net.Conn with Metadata.
StreamPacketConn(c net.Conn, metadata *Metadata) (net.Conn, error)
// DialContext return a C.Conn with protocol which
// contains multiplexing-related reuse logic (if any)
DialContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (Conn, error)
// ListenPacketContext listen for a PacketConn
ListenPacketContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (PacketConn, error)
// Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract.

View File

@ -101,8 +101,8 @@ func (m *Metadata) Resolved() bool {
// Pure is used to solve unexpected behavior
// when dialing proxy connection in DNSMapping mode.
func (m *Metadata) Pure() *Metadata {
if m.DNSMode == DNSMapping && m.DstIP.IsValid() {
func (m *Metadata) Pure(isMitmOutbound bool) *Metadata {
if !isMitmOutbound && m.DNSMode == DNSMapping && m.DstIP.IsValid() {
copyM := *m
copyM.Host = ""
if copyM.DstIP.Is4() {

View File

@ -3,7 +3,11 @@ package constant
import (
"encoding/json"
"errors"
"net/netip"
"strconv"
"strings"
"golang.org/x/exp/slices"
)
var StackTypeMapping = map[string]TUNStack{
@ -14,10 +18,21 @@ var StackTypeMapping = map[string]TUNStack{
const (
TunGvisor TUNStack = iota
TunSystem
TunDisabled TUNState = iota
TunEnabled
TunPaused
)
type TUNStack int
type TUNState int
type TUNChangeCallback interface {
Pause()
Resume()
}
// UnmarshalYAML unserialize TUNStack with yaml
func (e *TUNStack) UnmarshalYAML(unmarshal func(any) error) error {
var tp string
@ -40,7 +55,7 @@ func (e TUNStack) MarshalYAML() (any, error) {
// UnmarshalJSON unserialize TUNStack with json
func (e *TUNStack) UnmarshalJSON(data []byte) error {
var tp string
json.Unmarshal(data, &tp)
_ = json.Unmarshal(data, &tp)
mode, exist := StackTypeMapping[strings.ToUpper(tp)]
if !exist {
return errors.New("invalid tun stack")
@ -64,3 +79,113 @@ func (e TUNStack) String() string {
return "unknown"
}
}
type DNSAddrPort struct {
netip.AddrPort
}
func (p *DNSAddrPort) UnmarshalText(text []byte) error {
if len(text) == 0 {
*p = DNSAddrPort{}
return nil
}
addrPort := string(text)
if strings.HasPrefix(addrPort, "any") {
_, port, _ := strings.Cut(addrPort, "any")
addrPort = "0.0.0.0" + port
}
ap, err := netip.ParseAddrPort(addrPort)
*p = DNSAddrPort{AddrPort: ap}
return err
}
func (p DNSAddrPort) String() string {
addrPort := p.AddrPort.String()
if p.AddrPort.Addr().IsUnspecified() {
addrPort = "any:" + strconv.Itoa(int(p.AddrPort.Port()))
}
return addrPort
}
type DNSUrl struct {
Network string
AddrPort DNSAddrPort
}
func (d *DNSUrl) UnmarshalYAML(unmarshal func(any) error) error {
var text string
if err := unmarshal(&text); err != nil {
return err
}
text = strings.ToLower(text)
network := "udp"
if before, after, found := strings.Cut(text, "://"); found {
network = before
text = after
}
if network != "udp" && network != "tcp" {
return errors.New("invalid dns url schema")
}
ap := &DNSAddrPort{}
if err := ap.UnmarshalText([]byte(text)); err != nil {
return err
}
*d = DNSUrl{Network: network, AddrPort: *ap}
return nil
}
func (d DNSUrl) MarshalYAML() (any, error) {
return d.String(), nil
}
func (d *DNSUrl) UnmarshalJSON(data []byte) error {
var text string
if err := json.Unmarshal(data, &text); err != nil {
return err
}
text = strings.ToLower(text)
network := "udp"
if before, after, found := strings.Cut(text, "://"); found {
network = before
text = after
}
if network != "udp" && network != "tcp" {
return errors.New("invalid dns url schema")
}
ap := &DNSAddrPort{}
if err := ap.UnmarshalText([]byte(text)); err != nil {
return err
}
*d = DNSUrl{Network: network, AddrPort: *ap}
return nil
}
func (d DNSUrl) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}
func (d DNSUrl) String() string {
return d.Network + "://" + d.AddrPort.String()
}
func RemoveDuplicateDNSUrl(slice []DNSUrl) []DNSUrl {
slices.SortFunc[DNSUrl](slice, func(a, b DNSUrl) bool {
return a.Network < b.Network || (a.Network == b.Network && a.AddrPort.Addr().Less(b.AddrPort.Addr()))
})
return slices.CompactFunc[[]DNSUrl, DNSUrl](slice, func(a, b DNSUrl) bool {
return a.Network == b.Network && a.AddrPort == b.AddrPort
})
}

View File

@ -8,6 +8,7 @@ import (
"net/netip"
"time"
"github.com/Dreamacro/clash/adapter"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/component/dialer"
@ -132,16 +133,13 @@ func (wpc *wrapPacketConn) RemoteAddr() net.Addr {
}
func dialContextWithProxyAdapter(ctx context.Context, adapterName string, network string, dstIP netip.Addr, port string, opts ...dialer.Option) (net.Conn, error) {
adapter, ok := tunnel.Proxies()[adapterName]
proxy, ok := tunnel.Proxies()[adapterName]
if !ok {
return nil, fmt.Errorf("proxy adapter [%s] not found", adapterName)
}
networkType := C.TCP
if network == "udp" {
if !adapter.SupportUDP() {
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", adapterName)
}
networkType = C.UDP
}
@ -158,8 +156,14 @@ func dialContextWithProxyAdapter(ctx context.Context, adapterName string, networ
DstPort: port,
}
rawAdapter := fetchRawProxyAdapter(proxy.(*adapter.Proxy).ProxyAdapter, metadata)
if networkType == C.UDP {
packetConn, err := adapter.ListenPacketContext(ctx, metadata, opts...)
if !rawAdapter.SupportUDP() {
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", rawAdapter.Name())
}
packetConn, err := rawAdapter.ListenPacketContext(ctx, metadata, opts...)
if err != nil {
return nil, err
}
@ -170,5 +174,13 @@ func dialContextWithProxyAdapter(ctx context.Context, adapterName string, networ
}, nil
}
return adapter.DialContext(ctx, metadata, opts...)
return rawAdapter.DialContext(ctx, metadata, opts...)
}
func fetchRawProxyAdapter(proxyAdapter C.ProxyAdapter, metadata *C.Metadata) C.ProxyAdapter {
if p := proxyAdapter.Unwrap(metadata); p != nil {
return fetchRawProxyAdapter(p.(*adapter.Proxy).ProxyAdapter, metadata)
}
return proxyAdapter
}

20
go.mod
View File

@ -10,7 +10,7 @@ require (
github.com/gofrs/uuid v4.2.0+incompatible
github.com/gorilla/websocket v1.5.0
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f
github.com/miekg/dns v1.1.48
github.com/miekg/dns v1.1.49
github.com/oschwald/geoip2-golang v1.7.0
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.1
@ -18,17 +18,18 @@ require (
go.etcd.io/bbolt v1.3.6
go.uber.org/atomic v1.9.0
go.uber.org/automaxprocs v1.5.1
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf
golang.org/x/net v0.0.0-20220526153639-5463443f8c37
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab
golang.org/x/time v0.0.0-20220411224347-583f2d630306
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e
google.golang.org/protobuf v1.28.0
gopkg.in/yaml.v2 v2.4.0
gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150
gopkg.in/yaml.v3 v3.0.1
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8
)
require (
@ -39,9 +40,8 @@ require (
github.com/oschwald/maxminddb-golang v1.9.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect
golang.org/x/tools v0.1.9 // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/tools v0.1.10 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

39
go.sum
View File

@ -47,8 +47,8 @@ github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcK
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/miekg/dns v1.1.48 h1:Ucfr7IIVyMBz4lRE8qmGUuZ4Wt3/ZGu9hmcMT3Uu4tQ=
github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8=
github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXsw+Qs/S8=
github.com/oschwald/geoip2-golang v1.7.0/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ=
github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y=
@ -80,11 +80,13 @@ go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 h1:NvGWuYG8dkDHFSKksI1P9faiVJ9rayE6l0+ouWVIDs8=
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf h1:oXVg4h2qJDd9htKxb5SCpFBHLipW6hXmL3qpUixS2jw=
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf/go.mod h1:yh0Ynu2b5ZUe3MQfp2nM0ecK7wsgouWTDN0FNeJuIys=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 h1:LQmS1nU0twXLA96Kt7U9qtHJEbBk3z6Q0V4UXjZkpr4=
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -97,11 +99,12 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8=
golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -122,8 +125,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -136,8 +139,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -154,10 +157,8 @@ google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscL
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150 h1:bspdBY1iCLtW6JXold8yhXHkAiE9UoWfmHShNkTc9JA=
gvisor.dev/gvisor v0.0.0-20220506231117-8ef340c14150/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8 h1:K6RgHqNR+9t3sKVsfRFsvXryRL5kL6wtBPU5aPt1jLY=
gvisor.dev/gvisor v0.0.0-20220527053002-8ab279227ac8/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=

View File

@ -82,10 +82,9 @@ func ApplyConfig(cfg *config.Config, force bool) {
updateHosts(cfg.Hosts)
updateMitm(cfg.Mitm)
updateProfile(cfg)
updateDNS(cfg.DNS, cfg.Tun)
updateDNS(cfg.DNS, cfg.General.Tun)
updateGeneral(cfg.General, force)
updateIPTables(cfg)
updateTun(cfg.Tun, cfg.DNS)
updateExperimental(cfg)
log.SetLevel(cfg.General.LogLevel)
@ -94,8 +93,8 @@ func ApplyConfig(cfg *config.Config, force bool) {
func GetGeneral() *config.General {
ports := P.GetPorts()
authenticator := []string{}
if auth := authStore.Authenticator(); auth != nil {
authenticator = auth.Users()
if authM := authStore.Authenticator(); authM != nil {
authenticator = authM.Users()
}
general := &config.General{
@ -113,15 +112,16 @@ func GetGeneral() *config.General {
Mode: tunnel.Mode(),
LogLevel: log.Level(),
IPv6: !resolver.DisableIPv6,
Sniffing: tunnel.Sniffing(),
Tun: P.GetTunConf(),
}
return general
}
func updateExperimental(c *config.Config) {}
func updateExperimental(_ *config.Config) {}
func updateDNS(c *config.DNS, t *config.Tun) {
func updateDNS(c *config.DNS, t config.Tun) {
cfg := dns.Config{
Main: c.NameServer,
Fallback: c.Fallback,
@ -172,6 +172,10 @@ func updateDNS(c *config.DNS, t *config.Tun) {
}
dns.ReCreateServer("", nil, nil)
}
if cfg.Pool != nil {
P.SetTunAddressPrefix(cfg.Pool.IPNet())
}
}
func updateHosts(tree *trie.DomainTrie[netip.Addr]) {
@ -186,15 +190,6 @@ func updateRules(rules []C.Rule) {
tunnel.UpdateRules(rules)
}
func updateTun(tun *config.Tun, dns *config.DNS) {
var tunAddressPrefix *netip.Prefix
if dns.FakeIPRange != nil {
tunAddressPrefix = dns.FakeIPRange.IPNet()
}
P.ReCreateTun(tun, tunAddressPrefix, tunnel.TCPIn(), tunnel.UDPIn())
}
func updateGeneral(general *config.General, force bool) {
tunnel.SetMode(general.Mode)
resolver.DisableIPv6 = !general.IPv6
@ -223,6 +218,11 @@ func updateGeneral(general *config.General, force bool) {
bindAddress := general.BindAddress
P.SetBindAddress(bindAddress)
sniffing := general.Sniffing
tunnel.SetSniffing(sniffing)
log.Infoln("Use TLS SNI sniffer: %v", sniffing)
tcpIn := tunnel.TCPIn()
udpIn := tunnel.UDPIn()
@ -232,6 +232,7 @@ func updateGeneral(general *config.General, force bool) {
P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn)
P.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
P.ReCreateMitm(general.MitmPort, tcpIn)
P.ReCreateTun(&general.Tun, tcpIn, udpIn)
}
func updateUsers(users []auth.AuthUser) {
@ -273,7 +274,7 @@ func patchSelectGroup(proxies map[string]C.Proxy) {
continue
}
selector.Set(selected)
_ = selector.Set(selected)
}
}

View File

@ -1,15 +1,17 @@
package route
import (
"encoding/json"
"net/http"
"net/netip"
"path/filepath"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/hub/executor"
P "github.com/Dreamacro/clash/listener"
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
@ -26,26 +28,28 @@ func configRouter() http.Handler {
}
type configSchema struct {
Port *int `json:"port"`
SocksPort *int `json:"socks-port"`
RedirPort *int `json:"redir-port"`
TProxyPort *int `json:"tproxy-port"`
MixedPort *int `json:"mixed-port"`
MitmPort *int `json:"mitm-port"`
AllowLan *bool `json:"allow-lan"`
BindAddress *string `json:"bind-address"`
Mode *tunnel.TunnelMode `json:"mode"`
LogLevel *log.LogLevel `json:"log-level"`
IPv6 *bool `json:"ipv6"`
Tun *tunConfigSchema `json:"tun"`
Port *int `json:"port,omitempty"`
SocksPort *int `json:"socks-port,omitempty"`
RedirPort *int `json:"redir-port,omitempty"`
TProxyPort *int `json:"tproxy-port,omitempty"`
MixedPort *int `json:"mixed-port,omitempty"`
MitmPort *int `json:"mitm-port,omitempty"`
AllowLan *bool `json:"allow-lan,omitempty"`
BindAddress *string `json:"bind-address,omitempty"`
Mode *tunnel.TunnelMode `json:"mode,omitempty"`
LogLevel *log.LogLevel `json:"log-level,omitempty"`
IPv6 *bool `json:"ipv6,omitempty"`
Sniffing *bool `json:"sniffing,omitempty"`
Tun *tunConfigSchema `json:"tun,omitempty"`
}
type tunConfigSchema struct {
Enable *bool `json:"enable"`
Device *string `json:"device"`
Stack *constant.TUNStack `json:"stack"`
DNSHijack *[]netip.AddrPort `json:"dns-hijack"`
AutoRoute *bool `json:"auto-route"`
Enable *bool `json:"enable,omitempty"`
Device *string `json:"device,omitempty"`
Stack *constant.TUNStack `json:"stack,omitempty"`
DNSHijack *[]constant.DNSUrl `json:"dns-hijack,omitempty"`
AutoRoute *bool `json:"auto-route,omitempty"`
AutoDetectInterface *bool `json:"auto-detect-interface,omitempty"`
}
func getConfigs(w http.ResponseWriter, r *http.Request) {
@ -101,6 +105,10 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
resolver.DisableIPv6 = !*general.IPv6
}
if general.Sniffing != nil {
tunnel.SetSniffing(*general.Sniffing)
}
if general.Tun != nil {
tunSchema := general.Tun
tunConf := P.GetTunConf()
@ -120,10 +128,26 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
if tunSchema.AutoRoute != nil {
tunConf.AutoRoute = *tunSchema.AutoRoute
}
if tunSchema.AutoDetectInterface != nil {
tunConf.AutoDetectInterface = *tunSchema.AutoDetectInterface
}
P.ReCreateTun(&tunConf, nil, tcpIn, udpIn)
if dialer.DefaultInterface.Load() == "" && tunConf.Enable {
outboundInterface, err := commons.GetAutoDetectInterface()
if err != nil {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError("Get auto detect interface fail: "+err.Error()))
return
}
dialer.DefaultInterface.Store(outboundInterface)
}
P.ReCreateTun(&tunConf, tcpIn, udpIn)
}
msg, _ := json.Marshal(general)
log.Warnln("[REST-API] patch config by: %s", string(msg))
render.NoContent(w, r)
}
@ -151,6 +175,7 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, newError(err.Error()))
return
}
log.Warnln("[REST-API] update config by payload")
} else {
if req.Path == "" {
req.Path = constant.Path.Config()
@ -167,6 +192,7 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, newError(err.Error()))
return
}
log.Warnln("[REST-API] reload config from path: %s", req.Path)
}
executor.ApplyConfig(cfg, force)

64
hub/route/configsGeo.go Normal file
View File

@ -0,0 +1,64 @@
package route
import (
"net/http"
"sync"
"github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/hub/executor"
"github.com/Dreamacro/clash/log"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
)
var (
updatingGeo bool
updateGeoMux sync.Mutex
)
func configGeoRouter() http.Handler {
r := chi.NewRouter()
r.Post("/", updateGeoDatabases)
return r
}
func updateGeoDatabases(w http.ResponseWriter, r *http.Request) {
updateGeoMux.Lock()
if updatingGeo {
updateGeoMux.Unlock()
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError("updating..."))
return
}
updatingGeo = true
updateGeoMux.Unlock()
go func() {
defer func() {
updatingGeo = false
}()
log.Warnln("[REST-API] updating GEO databases...")
if err := config.UpdateGeoDatabases(); err != nil {
log.Errorln("[REST-API] update GEO databases failed: %v", err)
return
}
cfg, err := executor.ParseWithPath(constant.Path.Config())
if err != nil {
log.Errorln("[REST-API] update GEO databases failed: %v", err)
return
}
log.Warnln("[REST-API] update GEO databases successful, apply config...")
executor.ApplyConfig(cfg, false)
}()
render.NoContent(w, r)
}

View File

@ -67,6 +67,7 @@ func Start(addr string, secret string) {
r.Get("/traffic", traffic)
r.Get("/version", version)
r.Mount("/configs", configRouter())
r.Mount("/configs/geo", configGeoRouter())
r.Mount("/proxies", proxyRouter())
r.Mount("/rules", ruleRouter())
r.Mount("/connections", connectionRouter())

View File

@ -13,7 +13,15 @@ import (
)
func isUpgradeRequest(req *http.Request) bool {
return strings.EqualFold(req.Header.Get("Connection"), "Upgrade")
for _, header := range req.Header["Connection"] {
for _, elm := range strings.Split(header, ",") {
if strings.EqualFold(strings.TrimSpace(elm), "Upgrade") {
return true
}
}
}
return false
}
func HandleUpgrade(localConn net.Conn, serverConn *N.BufferedConn, request *http.Request, in chan<- C.ConnContext) (resp *http.Response) {

View File

@ -8,7 +8,6 @@ import (
"net"
"net/netip"
"os"
"sort"
"strconv"
"sync"
"time"
@ -36,6 +35,7 @@ var (
bindAddress = "*"
lastTunConf *config.Tun
lastTunAddressPrefix *netip.Prefix
tunAddressPrefix *netip.Prefix
socksListener *socks.Listener
socksUDPListener *socks.UDPListener
@ -70,11 +70,24 @@ type Ports struct {
func GetTunConf() config.Tun {
if lastTunConf == nil {
addrPort := C.DNSAddrPort{
AddrPort: netip.MustParseAddrPort("0.0.0.0:53"),
}
return config.Tun{
Enable: false,
Stack: C.TunGvisor,
DNSHijack: []netip.AddrPort{netip.MustParseAddrPort("0.0.0.0:53")},
AutoRoute: true,
Enable: false,
Stack: C.TunGvisor,
DNSHijack: []C.DNSUrl{ // default hijack all dns query
{
Network: "udp",
AddrPort: addrPort,
},
{
Network: "tcp",
AddrPort: addrPort,
},
},
AutoRoute: true,
AutoDetectInterface: false,
}
}
return *lastTunConf
@ -96,6 +109,10 @@ func SetBindAddress(host string) {
bindAddress = host
}
func SetTunAddressPrefix(tunAddress *netip.Prefix) {
tunAddressPrefix = tunAddress
}
func ReCreateHTTP(port int, tcpIn chan<- C.ConnContext) {
httpMux.Lock()
defer httpMux.Unlock()
@ -335,7 +352,7 @@ func ReCreateMixed(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
log.Infoln("Mixed(http+socks) proxy listening at: %s", mixedListener.Address())
}
func ReCreateTun(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
tunMux.Lock()
defer tunMux.Unlock()
@ -350,6 +367,8 @@ func ReCreateTun(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan
tunAddressPrefix = lastTunAddressPrefix
}
tunConf.DNSHijack = C.RemoveDuplicateDNSUrl(tunConf.DNSHijack)
if tunStackListener != nil {
if !hasTunConfigChange(tunConf, tunAddressPrefix) {
return
@ -366,7 +385,13 @@ func ReCreateTun(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan
return
}
tunStackListener, err = tun.New(tunConf, tunAddressPrefix, tcpIn, udpIn)
callback := &tunChangeCallback{
tunConf: *tunConf,
tcpIn: tcpIn,
udpIn: udpIn,
}
tunStackListener, err = tun.New(tunConf, tunAddressPrefix, tcpIn, udpIn, callback)
if err != nil {
return
}
@ -519,14 +544,6 @@ func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) boo
return true
}
sort.Slice(lastTunConf.DNSHijack, func(i, j int) bool {
return lastTunConf.DNSHijack[i].Addr().Less(lastTunConf.DNSHijack[j].Addr())
})
sort.Slice(tunConf.DNSHijack, func(i, j int) bool {
return tunConf.DNSHijack[i].Addr().Less(tunConf.DNSHijack[j].Addr())
})
for i, dns := range tunConf.DNSHijack {
if dns != lastTunConf.DNSHijack[i] {
return true
@ -536,7 +553,8 @@ func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) boo
if lastTunConf.Enable != tunConf.Enable ||
lastTunConf.Device != tunConf.Device ||
lastTunConf.Stack != tunConf.Stack ||
lastTunConf.AutoRoute != tunConf.AutoRoute {
lastTunConf.AutoRoute != tunConf.AutoRoute ||
lastTunConf.AutoDetectInterface != tunConf.AutoDetectInterface {
return true
}
@ -551,6 +569,24 @@ func hasTunConfigChange(tunConf *config.Tun, tunAddressPrefix *netip.Prefix) boo
return false
}
type tunChangeCallback struct {
tunConf config.Tun
tcpIn chan<- C.ConnContext
udpIn chan<- *inbound.PacketAdapter
}
func (t *tunChangeCallback) Pause() {
conf := t.tunConf
conf.Enable = false
ReCreateTun(&conf, t.tcpIn, t.udpIn)
}
func (t *tunChangeCallback) Resume() {
conf := t.tunConf
conf.Enable = true
ReCreateTun(&conf, t.tcpIn, t.udpIn)
}
func initCert() error {
if _, err := os.Stat(C.Path.RootCA()); os.IsNotExist(err) {
log.Infoln("Can't find mitm_ca.crt, start generate")

View File

@ -162,15 +162,12 @@ func CleanupTProxyIPTables() {
func addLocalnetworkToChain(chain string) {
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 0.0.0.0/8 -j RETURN", chain))
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 10.0.0.0/8 -j RETURN", chain))
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 100.64.0.0/10 -j RETURN", chain))
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 127.0.0.0/8 -j RETURN", chain))
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 169.254.0.0/16 -j RETURN", chain))
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 172.16.0.0/12 -j RETURN", chain))
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.0.0.0/24 -j RETURN", chain))
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.0.2.0/24 -j RETURN", chain))
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.88.99.0/24 -j RETURN", chain))
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 192.168.0.0/16 -j RETURN", chain))
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 198.51.100.0/24 -j RETURN", chain))
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 203.0.113.0/24 -j RETURN", chain))
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 224.0.0.0/4 -j RETURN", chain))
execCmd(fmt.Sprintf("iptables -t mangle -A %s -d 240.0.0.0/4 -j RETURN", chain))

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,233 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
*/
package driver
import (
"fmt"
"runtime"
"sync"
"sync/atomic"
"syscall"
"unsafe"
"github.com/Dreamacro/clash/log"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/driver/memmod"
)
//go:linkname modwintun golang.zx2c4.com/wintun.modwintun
//go:linkname procWintunCreateAdapter golang.zx2c4.com/wintun.procWintunCreateAdapter
//go:linkname procWintunOpenAdapter golang.zx2c4.com/wintun.procWintunOpenAdapter
//go:linkname procWintunCloseAdapter golang.zx2c4.com/wintun.procWintunCloseAdapter
//go:linkname procWintunDeleteDriver golang.zx2c4.com/wintun.procWintunDeleteDriver
//go:linkname procWintunGetAdapterLUID golang.zx2c4.com/wintun.procWintunGetAdapterLUID
//go:linkname procWintunGetRunningDriverVersion golang.zx2c4.com/wintun.procWintunGetRunningDriverVersion
//go:linkname procWintunAllocateSendPacket golang.zx2c4.com/wintun.procWintunAllocateSendPacket
//go:linkname procWintunEndSession golang.zx2c4.com/wintun.procWintunEndSession
//go:linkname procWintunGetReadWaitEvent golang.zx2c4.com/wintun.procWintunGetReadWaitEvent
//go:linkname procWintunReceivePacket golang.zx2c4.com/wintun.procWintunReceivePacket
//go:linkname procWintunReleaseReceivePacket golang.zx2c4.com/wintun.procWintunReleaseReceivePacket
//go:linkname procWintunSendPacket golang.zx2c4.com/wintun.procWintunSendPacket
//go:linkname procWintunStartSession golang.zx2c4.com/wintun.procWintunStartSession
var (
modwintun *lazyDLL
procWintunCreateAdapter *lazyProc
procWintunOpenAdapter *lazyProc
procWintunCloseAdapter *lazyProc
procWintunDeleteDriver *lazyProc
procWintunGetAdapterLUID *lazyProc
procWintunGetRunningDriverVersion *lazyProc
procWintunAllocateSendPacket *lazyProc
procWintunEndSession *lazyProc
procWintunGetReadWaitEvent *lazyProc
procWintunReceivePacket *lazyProc
procWintunReleaseReceivePacket *lazyProc
procWintunSendPacket *lazyProc
procWintunStartSession *lazyProc
)
type loggerLevel int
const (
logInfo loggerLevel = iota
logWarn
logErr
)
func init() {
modwintun = newLazyDLL("wintun.dll", setupLogger)
procWintunCreateAdapter = modwintun.NewProc("WintunCreateAdapter")
procWintunOpenAdapter = modwintun.NewProc("WintunOpenAdapter")
procWintunCloseAdapter = modwintun.NewProc("WintunCloseAdapter")
procWintunDeleteDriver = modwintun.NewProc("WintunDeleteDriver")
procWintunGetAdapterLUID = modwintun.NewProc("WintunGetAdapterLUID")
procWintunGetRunningDriverVersion = modwintun.NewProc("WintunGetRunningDriverVersion")
procWintunAllocateSendPacket = modwintun.NewProc("WintunAllocateSendPacket")
procWintunEndSession = modwintun.NewProc("WintunEndSession")
procWintunGetReadWaitEvent = modwintun.NewProc("WintunGetReadWaitEvent")
procWintunReceivePacket = modwintun.NewProc("WintunReceivePacket")
procWintunReleaseReceivePacket = modwintun.NewProc("WintunReleaseReceivePacket")
procWintunSendPacket = modwintun.NewProc("WintunSendPacket")
procWintunStartSession = modwintun.NewProc("WintunStartSession")
}
func InitWintun() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("init wintun.dll error: %v", r)
}
}()
if err = modwintun.Load(); err != nil {
return
}
procWintunCreateAdapter.Addr()
procWintunOpenAdapter.Addr()
procWintunCloseAdapter.Addr()
procWintunDeleteDriver.Addr()
procWintunGetAdapterLUID.Addr()
procWintunGetRunningDriverVersion.Addr()
procWintunAllocateSendPacket.Addr()
procWintunEndSession.Addr()
procWintunGetReadWaitEvent.Addr()
procWintunReceivePacket.Addr()
procWintunReleaseReceivePacket.Addr()
procWintunSendPacket.Addr()
procWintunStartSession.Addr()
return
}
func newLazyDLL(name string, onLoad func(d *lazyDLL)) *lazyDLL {
return &lazyDLL{Name: name, onLoad: onLoad}
}
func logMessage(level loggerLevel, _ uint64, msg *uint16) int {
switch level {
case logInfo:
log.Infoln("[TUN] %s", windows.UTF16PtrToString(msg))
case logWarn:
log.Warnln("[TUN] %s", windows.UTF16PtrToString(msg))
case logErr:
log.Errorln("[TUN] %s", windows.UTF16PtrToString(msg))
default:
log.Debugln("[TUN] %s", windows.UTF16PtrToString(msg))
}
return 0
}
func setupLogger(dll *lazyDLL) {
var callback uintptr
if runtime.GOARCH == "386" {
callback = windows.NewCallback(func(level loggerLevel, _, _ uint32, msg *uint16) int {
return logMessage(level, 0, msg)
})
} else if runtime.GOARCH == "arm" {
callback = windows.NewCallback(func(level loggerLevel, _, _, _ uint32, msg *uint16) int {
return logMessage(level, 0, msg)
})
} else if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" {
callback = windows.NewCallback(logMessage)
}
_, _, _ = syscall.SyscallN(dll.NewProc("WintunSetLogger").Addr(), callback)
}
func (d *lazyDLL) NewProc(name string) *lazyProc {
return &lazyProc{dll: d, Name: name}
}
type lazyProc struct {
Name string
mu sync.Mutex
dll *lazyDLL
addr uintptr
}
func (p *lazyProc) Find() error {
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr))) != nil {
return nil
}
p.mu.Lock()
defer p.mu.Unlock()
if p.addr != 0 {
return nil
}
err := p.dll.Load()
if err != nil {
return fmt.Errorf("error loading DLL: %s, MODULE: %s, error: %w", p.dll.Name, p.Name, err)
}
addr, err := p.nameToAddr()
if err != nil {
return fmt.Errorf("error getting %s address: %w", p.Name, err)
}
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.addr)), unsafe.Pointer(addr))
return nil
}
func (p *lazyProc) Addr() uintptr {
err := p.Find()
if err != nil {
panic(err)
}
return p.addr
}
func (p *lazyProc) Load() error {
return p.dll.Load()
}
type lazyDLL struct {
Name string
Base windows.Handle
mu sync.Mutex
module *memmod.Module
onLoad func(d *lazyDLL)
}
func (d *lazyDLL) Load() error {
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.module))) != nil {
return nil
}
d.mu.Lock()
defer d.mu.Unlock()
if d.module != nil {
return nil
}
module, err := memmod.LoadLibrary(dllData)
if err != nil {
return fmt.Errorf("unable to load library: %w", err)
}
d.Base = windows.Handle(module.BaseAddr())
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.module)), unsafe.Pointer(module))
if d.onLoad != nil {
d.onLoad(d)
}
return nil
}
func (p *lazyProc) nameToAddr() (uintptr, error) {
return p.dll.module.ProcAddressByName(p.Name)
}

View File

@ -0,0 +1,13 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
*/
package driver
import (
_ "embed"
)
//go:embed x86/wintun.dll
var dllData []byte

View File

@ -0,0 +1,13 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
*/
package driver
import (
_ "embed"
)
//go:embed amd64/wintun.dll
var dllData []byte

View File

@ -0,0 +1,13 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
*/
package driver
import (
_ "embed"
)
//go:embed arm/wintun.dll
var dllData []byte

View File

@ -0,0 +1,13 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2017-2021 WireGuard LLC. All Rights Reserved.
*/
package driver
import (
_ "embed"
)
//go:embed arm64/wintun.dll
var dllData []byte

View File

@ -0,0 +1,10 @@
//go:build windows
// https://git.zx2c4.com/wireguard-go/tree/tun/wintun
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
*/
package driver

Binary file not shown.

View File

@ -3,7 +3,6 @@
package tun
import (
"errors"
"fmt"
"os"
"runtime"
@ -43,11 +42,11 @@ func Open(name string, mtu uint32) (_ device.Device, err error) {
forcedMTU = int(t.mtu)
}
nt, err := tun.CreateTUN(t.name, forcedMTU) // forcedMTU do not work on wintun, need to be setting by other way
nt, err := newDevice(t.name, forcedMTU) // forcedMTU do not work on wintun, need to be setting by other way
// retry if abnormal exit on Windows at last time
if err != nil && runtime.GOOS == "windows" && errors.Is(err, os.ErrExist) {
nt, err = tun.CreateTUN(t.name, forcedMTU)
// retry if abnormal exit at last time on Windows
if err != nil && runtime.GOOS == "windows" && os.IsExist(err) {
nt, err = newDevice(t.name, forcedMTU)
}
if err != nil {

View File

@ -2,7 +2,13 @@
package tun
import "golang.zx2c4.com/wireguard/tun"
const (
offset = 4 /* 4 bytes TUN_PI */
defaultMTU = 1500
)
func newDevice(name string, mtu int) (tun.Device, error) {
return tun.CreateTUN(name, mtu)
}

View File

@ -1,6 +1,8 @@
package tun
import (
"github.com/Dreamacro/clash/listener/tun/device/tun/driver"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/tun"
)
@ -20,3 +22,11 @@ func init() {
func (t *TUN) LUID() uint64 {
return t.nt.LUID()
}
func newDevice(name string, mtu int) (nt tun.Device, err error) {
if err = driver.InitWintun(); err != nil {
return
}
return tun.CreateTUN(name, mtu)
}

View File

@ -5,15 +5,16 @@ import (
"time"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
D "github.com/miekg/dns"
)
const DefaultDnsReadTimeout = time.Second * 10
func ShouldHijackDns(dnsAdds []netip.AddrPort, targetAddr netip.AddrPort) bool {
for _, addrPort := range dnsAdds {
if addrPort == targetAddr || (addrPort.Addr().IsUnspecified() && targetAddr.Port() == 53) {
func ShouldHijackDns(dnsHijack []C.DNSUrl, targetAddr netip.AddrPort, network string) bool {
for _, dns := range dnsHijack {
if dns.Network == network && (dns.AddrPort.AddrPort == targetAddr || (dns.AddrPort.Addr().IsUnspecified() && dns.AddrPort.Port() == targetAddr.Port())) {
return true
}
}

View File

@ -1,18 +1,26 @@
package commons
import (
"errors"
"fmt"
"net"
"sync"
"time"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/log"
C "github.com/Dreamacro/clash/constant"
)
var (
defaultRoutes = []string{"1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1"}
defaultInterfaceMonitorDuration = 20 * time.Second
monitorDuration = 10 * time.Second
monitorStarted = false
monitorStop = make(chan struct{}, 2)
monitorMux sync.Mutex
tunStatus = C.TunDisabled
tunChangeCallback C.TUNChangeCallback
errInterfaceNotFound = errors.New("default interface not found")
)
func ipv4MaskString(bits int) string {
@ -24,26 +32,6 @@ func ipv4MaskString(bits int) string {
return fmt.Sprintf("%d.%d.%d.%d", m[0], m[1], m[2], m[3])
}
func defaultInterfaceChangeMonitor() {
t := time.NewTicker(defaultInterfaceMonitorDuration)
defer t.Stop()
for {
<-t.C
interfaceName, err := GetAutoDetectInterface()
if err != nil {
log.Warnln("[TUN] default interface monitor exited, cause: %v", err)
break
}
old := dialer.DefaultInterface.Load()
if interfaceName == old {
continue
}
dialer.DefaultInterface.Store(interfaceName)
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
}
func SetTunChangeCallback(callback C.TUNChangeCallback) {
tunChangeCallback = callback
}

View File

@ -2,17 +2,30 @@ package commons
import (
"fmt"
"net"
"net/netip"
"strings"
"syscall"
"time"
"github.com/Dreamacro/clash/common/cmd"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/iface"
"github.com/Dreamacro/clash/listener/tun/device"
"github.com/Dreamacro/clash/log"
"golang.org/x/net/route"
)
func GetAutoDetectInterface() (string, error) {
return cmd.ExecCmd("/bin/bash -c /sbin/route -n get default | grep 'interface:' | awk -F ' ' 'NR==1{print $2}' | xargs echo -n")
ifaceM, err := defaultRouteInterface()
if err != nil {
return "", err
}
return ifaceM.Name, nil
}
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, _ int, autoRoute bool) error {
if !addr.Addr().Is4() {
return fmt.Errorf("supported ipv4 only")
}
@ -54,8 +67,6 @@ func configInterfaceRouting(interfaceName string, addr netip.Prefix) error {
}
}
go defaultInterfaceChangeMonitor()
return execRouterCmd("add", "-inet6", "2000::/3", interfaceName)
}
@ -63,3 +74,95 @@ func execRouterCmd(action, inet, route string, interfaceName string) error {
_, err := cmd.ExecCmd(fmt.Sprintf("/sbin/route %s %s %s -interface %s", action, inet, route, interfaceName))
return err
}
func defaultRouteInterface() (*net.Interface, error) {
rib, err := route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0)
if err != nil {
return nil, fmt.Errorf("route.FetchRIB: %w", err)
}
msgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib)
if err != nil {
return nil, fmt.Errorf("route.ParseRIB: %w", err)
}
for _, message := range msgs {
routeMessage := message.(*route.RouteMessage)
if routeMessage.Flags&(syscall.RTF_UP|syscall.RTF_GATEWAY|syscall.RTF_STATIC) == 0 {
continue
}
addresses := routeMessage.Addrs
if (addresses[0].Family() == syscall.AF_INET && addresses[0].(*route.Inet4Addr).IP != *(*[4]byte)(net.IPv4zero)) ||
(addresses[0].Family() == syscall.AF_INET6 && addresses[0].(*route.Inet6Addr).IP != *(*[16]byte)(net.IPv6zero)) {
continue
}
ifaceM, err1 := net.InterfaceByIndex(routeMessage.Index)
if err1 != nil {
continue
}
if strings.HasPrefix(ifaceM.Name, "utun") {
continue
}
return ifaceM, nil
}
return nil, fmt.Errorf("ambiguous gateway interfaces found")
}
func StartDefaultInterfaceChangeMonitor() {
monitorMux.Lock()
if monitorStarted {
monitorMux.Unlock()
return
}
monitorStarted = true
monitorMux.Unlock()
select {
case <-monitorStop:
default:
}
t := time.NewTicker(monitorDuration)
defer t.Stop()
for {
select {
case <-t.C:
interfaceName, err := GetAutoDetectInterface()
if err != nil {
log.Warnln("[TUN] default interface monitor err: %v", err)
continue
}
old := dialer.DefaultInterface.Load()
if interfaceName == old {
continue
}
dialer.DefaultInterface.Store(interfaceName)
iface.FlushCache()
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
case <-monitorStop:
break
}
}
}
func StopDefaultInterfaceChangeMonitor() {
monitorMux.Lock()
defer monitorMux.Unlock()
if monitorStarted {
monitorStop <- struct{}{}
monitorStarted = false
}
}

View File

@ -3,16 +3,20 @@ package commons
import (
"fmt"
"net/netip"
"time"
"github.com/Dreamacro/clash/common/cmd"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/iface"
"github.com/Dreamacro/clash/listener/tun/device"
"github.com/Dreamacro/clash/log"
)
func GetAutoDetectInterface() (string, error) {
return cmd.ExecCmd("bash -c ip route show | grep 'default via' | awk -F ' ' 'NR==1{print $5}' | xargs echo -n")
}
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, forceMTU int, autoRoute bool) error {
func ConfigInterfaceAddress(dev device.Device, addr netip.Prefix, _ int, autoRoute bool) error {
var (
interfaceName = dev.Name()
ip = addr.Masked().Addr().Next()
@ -42,8 +46,6 @@ func configInterfaceRouting(interfaceName string, addr netip.Prefix) error {
}
}
go defaultInterfaceChangeMonitor()
return nil
}
@ -53,3 +55,55 @@ func execRouterCmd(action, route string, interfaceName string, linkIP string) er
_, err := cmd.ExecCmd(cmdStr)
return err
}
func StartDefaultInterfaceChangeMonitor() {
monitorMux.Lock()
if monitorStarted {
monitorMux.Unlock()
return
}
monitorStarted = true
monitorMux.Unlock()
select {
case <-monitorStop:
default:
}
t := time.NewTicker(monitorDuration)
defer t.Stop()
for {
select {
case <-t.C:
interfaceName, err := GetAutoDetectInterface()
if err != nil {
log.Warnln("[TUN] default interface monitor err: %v", err)
continue
}
old := dialer.DefaultInterface.Load()
if interfaceName == old {
continue
}
dialer.DefaultInterface.Store(interfaceName)
iface.FlushCache()
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, interfaceName)
case <-monitorStop:
break
}
}
}
func StopDefaultInterfaceChangeMonitor() {
monitorMux.Lock()
defer monitorMux.Unlock()
if monitorStarted {
monitorStop <- struct{}{}
monitorStarted = false
}
}

View File

@ -17,3 +17,7 @@ func GetAutoDetectInterface() (string, error) {
func ConfigInterfaceAddress(device.Device, netip.Prefix, int, bool) error {
return fmt.Errorf("unsupported on this OS: %s", runtime.GOOS)
}
func StartDefaultInterfaceChangeMonitor() {}
func StopDefaultInterfaceChangeMonitor() {}

View File

@ -1,12 +1,16 @@
package commons
import (
"errors"
"fmt"
"net"
"net/netip"
"sync"
"time"
"github.com/Dreamacro/clash/common/nnip"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/iface"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/listener/tun/device"
"github.com/Dreamacro/clash/listener/tun/device/tun"
"github.com/Dreamacro/clash/log"
@ -16,7 +20,11 @@ import (
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
)
var wintunInterfaceName string
var (
wintunInterfaceName string
unicastAddressChangeCallback *winipcfg.UnicastAddressChangeCallback
unicastAddressChangeLock sync.Mutex
)
func GetAutoDetectInterface() (string, error) {
ifname, err := getAutoDetectInterfaceByFamily(winipcfg.AddressFamily(windows.AF_INET))
@ -205,8 +213,6 @@ startOver:
wintunInterfaceName = dev.Name()
go defaultInterfaceChangeMonitor()
return nil
}
@ -222,15 +228,15 @@ func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, add
if err != nil {
return
}
for _, iface := range interfaces {
if iface.OperStatus == winipcfg.IfOperStatusUp {
for _, ifaceM := range interfaces {
if ifaceM.OperStatus == winipcfg.IfOperStatusUp {
continue
}
for address := iface.FirstUnicastAddress; address != nil; address = address.Next {
for address := ifaceM.FirstUnicastAddress; address != nil; address = address.Next {
if ip := nnip.IpToAddr(address.Address.IP()); addrHash[ip] {
prefix := netip.PrefixFrom(ip, int(address.OnLinkPrefixLength))
log.Infoln("[TUN] cleaning up stale address %s from interface %s", prefix.String(), iface.FriendlyName())
_ = iface.LUID.DeleteIPAddress(prefix)
log.Infoln("[TUN] cleaning up stale address %s from interface %s", prefix.String(), ifaceM.FriendlyName())
_ = ifaceM.LUID.DeleteIPAddress(prefix)
}
}
}
@ -239,7 +245,7 @@ func cleanupAddressesOnDisconnectedInterfaces(family winipcfg.AddressFamily, add
func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, error) {
interfaces, err := winipcfg.GetAdaptersAddresses(family, winipcfg.GAAFlagIncludeGateways)
if err != nil {
return "", fmt.Errorf("get ethernet interface failure. %w", err)
return "", fmt.Errorf("get default interface failure. %w", err)
}
var destination netip.Prefix
@ -249,25 +255,96 @@ func getAutoDetectInterfaceByFamily(family winipcfg.AddressFamily) (string, erro
destination = netip.PrefixFrom(netip.IPv6Unspecified(), 0)
}
for _, iface := range interfaces {
if iface.OperStatus != winipcfg.IfOperStatusUp {
for _, ifaceM := range interfaces {
if ifaceM.OperStatus != winipcfg.IfOperStatusUp {
continue
}
ifname := iface.FriendlyName()
ifname := ifaceM.FriendlyName()
if wintunInterfaceName == ifname {
continue
}
for gatewayAddress := iface.FirstGatewayAddress; gatewayAddress != nil; gatewayAddress = gatewayAddress.Next {
for gatewayAddress := ifaceM.FirstGatewayAddress; gatewayAddress != nil; gatewayAddress = gatewayAddress.Next {
nextHop := nnip.IpToAddr(gatewayAddress.Address.IP())
if _, err = iface.LUID.Route(destination, nextHop); err == nil {
if _, err = ifaceM.LUID.Route(destination, nextHop); err == nil {
return ifname, nil
}
}
}
return "", errors.New("ethernet interface not found")
return "", errInterfaceNotFound
}
func unicastAddressChange(_ winipcfg.MibNotificationType, unicastAddress *winipcfg.MibUnicastIPAddressRow) {
unicastAddressChangeLock.Lock()
defer unicastAddressChangeLock.Unlock()
interfaceName, err := GetAutoDetectInterface()
if err != nil {
if err == errInterfaceNotFound && tunStatus == C.TunEnabled {
log.Warnln("[TUN] lost the default interface, pause tun adapter")
tunStatus = C.TunPaused
tunChangeCallback.Pause()
}
return
}
ifaceM, err := net.InterfaceByIndex(int(unicastAddress.InterfaceIndex))
if err != nil {
log.Warnln("[TUN] default interface monitor err: %v", err)
return
}
newName := ifaceM.Name
if newName != interfaceName {
return
}
dialer.DefaultInterface.Store(interfaceName)
iface.FlushCache()
if tunStatus == C.TunPaused {
log.Warnln("[TUN] found interface %s(%s), resume tun adapter", interfaceName, unicastAddress.Address.Addr())
tunStatus = C.TunEnabled
tunChangeCallback.Resume()
return
}
log.Warnln("[TUN] default interface changed to %s(%s) by monitor", interfaceName, unicastAddress.Address.Addr())
}
func StartDefaultInterfaceChangeMonitor() {
if unicastAddressChangeCallback != nil {
return
}
var err error
unicastAddressChangeCallback, err = winipcfg.RegisterUnicastAddressChangeCallback(unicastAddressChange)
if err != nil {
log.Errorln("[TUN] register uni-cast address change callback failed: %v", err)
return
}
tunStatus = C.TunEnabled
log.Infoln("[TUN] register uni-cast address change callback")
}
func StopDefaultInterfaceChangeMonitor() {
if unicastAddressChangeCallback == nil || tunStatus == C.TunPaused {
return
}
_ = unicastAddressChangeCallback.Unregister()
unicastAddressChangeCallback = nil
tunChangeCallback = nil
tunStatus = C.TunDisabled
}

View File

@ -20,7 +20,7 @@ var _ adapter.Handler = (*gvHandler)(nil)
type gvHandler struct {
gateway netip.Addr
dnsHijack []netip.AddrPort
dnsHijack []C.DNSUrl
tcpIn chan<- C.ConnContext
udpIn chan<- *inbound.PacketAdapter
@ -37,7 +37,7 @@ func (gh *gvHandler) HandleTCP(tunConn adapter.TCPConn) {
rAddrPort := netip.AddrPortFrom(nnip.IpToAddr(rAddr.IP), id.LocalPort)
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort) {
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort, "tcp") {
go func() {
log.Debugln("[TUN] hijack dns tcp: %s", rAddrPort.String())
@ -111,7 +111,7 @@ func (gh *gvHandler) HandleUDP(tunConn adapter.UDPConn) {
payload := buf[:n]
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort) {
if D.ShouldHijackDns(gh.dnsHijack, rAddrPort, "udp") {
go func() {
defer func() {
_ = pool.Put(buf)

View File

@ -8,6 +8,7 @@ import (
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/listener/tun/device"
"github.com/Dreamacro/clash/listener/tun/ipstack"
"github.com/Dreamacro/clash/listener/tun/ipstack/commons"
"github.com/Dreamacro/clash/listener/tun/ipstack/gvisor/option"
"gvisor.dev/gvisor/pkg/tcpip"
@ -25,6 +26,8 @@ type gvStack struct {
}
func (s *gvStack) Close() error {
commons.StopDefaultInterfaceChangeMonitor()
var err error
if s.device != nil {
err = s.device.Close()
@ -37,7 +40,7 @@ func (s *gvStack) Close() error {
}
// New allocates a new *gvStack with given options.
func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter, opts ...option.Option) (ipstack.Stack, error) {
func New(device device.Device, dnsHijack []C.DNSUrl, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter, opts ...option.Option) (ipstack.Stack, error) {
s := &gvStack{
Stack: stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{

View File

@ -34,6 +34,8 @@ type sysStack struct {
}
func (s *sysStack) Close() error {
D.StopDefaultInterfaceChangeMonitor()
defer func() {
if s.device != nil {
_ = s.device.Close()
@ -49,7 +51,7 @@ func (s *sysStack) Close() error {
return err
}
func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) {
func New(device device.Device, dnsHijack []C.DNSUrl, tunAddress netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) {
var (
gateway = tunAddress.Masked().Addr().Next()
portal = gateway.Next()
@ -91,7 +93,7 @@ func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Pref
continue
}
if D.ShouldHijackDns(dnsAddr, rAddrPort) {
if D.ShouldHijackDns(dnsAddr, rAddrPort, "tcp") {
go func() {
log.Debugln("[TUN] hijack dns tcp: %s", rAddrPort.String())
@ -175,7 +177,7 @@ func New(device device.Device, dnsHijack []netip.AddrPort, tunAddress netip.Pref
continue
}
if D.ShouldHijackDns(dnsAddr, rAddrPort) {
if D.ShouldHijackDns(dnsAddr, rAddrPort, "udp") {
go func() {
msg, err := D.RelayDnsPacket(raw)
if err != nil {

View File

@ -24,7 +24,7 @@ import (
)
// New TunAdapter
func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (ipstack.Stack, error) {
func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter, tunChangeCallback C.TUNChangeCallback) (ipstack.Stack, error) {
var (
tunAddress = netip.Prefix{}
devName = tunConf.Device
@ -38,6 +38,12 @@ func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.Con
err error
)
defer func() {
if err != nil && tunDevice != nil {
_ = tunDevice.Close()
}
}()
if devName == "" {
devName = generateDeviceName()
}
@ -63,26 +69,22 @@ func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.Con
case C.TunGvisor:
err = tunDevice.UseEndpoint()
if err != nil {
_ = tunDevice.Close()
return nil, fmt.Errorf("can't attach endpoint to tun: %w", err)
}
tunStack, err = gvisor.New(tunDevice, tunConf.DNSHijack, tunAddress, tcpIn, udpIn, option.WithDefault())
if err != nil {
_ = tunDevice.Close()
return nil, fmt.Errorf("can't New gvisor stack: %w", err)
}
case C.TunSystem:
err = tunDevice.UseIOBased()
if err != nil {
_ = tunDevice.Close()
return nil, fmt.Errorf("can't New system stack: %w", err)
}
tunStack, err = system.New(tunDevice, tunConf.DNSHijack, tunAddress, tcpIn, udpIn)
if err != nil {
_ = tunDevice.Close()
return nil, fmt.Errorf("can't New system stack: %w", err)
}
default:
@ -92,10 +94,14 @@ func New(tunConf *config.Tun, tunAddressPrefix *netip.Prefix, tcpIn chan<- C.Con
// setting address and routing
err = commons.ConfigInterfaceAddress(tunDevice, tunAddress, mtu, autoRoute)
if err != nil {
_ = tunDevice.Close()
return nil, fmt.Errorf("setting interface address and routing failed: %w", err)
}
if tunConf.AutoDetectInterface {
commons.SetTunChangeCallback(tunChangeCallback)
go commons.StartDefaultInterfaceChangeMonitor()
}
setAtLatest(stackType, devName)
log.Infoln("TUN stack listening at: %s(%s), mtu: %d, auto route: %v, ip stack: %s", tunDevice.Name(), tunAddress.Masked().Addr().Next().String(), mtu, autoRoute, stackType)

16
test/.golangci.yaml Normal file
View File

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

View File

@ -1,8 +1,9 @@
lint:
golangci-lint run --disable-all -E govet -E gofumpt -E megacheck ./...
GOOS=darwin golangci-lint run ./...
GOOS=linux golangci-lint run ./...
test:
go test -p 1 -v ./...
benchmark:
go test -benchmem -run=^$ -bench .
go test -benchmem -run=^$$ -bench .

View File

@ -8,6 +8,7 @@ import (
"fmt"
"io"
"net"
"net/netip"
"os"
"path/filepath"
"runtime"
@ -24,6 +25,7 @@ import (
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
@ -38,7 +40,7 @@ const (
var (
waitTime = time.Second
localIP = net.ParseIP("127.0.0.1")
localIP = netip.MustParseAddr("127.0.0.1")
defaultExposedPorts = nat.PortSet{
"10002/tcp": struct{}{},
@ -52,13 +54,10 @@ var (
{HostPort: "10002", HostIP: "0.0.0.0"},
},
}
isDarwin = runtime.GOOS == "darwin"
)
func init() {
if runtime.GOOS == "darwin" {
isDarwin = true
}
currentDir, err := os.Getwd()
if err != nil {
panic(err)
@ -67,10 +66,11 @@ func init() {
C.SetHomeDir(homeDir)
if isDarwin {
localIP, err = defaultRouteIP()
routeIp, err := defaultRouteIP()
if err != nil {
panic(err)
}
localIP = netip.MustParseAddr(routeIp.String())
}
c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
@ -110,6 +110,7 @@ func init() {
continue
}
println("pulling image:", image)
imageStream, err := c.ImagePull(context.Background(), image, types.ImagePullOptions{})
if err != nil {
panic(err)
@ -214,46 +215,35 @@ func testPingPongWithSocksPort(t *testing.T, port int) {
pingCh, pongCh, test := newPingPongPair()
go func() {
l, err := Listen("tcp", ":10001")
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
defer l.Close()
c, err := l.Accept()
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
buf := make([]byte, 4)
if _, err := io.ReadFull(c, buf); err != nil {
assert.FailNow(t, err.Error())
}
_, err = io.ReadFull(c, buf)
require.NoError(t, err)
pingCh <- buf
if _, err := c.Write([]byte("pong")); err != nil {
assert.FailNow(t, err.Error())
}
_, err = c.Write([]byte("pong"))
require.NoError(t, err)
}()
go func() {
c, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
defer c.Close()
if _, err := socks5.ClientHandshake(c, socks5.ParseAddr("127.0.0.1:10001"), socks5.CmdConnect, nil); err != nil {
assert.FailNow(t, err.Error())
}
_, err = socks5.ClientHandshake(c, socks5.ParseAddr("127.0.0.1:10001"), socks5.CmdConnect, nil)
require.NoError(t, err)
if _, err := c.Write([]byte("ping")); err != nil {
assert.FailNow(t, err.Error())
}
_, err = c.Write([]byte("ping"))
require.NoError(t, err)
buf := make([]byte, 4)
if _, err := io.ReadFull(c, buf); err != nil {
assert.FailNow(t, err.Error())
}
_, err = io.ReadFull(c, buf)
require.NoError(t, err)
pongCh <- buf
}()
@ -304,12 +294,10 @@ func testPingPongWithConn(t *testing.T, c net.Conn) error {
func testPingPongWithPacketConn(t *testing.T, pc net.PacketConn) error {
l, err := ListenPacket("udp", ":10001")
if err != nil {
return err
}
require.NoError(t, err)
defer l.Close()
rAddr := &net.UDPAddr{IP: localIP, Port: 10001}
rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: 10001}
pingCh, pongCh, test := newPingPongPair()
go func() {
@ -349,9 +337,7 @@ type hashPair struct {
func testLargeDataWithConn(t *testing.T, c net.Conn) error {
l, err := Listen("tcp", ":10001")
if err != nil {
return err
}
require.NoError(t, err)
defer l.Close()
times := 100
@ -443,12 +429,10 @@ func testLargeDataWithConn(t *testing.T, c net.Conn) error {
func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error {
l, err := ListenPacket("udp", ":10001")
if err != nil {
return err
}
require.NoError(t, err)
defer l.Close()
rAddr := &net.UDPAddr{IP: localIP, Port: 10001}
rAddr := &net.UDPAddr{IP: localIP.AsSlice(), Port: 10001}
times := 50
chunkSize := int64(1024)
@ -541,7 +525,7 @@ func testLargeDataWithPacketConn(t *testing.T, pc net.PacketConn) error {
func testPacketConnTimeout(t *testing.T, pc net.PacketConn) error {
err := pc.SetReadDeadline(time.Now().Add(time.Millisecond * 300))
assert.NoError(t, err)
require.NoError(t, err)
errCh := make(chan error, 1)
go func() {
@ -564,9 +548,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
DstPort: "10001",
AddrType: socks5.AtypDomainName,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
defer conn.Close()
assert.NoError(t, testPingPongWithConn(t, conn))
@ -575,9 +557,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
DstPort: "10001",
AddrType: socks5.AtypDomainName,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
defer conn.Close()
assert.NoError(t, testLargeDataWithConn(t, conn))
@ -591,9 +571,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
DstPort: "10001",
AddrType: socks5.AtypIPv4,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
defer pc.Close()
assert.NoError(t, testPingPongWithPacketConn(t, pc))
@ -604,9 +582,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
DstPort: "10001",
AddrType: socks5.AtypIPv4,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
defer pc.Close()
assert.NoError(t, testLargeDataWithPacketConn(t, pc))
@ -617,9 +593,7 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
DstPort: "10001",
AddrType: socks5.AtypIPv4,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
defer pc.Close()
assert.NoError(t, testPacketConnTimeout(t, pc))
@ -627,40 +601,55 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
func benchmarkProxy(b *testing.B, proxy C.ProxyAdapter) {
l, err := Listen("tcp", ":10001")
if err != nil {
assert.FailNow(b, err.Error())
}
require.NoError(b, err)
defer l.Close()
go func() {
c, err := l.Accept()
if err != nil {
assert.FailNow(b, err.Error())
}
defer c.Close()
io.Copy(io.Discard, c)
}()
chunkSize := int64(16 * 1024)
chunk := make([]byte, chunkSize)
rand.Read(chunk)
go func() {
c, err := l.Accept()
if err != nil {
return
}
defer c.Close()
go func() {
for {
_, err := c.Write(chunk)
if err != nil {
return
}
}
}()
io.Copy(io.Discard, c)
}()
conn, err := proxy.DialContext(context.Background(), &C.Metadata{
Host: localIP.String(),
DstPort: "10001",
AddrType: socks5.AtypDomainName,
})
if err != nil {
assert.FailNow(b, err.Error())
}
require.NoError(b, err)
b.SetBytes(chunkSize)
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err := conn.Write(chunk); err != nil {
assert.FailNow(b, err.Error())
_, err = conn.Write([]byte("skip protocol handshake"))
require.NoError(b, err)
b.Run("Write", func(b *testing.B) {
b.SetBytes(chunkSize)
for i := 0; i < b.N; i++ {
conn.Write(chunk)
}
}
})
b.Run("Read", func(b *testing.B) {
b.SetBytes(chunkSize)
buf := make([]byte, chunkSize)
for i := 0; i < b.N; i++ {
io.ReadFull(conn, buf)
}
})
}
func TestClash_Basic(t *testing.T) {
@ -669,12 +658,11 @@ mixed-port: 10000
log-level: silent
`
if err := parseAndApply(basic); err != nil {
assert.FailNow(t, err.Error())
}
err := parseAndApply(basic)
require.NoError(t, err)
defer cleanup()
time.Sleep(waitTime)
require.True(t, TCPing(net.JoinHostPort(localIP.String(), "10000")))
testPingPongWithSocksPort(t, 10000)
}

View File

@ -6,6 +6,7 @@ import (
"github.com/miekg/dns"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func exchange(address, domain string, tp uint16) ([]dns.RR, error) {
@ -30,18 +31,15 @@ dns:
- 119.29.29.29
`
if err := parseAndApply(basic); err != nil {
assert.FailNow(t, err.Error())
}
err := parseAndApply(basic)
require.NoError(t, err)
defer cleanup()
time.Sleep(waitTime)
rr, err := exchange("127.0.0.1:8553", "1.1.1.1.nip.io", dns.TypeA)
assert.NoError(t, err)
if !assert.NotEmpty(t, rr) {
assert.FailNow(t, "record empty")
}
assert.NotEmptyf(t, rr, "record empty")
record := rr[0].(*dns.A)
assert.Equal(t, record.A.String(), "1.1.1.1")
@ -68,9 +66,8 @@ dns:
- 119.29.29.29
`
if err := parseAndApply(basic); err != nil {
assert.FailNow(t, err.Error())
}
err := parseAndApply(basic)
require.NoError(t, err)
defer cleanup()
time.Sleep(waitTime)

View File

@ -8,8 +8,6 @@ import (
"github.com/docker/docker/client"
)
var isDarwin = false
func startContainer(cfg *container.Config, hostCfg *container.HostConfig, name string) (string, error) {
c, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {

View File

@ -3,31 +3,29 @@ module clash-test
go 1.18
require (
github.com/Dreamacro/clash v1.7.2-0.20211108085948-bd2ea2b917aa
github.com/docker/docker v20.10.13+incompatible
github.com/Dreamacro/clash v0.0.0
github.com/docker/docker v20.10.16+incompatible
github.com/docker/go-connections v0.4.0
github.com/miekg/dns v1.1.48
github.com/miekg/dns v1.1.49
github.com/stretchr/testify v1.7.1
golang.org/x/net v0.0.0-20220421235706-1d1ef9303861
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2
)
replace github.com/Dreamacro/clash => ../
require (
github.com/Dreamacro/go-shadowsocks2 v0.1.7 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/containerd/containerd v1.6.1 // indirect
github.com/Dreamacro/go-shadowsocks2 v0.1.8 // indirect
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/gofrs/uuid v4.2.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd // indirect
github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/oschwald/geoip2-golang v1.7.0 // indirect
@ -39,22 +37,21 @@ require (
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898 // indirect
golang.org/x/exp v0.0.0-20220518171630-0b5c67f07fdf // indirect
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
golang.org/x/tools v0.1.9 // indirect
golang.org/x/tools v0.1.10 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224 // indirect
golang.zx2c4.com/wireguard v0.0.0-20220318042302-193cf8d6a5d6 // indirect
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220317000008-6432784c2469 // indirect
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
google.golang.org/grpc v1.45.0 // indirect
golang.zx2c4.com/wireguard v0.0.0-20220407013110-ef5c587f782d // indirect
golang.zx2c4.com/wireguard/windows v0.5.4-0.20220328111914-004c22c5647e // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.0 // indirect
gotest.tools/v3 v3.1.0 // indirect
gvisor.dev/gvisor v0.0.0-20220422224113-2cca6b79d9f4 // indirect
gvisor.dev/gvisor v0.0.0-20220520211629-7e72240f4f2e // indirect
)

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ import (
C "github.com/Dreamacro/clash/constant"
"github.com/docker/docker/api/types/container"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestClash_SnellObfsHTTP(t *testing.T) {
@ -24,9 +24,7 @@ func TestClash_SnellObfsHTTP(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "snell-http")
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
@ -41,9 +39,7 @@ func TestClash_SnellObfsHTTP(t *testing.T) {
"mode": "http",
},
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -61,9 +57,7 @@ func TestClash_SnellObfsTLS(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "snell-tls")
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
@ -78,9 +72,7 @@ func TestClash_SnellObfsTLS(t *testing.T) {
"mode": "tls",
},
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -98,9 +90,7 @@ func TestClash_Snell(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "snell")
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
@ -112,9 +102,7 @@ func TestClash_Snell(t *testing.T) {
Port: 10002,
Psk: "password",
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -132,9 +120,7 @@ func TestClash_Snellv3(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "snell")
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
@ -148,9 +134,7 @@ func TestClash_Snellv3(t *testing.T) {
UDP: true,
Version: 3,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -167,10 +151,8 @@ func Benchmark_Snell(b *testing.B) {
Binds: []string{fmt.Sprintf("%s:/config.conf", C.Path.Resolve("snell-http.conf"))},
}
id, err := startContainer(cfg, hostCfg, "snell-http")
if err != nil {
assert.FailNow(b, err.Error())
}
id, err := startContainer(cfg, hostCfg, "snell-bench")
require.NoError(b, err)
b.Cleanup(func() {
cleanContainer(id)
@ -185,9 +167,7 @@ func Benchmark_Snell(b *testing.B) {
"mode": "http",
},
})
if err != nil {
assert.FailNow(b, err.Error())
}
require.NoError(b, err)
time.Sleep(waitTime)
benchmarkProxy(b, proxy)

View File

@ -1,13 +1,14 @@
package main
import (
"net"
"testing"
"time"
"github.com/Dreamacro/clash/adapter/outbound"
"github.com/docker/docker/api/types/container"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestClash_Shadowsocks(t *testing.T) {
@ -22,9 +23,7 @@ func TestClash_Shadowsocks(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "ss")
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
@ -38,9 +37,7 @@ func TestClash_Shadowsocks(t *testing.T) {
Cipher: "chacha20-ietf-poly1305",
UDP: true,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -60,9 +57,7 @@ func TestClash_ShadowsocksObfsHTTP(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "ss-obfs-http")
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
@ -80,9 +75,7 @@ func TestClash_ShadowsocksObfsHTTP(t *testing.T) {
"mode": "http",
},
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -102,9 +95,7 @@ func TestClash_ShadowsocksObfsTLS(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "ss-obfs-tls")
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
@ -122,9 +113,7 @@ func TestClash_ShadowsocksObfsTLS(t *testing.T) {
"mode": "tls",
},
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -144,9 +133,7 @@ func TestClash_ShadowsocksV2RayPlugin(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "ss-v2ray-plugin")
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
@ -164,9 +151,7 @@ func TestClash_ShadowsocksV2RayPlugin(t *testing.T) {
"mode": "websocket",
},
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -183,10 +168,8 @@ func Benchmark_Shadowsocks(b *testing.B) {
PortBindings: defaultPortBindings,
}
id, err := startContainer(cfg, hostCfg, "ss")
if err != nil {
assert.FailNow(b, err.Error())
}
id, err := startContainer(cfg, hostCfg, "ss-bench")
require.NoError(b, err)
b.Cleanup(func() {
cleanContainer(id)
@ -200,10 +183,8 @@ func Benchmark_Shadowsocks(b *testing.B) {
Cipher: "aes-256-gcm",
UDP: true,
})
if err != nil {
assert.FailNow(b, err.Error())
}
require.NoError(b, err)
time.Sleep(waitTime)
require.True(b, TCPing(net.JoinHostPort(localIP.String(), "10002")))
benchmarkProxy(b, proxy)
}

View File

@ -2,6 +2,7 @@ package main
import (
"fmt"
"net"
"testing"
"time"
@ -9,7 +10,7 @@ import (
C "github.com/Dreamacro/clash/constant"
"github.com/docker/docker/api/types/container"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestClash_Trojan(t *testing.T) {
@ -27,9 +28,7 @@ func TestClash_Trojan(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "trojan")
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
@ -44,9 +43,7 @@ func TestClash_Trojan(t *testing.T) {
SkipCertVerify: true,
UDP: true,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -67,10 +64,10 @@ func TestClash_TrojanGrpc(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "trojan-grpc")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewTrojan(outbound.TrojanOption{
Name: "trojan",
@ -85,9 +82,7 @@ func TestClash_TrojanGrpc(t *testing.T) {
GrpcServiceName: "example",
},
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -108,10 +103,10 @@ func TestClash_TrojanWebsocket(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "trojan-ws")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewTrojan(outbound.TrojanOption{
Name: "trojan",
@ -123,9 +118,7 @@ func TestClash_TrojanWebsocket(t *testing.T) {
UDP: true,
Network: "ws",
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -146,10 +139,11 @@ func TestClash_TrojanXTLS(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "trojan-xtls")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewTrojan(outbound.TrojanOption{
Name: "trojan",
@ -163,9 +157,7 @@ func TestClash_TrojanXTLS(t *testing.T) {
Flow: "xtls-rprx-direct",
FlowShow: true,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -185,10 +177,8 @@ func Benchmark_Trojan(b *testing.B) {
},
}
id, err := startContainer(cfg, hostCfg, "trojan")
if err != nil {
assert.FailNow(b, err.Error())
}
id, err := startContainer(cfg, hostCfg, "trojan-bench")
require.NoError(b, err)
b.Cleanup(func() {
cleanContainer(id)
@ -203,10 +193,8 @@ func Benchmark_Trojan(b *testing.B) {
SkipCertVerify: true,
UDP: true,
})
if err != nil {
assert.FailNow(b, err.Error())
}
require.NoError(b, err)
time.Sleep(waitTime)
require.True(b, TCPing(net.JoinHostPort(localIP.String(), "10002")))
benchmarkProxy(b, proxy)
}

View File

@ -35,3 +35,16 @@ func ListenPacket(network, address string) (net.PacketConn, error) {
}
return nil, lastErr
}
func TCPing(addr string) bool {
for i := 0; i < 10; i++ {
conn, err := net.Dial("tcp", addr)
if err == nil {
conn.Close()
return true
}
time.Sleep(time.Millisecond * 500)
}
return false
}

View File

@ -9,7 +9,7 @@ import (
C "github.com/Dreamacro/clash/constant"
"github.com/docker/docker/api/types/container"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestClash_VlessTLS(t *testing.T) {
@ -27,10 +27,10 @@ func TestClash_VlessTLS(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "vless-tls")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewVless(outbound.VlessOption{
Name: "vless",
@ -41,9 +41,7 @@ func TestClash_VlessTLS(t *testing.T) {
ServerName: "example.org",
UDP: true,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -64,10 +62,10 @@ func TestClash_VlessXTLS(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "vless-xtls")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewVless(outbound.VlessOption{
Name: "vless",
@ -80,9 +78,7 @@ func TestClash_VlessXTLS(t *testing.T) {
Flow: "xtls-rprx-direct",
FlowShow: true,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -103,10 +99,10 @@ func TestClash_VlessWS(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "vless-ws")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewVless(outbound.VlessOption{
Name: "vless",
@ -118,9 +114,7 @@ func TestClash_VlessWS(t *testing.T) {
Network: "ws",
UDP: true,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)

View File

@ -9,7 +9,7 @@ import (
C "github.com/Dreamacro/clash/constant"
"github.com/docker/docker/api/types/container"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestClash_Vmess(t *testing.T) {
@ -25,9 +25,7 @@ func TestClash_Vmess(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "vmess")
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
@ -41,9 +39,7 @@ func TestClash_Vmess(t *testing.T) {
Cipher: "auto",
UDP: true,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -64,10 +60,10 @@ func TestClash_VmessTLS(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "vmess-tls")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess",
@ -80,9 +76,7 @@ func TestClash_VmessTLS(t *testing.T) {
ServerName: "example.org",
UDP: true,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -103,10 +97,10 @@ func TestClash_VmessHTTP2(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "vmess-http2")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess",
@ -124,9 +118,7 @@ func TestClash_VmessHTTP2(t *testing.T) {
Path: "/test",
},
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -145,10 +137,10 @@ func TestClash_VmessHTTP(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "vmess-http")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess",
@ -176,9 +168,7 @@ func TestClash_VmessHTTP(t *testing.T) {
},
},
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -197,10 +187,10 @@ func TestClash_VmessWebsocket(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "vmess-ws")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess",
@ -211,9 +201,7 @@ func TestClash_VmessWebsocket(t *testing.T) {
Network: "ws",
UDP: true,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -234,10 +222,10 @@ func TestClash_VmessWebsocketTLS(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "vmess-ws")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess",
@ -250,9 +238,7 @@ func TestClash_VmessWebsocketTLS(t *testing.T) {
SkipCertVerify: true,
UDP: true,
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -273,10 +259,10 @@ func TestClash_VmessGrpc(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "vmess-grpc")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess",
@ -293,9 +279,7 @@ func TestClash_VmessGrpc(t *testing.T) {
GrpcServiceName: "example!",
},
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -314,10 +298,10 @@ func TestClash_VmessWebsocket0RTT(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "vmess-ws-0rtt")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess",
@ -333,9 +317,7 @@ func TestClash_VmessWebsocket0RTT(t *testing.T) {
EarlyDataHeaderName: "Sec-WebSocket-Protocol",
},
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
@ -354,10 +336,10 @@ func TestClash_VmessWebsocketXray0RTT(t *testing.T) {
}
id, err := startContainer(cfg, hostCfg, "vmess-xray-ws-0rtt")
if err != nil {
assert.FailNow(t, err.Error())
}
defer cleanContainer(id)
require.NoError(t, err)
t.Cleanup(func() {
cleanContainer(id)
})
proxy, err := outbound.NewVmess(outbound.VmessOption{
Name: "vmess",
@ -372,16 +354,14 @@ func TestClash_VmessWebsocketXray0RTT(t *testing.T) {
Path: "/?ed=2048",
},
})
if err != nil {
assert.FailNow(t, err.Error())
}
require.NoError(t, err)
time.Sleep(waitTime)
testSuit(t, proxy)
}
func Benchmark_Vmess(b *testing.B) {
configPath := C.Path.Resolve("vmess-aead.json")
configPath := C.Path.Resolve("vmess.json")
cfg := &container.Config{
Image: ImageVmess,
@ -392,10 +372,8 @@ func Benchmark_Vmess(b *testing.B) {
Binds: []string{fmt.Sprintf("%s:/etc/v2ray/config.json", configPath)},
}
id, err := startContainer(cfg, hostCfg, "vmess-aead")
if err != nil {
assert.FailNow(b, err.Error())
}
id, err := startContainer(cfg, hostCfg, "vmess-bench")
require.NoError(b, err)
b.Cleanup(func() {
cleanContainer(id)
@ -410,9 +388,7 @@ func Benchmark_Vmess(b *testing.B) {
AlterID: 0,
UDP: true,
})
if err != nil {
assert.FailNow(b, err.Error())
}
require.NoError(b, err)
time.Sleep(waitTime)
benchmarkProxy(b, proxy)

View File

@ -154,6 +154,9 @@ func (a *authAES128) Encode(buf *bytes.Buffer, b []byte) error {
}
func (a *authAES128) DecodePacket(b []byte) ([]byte, error) {
if len(b) < 4 {
return nil, errAuthAES128LengthError
}
if !bytes.Equal(a.hmac(a.Key, b[:len(b)-4])[:4], b[len(b)-4:]) {
return nil, errAuthAES128ChksumError
}

View File

@ -133,7 +133,7 @@ func (t *Trojan) StreamWebsocketConn(conn net.Conn, wsOptions *WebsocketOption)
})
}
func (t *Trojan) PresetXTLSConn(conn net.Conn) (net.Conn, error) {
func (t *Trojan) PrepareXTLSConn(conn net.Conn) (net.Conn, error) {
switch t.option.Flow {
case vless.XRO, vless.XRD, vless.XRS:
if xtlsConn, ok := conn.(*xtls.Conn); ok {
@ -189,7 +189,7 @@ func writePacket(w io.Writer, socks5Addr, payload []byte) (int, error) {
defer pool.PutBuffer(buf)
buf.Write(socks5Addr)
binary.Write(buf, binary.BigEndian, uint16(len(payload)))
_ = binary.Write(buf, binary.BigEndian, uint16(len(payload)))
buf.Write(crlf)
buf.Write(payload)

View File

@ -26,11 +26,9 @@ func (r *sniffing) Read(b []byte) (int, error) {
func (r *sniffing) Write(b []byte) (int, error) {
if r.totalWrite.Load() < 128 && r.metadata.Host == "" && (r.metadata.DstPort == "443" || r.metadata.DstPort == "8443" || r.metadata.DstPort == "993" || r.metadata.DstPort == "465" || r.metadata.DstPort == "995") {
header, err := tls.SniffTLS(b)
if err != nil {
// log.Errorln("Expect no error but actually %s %s:%s:%s", err.Error(), tt.Metadata.Host, tt.Metadata.DstIP.String(), tt.Metadata.DstPort)
} else {
if err == nil {
resolver.InsertHostByIP(r.metadata.DstIP, header.Domain())
log.Warnln("use sni update host: %s ip: %s", header.Domain(), r.metadata.DstIP.String())
log.Debugln("[SNIFFER] use sni update host: %s ip: %s", header.Domain(), r.metadata.DstIP.String())
if r.allowBreak {
_ = r.Conn.Close()
return 0, errors.New("sni update, break current link to avoid leaks")

View File

@ -58,13 +58,13 @@ func (tt *tcpTracker) Close() error {
}
func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) C.Conn {
uuid, _ := uuid.NewV4()
uuidM, _ := uuid.NewV4()
t := &tcpTracker{
Conn: conn,
manager: manager,
trackerInfo: &trackerInfo{
UUID: uuid,
UUID: uuidM,
Start: time.Now(),
Metadata: metadata,
Chain: conn.Chains(),
@ -80,7 +80,7 @@ func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R
}
manager.Join(t)
return NewSniffing(t, metadata, rule)
return t
}
type udpTracker struct {
@ -115,13 +115,13 @@ func (ut *udpTracker) Close() error {
}
func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker {
uuid, _ := uuid.NewV4()
uuidM, _ := uuid.NewV4()
ut := &udpTracker{
PacketConn: conn,
manager: manager,
trackerInfo: &trackerInfo{
UUID: uuid,
UUID: uuidM,
Start: time.Now(),
Metadata: metadata,
Chain: conn.Chains(),

View File

@ -38,6 +38,9 @@ var (
// Outbound Rule
mode = Rule
// sniffing switch
sniffing = false
// default timeout for UDP session
udpTimeout = 60 * time.Second
@ -99,9 +102,21 @@ func SetMode(m TunnelMode) {
mode = m
}
func Sniffing() bool {
return sniffing
}
func SetSniffing(s bool) {
sniffing = s
}
// SetMitmOutbound set the MITM outbound
func SetMitmOutbound(outbound C.ProxyAdapter) {
mitmProxy = A.NewProxy(outbound)
if outbound != nil {
mitmProxy = A.NewProxy(outbound)
} else {
mitmProxy = nil
}
}
// Rewrites return all rewrites
@ -269,7 +284,7 @@ func handleUDPConn(packet *inbound.PacketAdapter) {
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultUDPTimeout)
defer cancel()
rawPc, err := proxy.ListenPacketContext(ctx, metadata.Pure())
rawPc, err := proxy.ListenPacketContext(ctx, metadata.Pure(false))
if err != nil {
if rule == nil {
log.Warnln("[UDP] dial %s to %s error: %s", proxy.Name(), metadata.RemoteAddress(), err.Error())
@ -321,14 +336,11 @@ func handleTCPConn(connCtx C.ConnContext) {
return
}
mtd := metadata
if proxy != mitmProxy {
mtd = metadata.Pure()
}
isMitmOutbound := proxy == mitmProxy
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
defer cancel()
remoteConn, err := proxy.DialContext(ctx, mtd)
remoteConn, err := proxy.DialContext(ctx, metadata.Pure(isMitmOutbound))
if err != nil {
if rule == nil {
log.Warnln("[TCP] dial %s to %s error: %s", proxy.Name(), metadata.RemoteAddress(), err.Error())
@ -338,8 +350,11 @@ func handleTCPConn(connCtx C.ConnContext) {
return
}
if remoteConn.Chains().Last() != "REJECT" && proxy != mitmProxy {
if remoteConn.Chains().Last() != "REJECT" && !isMitmOutbound {
remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule)
if sniffing {
remoteConn = statistic.NewSniffing(remoteConn, metadata, rule)
}
}
defer func(remoteConn C.Conn) {
@ -347,7 +362,7 @@ func handleTCPConn(connCtx C.ConnContext) {
}(remoteConn)
switch true {
case proxy == mitmProxy:
case isMitmOutbound:
break
case rule != nil:
log.Infoln("[TCP] %s(%s) --> %s match %s(%s) using %s", metadata.SourceAddress(), metadata.Process, metadata.RemoteAddress(), rule.RuleType().String(), rule.Payload(), remoteConn.Chains().String())