Compare commits

...

36 Commits

Author SHA1 Message Date
d78b2b1cfb Merge pull request #256 from Skimmle/Alpha
featrue: DoH and DoQ are implemented using AdGuardTeam/dnsProxy
2022-11-12 11:19:04 +08:00
3e20912339 featrue: DoH and DoQ are implemented using AdGuardTeam/dnsProxy, DoH support perfer and force http3 2022-11-12 11:14:51 +08:00
b2d7149a95 chore: support IN-PORT rule 2022-11-11 23:36:06 +08:00
64be213b66 code cleanup 2022-11-11 22:48:44 +08:00
68b28ed530 chore: shadowsocks listener support old cipher 2022-11-11 22:44:44 +08:00
3eacce9a66 chore: add vmess, shadowsocks, tcptun and udptun listener 2022-11-11 20:56:08 +08:00
6dadc2357a chore: remove AddrType on Metadata 2022-11-11 09:19:50 +08:00
698d8ca701 Update README.md 2022-11-11 04:05:13 +08:00
1a4b00c70e fix: update sing-vmess 2022-11-10 21:23:52 +08:00
64552fbd00 fix: when host's ip in fakeip's range, don't send to remote server 2022-11-10 21:08:06 +08:00
7c8d8f56e1 Update docs/config.yaml 2022-11-09 22:55:32 +08:00
93ada8989f Update README.md 2022-11-09 19:55:30 +08:00
4b4c3dc41e fix: small-case import name 2022-11-09 19:42:56 +08:00
b699fb046b fix: wireguard's dns resolve 2022-11-09 19:35:03 +08:00
ae08d13de4 chore: support wireguard outbound 2022-11-09 18:44:06 +08:00
1d784231b0 fix: exclude-filter not work when filter is empty 2022-11-09 08:41:30 +08:00
5fd79890e7 chore: add exclude-filter to ProxyGroup 2022-11-09 08:06:37 +08:00
53b2a480ef fix: subscriptionInfo api 2022-11-08 22:30:50 +08:00
943137de3b Merge pull request #250 from Skimmle/Alpha
chore: upgrade dependencies
2022-11-08 15:58:21 +08:00
2d3aad573e chore: upgrade dependencies 2022-11-08 15:50:01 +08:00
409cd4f6a1 fix: subscriptionInfo api
fix: subscriptionInfo api

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

chore: adjust tun demo
2022-11-07 18:33:27 +08:00
4673d2093a fix: context import 2022-11-06 08:43:39 +08:00
94a765ee31 fix: avoid choose ZeroTier's tap to defaultInterface 2022-11-05 20:51:28 +08:00
dcd2417fce feat: subscriptionInfo 2022-11-05 19:39:17 +08:00
4c5853e5e7 feat: Converter Shadowsocks UoT support 2022-11-05 06:41:07 +00:00
52f4cb599a fix: pool_test.go 2022-11-05 13:08:50 +08:00
90f6cc233c fix: correct yaml config name 2022-11-04 17:38:24 +08:00
e20d01a679 chore: try to let tun's restful patch api work 2022-11-04 08:52:30 +08:00
9a5c0a4b6d chore: better tun config passing 2022-11-03 18:56:03 +08:00
1b0d09068b fix: RESTful API empty tun device name 2022-11-03 18:04:37 +08:00
3373b62b02 fix: try to support android hotspot when using tun 2022-11-03 12:58:21 +08:00
508e257543 fix: RESTful API sniffingEnable 2022-11-03 00:31:31 +08:00
7b0cd14b00 chore: netlink duplicate contains 2022-11-02 23:58:51 +08:00
22fb219ad8 chore: trie.DomainTrie will not depend on zero value 2022-11-02 22:28:18 +08:00
c34c5ff1f9 build: fix golang build cache 2022-11-02 22:13:54 +08:00
79 changed files with 3323 additions and 940 deletions

View File

@ -5,12 +5,14 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v1
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: 1.18
- name: Check out code
uses: actions/checkout@v1
go-version: '1.19'
check-latest: true
cache: true
- name: Build
run: make all
- name: Release

View File

@ -12,26 +12,15 @@ jobs:
Build:
runs-on: ubuntu-latest
steps:
- name: Get latest go version
id: version
run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Cache go module
uses: actions/cache@v2
- name: Setup Go
uses: actions/setup-go@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
go-version: '1.19'
check-latest: true
cache: true
- name: Test
if: ${{github.ref_name=='Beta'}}

View File

@ -7,24 +7,16 @@ jobs:
Build:
runs-on: ubuntu-latest
steps:
- name: Get latest go version
id: version
run: |
echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g')
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Cache go module
uses: actions/cache@v2
- name: Setup Go
uses: actions/setup-go@v3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
go-version: '1.19'
check-latest: true
cache: true
- name: Test
run: |
go test ./...

View File

@ -212,6 +212,21 @@ proxies:
grpc-service-name: grpcname
```
Support outbound transport protocol `Wireguard`
```yaml
proxies:
- name: "wg"
type: wireguard
server: 162.159.192.1
port: 2480
ip: 172.16.0.2
ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5
private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
udp: true
```
### IPTABLES configuration
Work on Linux OS who's supported `iptables`
@ -286,6 +301,7 @@ the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library
## Credits
* [Dreamacro/clash](https://github.com/Dreamacro/clash)
* [SagerNet/sing-box](https://github.com/SagerNet/sing-box)
* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
* [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core)
* [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go)

View File

@ -198,10 +198,9 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
}
addr = C.Metadata{
AddrType: C.AtypDomainName,
Host: u.Hostname(),
DstIP: netip.Addr{},
DstPort: port,
Host: u.Hostname(),
DstIP: netip.Addr{},
DstPort: port,
}
return
}

View File

@ -17,5 +17,9 @@ func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn) *context.ConnCo
metadata.SrcIP = ip
metadata.SrcPort = port
}
if ip, port, err := parseAddr(conn.LocalAddr().String()); err == nil {
metadata.InIP = ip
metadata.InPort = port
}
return context.NewConnContext(conn, metadata)
}

View File

@ -16,5 +16,9 @@ func NewHTTPS(request *http.Request, conn net.Conn) *context.ConnContext {
metadata.SrcIP = ip
metadata.SrcPort = port
}
if ip, port, err := parseAddr(conn.LocalAddr().String()); err == nil {
metadata.InIP = ip
metadata.InPort = port
}
return context.NewConnContext(conn, metadata)
}

View File

@ -25,6 +25,12 @@ func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type) *PacketAda
metadata.SrcIP = ip
metadata.SrcPort = port
}
if p, ok := packet.(C.UDPPacketInAddr); ok {
if ip, port, err := parseAddr(p.InAddr().String()); err == nil {
metadata.InIP = ip
metadata.InPort = port
}
}
return &PacketAdapter{
UDPPacket: packet,

View File

@ -22,6 +22,14 @@ func NewSocket(target socks5.Addr, conn net.Conn, source C.Type) *context.ConnCo
metadata.SrcPort = port
}
}
localAddr := conn.LocalAddr()
// Filter when net.Addr interface is nil
if localAddr != nil {
if ip, port, err := parseAddr(localAddr.String()); err == nil {
metadata.InIP = ip
metadata.InPort = port
}
}
return context.NewConnContext(conn, metadata)
}
@ -32,17 +40,12 @@ func NewInner(conn net.Conn, dst string, host string) *context.ConnContext {
metadata.Type = C.INNER
metadata.DNSMode = C.DNSMapping
metadata.Host = host
metadata.AddrType = C.AtypDomainName
metadata.Process = C.ClashName
if h, port, err := net.SplitHostPort(dst); err == nil {
metadata.DstPort = port
if host == "" {
if ip, err := netip.ParseAddr(h); err == nil {
metadata.DstIP = ip
metadata.AddrType = C.AtypIPv4
if ip.Is6() {
metadata.AddrType = C.AtypIPv6
}
}
}
}

View File

@ -13,9 +13,7 @@ import (
)
func parseSocksAddr(target socks5.Addr) *C.Metadata {
metadata := &C.Metadata{
AddrType: int(target[0]),
}
metadata := &C.Metadata{}
switch target[0] {
case socks5.AtypDomainName:
@ -45,21 +43,14 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
host = strings.TrimRight(host, ".")
metadata := &C.Metadata{
NetWork: C.TCP,
AddrType: C.AtypDomainName,
Host: host,
DstIP: netip.Addr{},
DstPort: port,
NetWork: C.TCP,
Host: host,
DstIP: netip.Addr{},
DstPort: port,
}
ip, err := netip.ParseAddr(host)
if err == nil {
switch {
case ip.Is6():
metadata.AddrType = C.AtypIPv6
default:
metadata.AddrType = C.AtypIPv4
}
metadata.DstIP = ip
}

View File

@ -44,10 +44,11 @@ func getClientXSessionCache() xtls.ClientSessionCache {
func serializesSocksAddr(metadata *C.Metadata) []byte {
var buf [][]byte
aType := uint8(metadata.AddrType)
addrType := metadata.AddrType()
aType := uint8(addrType)
p, _ := strconv.ParseUint(metadata.DstPort, 10, 16)
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
switch metadata.AddrType {
switch addrType {
case socks5.AtypDomainName:
lenM := uint8(len(metadata.Host))
host := []byte(metadata.Host)

View File

@ -6,18 +6,19 @@ import (
"encoding/binary"
"errors"
"fmt"
"github.com/Dreamacro/clash/common/convert"
tlsC "github.com/Dreamacro/clash/component/tls"
"io"
"net"
"net/http"
"strconv"
"sync"
"github.com/Dreamacro/clash/common/convert"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/clash/transport/vless"
"github.com/Dreamacro/clash/transport/vmess"
)
@ -280,16 +281,16 @@ func (v *Vless) SupportUOT() bool {
func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr {
var addrType byte
var addr []byte
switch metadata.AddrType {
case C.AtypIPv4:
switch metadata.AddrType() {
case socks5.AtypIPv4:
addrType = vless.AtypIPv4
addr = make([]byte, net.IPv4len)
copy(addr[:], metadata.DstIP.AsSlice())
case C.AtypIPv6:
case socks5.AtypIPv6:
addrType = vless.AtypIPv6
addr = make([]byte, net.IPv6len)
copy(addr[:], metadata.DstIP.AsSlice())
case C.AtypDomainName:
case socks5.AtypDomainName:
addrType = vless.AtypDomainName
addr = make([]byte, len(metadata.Host)+1)
addr[0] = byte(len(metadata.Host))

View File

@ -0,0 +1,328 @@
package outbound
import (
"context"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"net"
"net/netip"
"runtime"
"strconv"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/listener/sing"
wireguard "github.com/metacubex/sing-wireguard"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/wireguard-go/device"
)
type WireGuard struct {
*Base
bind *wireguard.ClientBind
device *device.Device
tunDevice wireguard.Device
dialer *wgDialer
startOnce sync.Once
}
type WireGuardOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Ip string `proxy:"ip,omitempty"`
Ipv6 string `proxy:"ipv6,omitempty"`
PrivateKey string `proxy:"private-key"`
PublicKey string `proxy:"public-key"`
PreSharedKey string `proxy:"pre-shared-key,omitempty"`
Reserved []int `proxy:"reserved,omitempty"`
Workers int `proxy:"workers,omitempty"`
MTU int `proxy:"mtu,omitempty"`
UDP bool `proxy:"udp,omitempty"`
}
type wgDialer struct {
options []dialer.Option
}
func (d *wgDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
return dialer.DialContext(ctx, network, destination.String(), d.options...)
}
func (d *wgDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return dialer.ListenPacket(ctx, "udp", "", d.options...)
}
func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
outbound := &WireGuard{
Base: &Base{
name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.WireGuard,
udp: option.UDP,
iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion),
},
dialer: &wgDialer{},
}
runtime.SetFinalizer(outbound, closeWireGuard)
var reserved [3]uint8
if len(option.Reserved) > 0 {
if len(option.Reserved) != 3 {
return nil, E.New("invalid reserved value, required 3 bytes, got ", len(option.Reserved))
}
reserved[0] = uint8(option.Reserved[0])
reserved[1] = uint8(option.Reserved[1])
reserved[2] = uint8(option.Reserved[2])
}
peerAddr := M.ParseSocksaddr(option.Server)
peerAddr.Port = uint16(option.Port)
outbound.bind = wireguard.NewClientBind(context.Background(), outbound.dialer, peerAddr, reserved)
localPrefixes := make([]netip.Prefix, 0, 2)
if len(option.Ip) > 0 {
if !strings.Contains(option.Ip, "/") {
option.Ip = option.Ip + "/32"
}
if prefix, err := netip.ParsePrefix(option.Ip); err == nil {
localPrefixes = append(localPrefixes, prefix)
} else {
return nil, E.Cause(err, "ip address parse error")
}
}
if len(option.Ipv6) > 0 {
if !strings.Contains(option.Ipv6, "/") {
option.Ipv6 = option.Ipv6 + "/128"
}
if prefix, err := netip.ParsePrefix(option.Ipv6); err == nil {
localPrefixes = append(localPrefixes, prefix)
} else {
return nil, E.Cause(err, "ipv6 address parse error")
}
}
if len(localPrefixes) == 0 {
return nil, E.New("missing local address")
}
var privateKey, peerPublicKey, preSharedKey string
{
bytes, err := base64.StdEncoding.DecodeString(option.PrivateKey)
if err != nil {
return nil, E.Cause(err, "decode private key")
}
privateKey = hex.EncodeToString(bytes)
}
{
bytes, err := base64.StdEncoding.DecodeString(option.PublicKey)
if err != nil {
return nil, E.Cause(err, "decode peer public key")
}
peerPublicKey = hex.EncodeToString(bytes)
}
if option.PreSharedKey != "" {
bytes, err := base64.StdEncoding.DecodeString(option.PreSharedKey)
if err != nil {
return nil, E.Cause(err, "decode pre shared key")
}
preSharedKey = hex.EncodeToString(bytes)
}
ipcConf := "private_key=" + privateKey
ipcConf += "\npublic_key=" + peerPublicKey
ipcConf += "\nendpoint=" + peerAddr.String()
if preSharedKey != "" {
ipcConf += "\npreshared_key=" + preSharedKey
}
var has4, has6 bool
for _, address := range localPrefixes {
if address.Addr().Is4() {
has4 = true
} else {
has6 = true
}
}
if has4 {
ipcConf += "\nallowed_ip=0.0.0.0/0"
}
if has6 {
ipcConf += "\nallowed_ip=::/0"
}
mtu := option.MTU
if mtu == 0 {
mtu = 1408
}
var err error
outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu))
if err != nil {
return nil, E.Cause(err, "create WireGuard device")
}
outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, &device.Logger{
Verbosef: func(format string, args ...interface{}) {
sing.Logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
},
Errorf: func(format string, args ...interface{}) {
sing.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...))
},
}, option.Workers)
if debug.Enabled {
sing.Logger.Trace("created wireguard ipc conf: \n", ipcConf)
}
err = outbound.device.IpcSet(ipcConf)
if err != nil {
return nil, E.Cause(err, "setup wireguard")
}
//err = outbound.tunDevice.Start()
return outbound, nil
}
func closeWireGuard(w *WireGuard) {
if w.device != nil {
w.device.Close()
}
_ = common.Close(w.tunDevice)
}
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
w.dialer.options = opts
var conn net.Conn
w.startOnce.Do(func() {
err = w.tunDevice.Start()
})
if err != nil {
return nil, err
}
if !metadata.Resolved() {
var addrs []netip.Addr
addrs, err = resolver.ResolveAllIP(metadata.Host)
if err != nil {
return nil, err
}
conn, err = N.DialSerial(ctx, w.tunDevice, "tcp", M.ParseSocksaddr(metadata.RemoteAddress()), addrs)
} else {
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.ParseSocksaddr(metadata.Pure().RemoteAddress()))
}
if err != nil {
return nil, err
}
return NewConn(&wgConn{conn, w}, w), nil
}
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
w.dialer.options = opts
var pc net.PacketConn
w.startOnce.Do(func() {
err = w.tunDevice.Start()
})
if err != nil {
return nil, err
}
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
pc, err = w.tunDevice.ListenPacket(ctx, M.ParseSocksaddr(metadata.Pure().RemoteAddress()))
if err != nil {
return nil, err
}
return newPacketConn(&wgPacketConn{pc, w}, w), nil
}
type wgConn struct {
conn net.Conn
wg *WireGuard
}
func (c *wgConn) Read(b []byte) (n int, err error) {
defer runtime.KeepAlive(c.wg)
return c.conn.Read(b)
}
func (c *wgConn) Write(b []byte) (n int, err error) {
defer runtime.KeepAlive(c.wg)
return c.conn.Write(b)
}
func (c *wgConn) Close() error {
defer runtime.KeepAlive(c.wg)
return c.conn.Close()
}
func (c *wgConn) LocalAddr() net.Addr {
defer runtime.KeepAlive(c.wg)
return c.conn.LocalAddr()
}
func (c *wgConn) RemoteAddr() net.Addr {
defer runtime.KeepAlive(c.wg)
return c.conn.RemoteAddr()
}
func (c *wgConn) SetDeadline(t time.Time) error {
defer runtime.KeepAlive(c.wg)
return c.conn.SetDeadline(t)
}
func (c *wgConn) SetReadDeadline(t time.Time) error {
defer runtime.KeepAlive(c.wg)
return c.conn.SetReadDeadline(t)
}
func (c *wgConn) SetWriteDeadline(t time.Time) error {
defer runtime.KeepAlive(c.wg)
return c.conn.SetWriteDeadline(t)
}
type wgPacketConn struct {
pc net.PacketConn
wg *WireGuard
}
func (pc *wgPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
defer runtime.KeepAlive(pc.wg)
return pc.pc.ReadFrom(p)
}
func (pc *wgPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
defer runtime.KeepAlive(pc.wg)
return pc.pc.WriteTo(p, addr)
}
func (pc *wgPacketConn) Close() error {
defer runtime.KeepAlive(pc.wg)
return pc.pc.Close()
}
func (pc *wgPacketConn) LocalAddr() net.Addr {
defer runtime.KeepAlive(pc.wg)
return pc.pc.LocalAddr()
}
func (pc *wgPacketConn) SetDeadline(t time.Time) error {
defer runtime.KeepAlive(pc.wg)
return pc.pc.SetDeadline(t)
}
func (pc *wgPacketConn) SetReadDeadline(t time.Time) error {
defer runtime.KeepAlive(pc.wg)
return pc.pc.SetReadDeadline(t)
}
func (pc *wgPacketConn) SetWriteDeadline(t time.Time) error {
defer runtime.KeepAlive(pc.wg)
return pc.pc.SetWriteDeadline(t)
}

View File

@ -131,6 +131,7 @@ func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider)
RoutingMark: option.RoutingMark,
},
option.Filter,
option.ExcludeFilter,
providers,
}),
disableUDP: option.DisableUDP,

View File

@ -18,23 +18,30 @@ import (
type GroupBase struct {
*outbound.Base
filterRegs []*regexp2.Regexp
providers []provider.ProxyProvider
failedTestMux sync.Mutex
failedTimes int
failedTime time.Time
failedTesting *atomic.Bool
proxies [][]C.Proxy
versions []atomic.Uint32
filterRegs []*regexp2.Regexp
excludeFilterReg *regexp2.Regexp
providers []provider.ProxyProvider
failedTestMux sync.Mutex
failedTimes int
failedTime time.Time
failedTesting *atomic.Bool
proxies [][]C.Proxy
versions []atomic.Uint32
}
type GroupBaseOption struct {
outbound.BaseOption
filter string
providers []provider.ProxyProvider
filter string
excludeFilter string
providers []provider.ProxyProvider
}
func NewGroupBase(opt GroupBaseOption) *GroupBase {
var excludeFilterReg *regexp2.Regexp
if opt.excludeFilter != "" {
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, 0)
}
var filterRegs []*regexp2.Regexp
if opt.filter != "" {
for _, filter := range strings.Split(opt.filter, "`") {
@ -44,10 +51,11 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
}
gb := &GroupBase{
Base: outbound.NewBase(opt.BaseOption),
filterRegs: filterRegs,
providers: opt.providers,
failedTesting: atomic.NewBool(false),
Base: outbound.NewBase(opt.BaseOption),
filterRegs: filterRegs,
excludeFilterReg: excludeFilterReg,
providers: opt.providers,
failedTesting: atomic.NewBool(false),
}
gb.proxies = make([][]C.Proxy, len(opt.providers))
@ -63,59 +71,54 @@ func (gb *GroupBase) Touch() {
}
func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
var proxies []C.Proxy
if len(gb.filterRegs) == 0 {
var proxies []C.Proxy
for _, pd := range gb.providers {
if touch {
pd.Touch()
}
proxies = append(proxies, pd.Proxies()...)
}
if len(proxies) == 0 {
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
}
return proxies
}
} else {
for i, pd := range gb.providers {
if touch {
pd.Touch()
}
for i, pd := range gb.providers {
if touch {
pd.Touch()
}
if pd.VehicleType() == types.Compatible {
gb.versions[i].Store(pd.Version())
gb.proxies[i] = pd.Proxies()
continue
}
if pd.VehicleType() == types.Compatible {
gb.versions[i].Store(pd.Version())
gb.proxies[i] = pd.Proxies()
continue
}
version := gb.versions[i].Load()
if version != pd.Version() && gb.versions[i].CompareAndSwap(version, pd.Version()) {
var (
proxies []C.Proxy
newProxies []C.Proxy
)
version := gb.versions[i].Load()
if version != pd.Version() && gb.versions[i].CompareAndSwap(version, pd.Version()) {
var (
proxies []C.Proxy
newProxies []C.Proxy
)
proxies = pd.Proxies()
proxiesSet := map[string]struct{}{}
for _, filterReg := range gb.filterRegs {
for _, p := range proxies {
name := p.Name()
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
if _, ok := proxiesSet[name]; !ok {
proxiesSet[name] = struct{}{}
newProxies = append(newProxies, p)
proxies = pd.Proxies()
proxiesSet := map[string]struct{}{}
for _, filterReg := range gb.filterRegs {
for _, p := range proxies {
name := p.Name()
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
if _, ok := proxiesSet[name]; !ok {
proxiesSet[name] = struct{}{}
newProxies = append(newProxies, p)
}
}
}
}
gb.proxies[i] = newProxies
}
gb.proxies[i] = newProxies
}
}
var proxies []C.Proxy
for _, p := range gb.proxies {
proxies = append(proxies, p...)
for _, p := range gb.proxies {
proxies = append(proxies, p...)
}
}
if len(proxies) == 0 {
@ -146,6 +149,18 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
proxies = newProxies
}
if gb.excludeFilterReg != nil {
var newProxies []C.Proxy
for _, p := range proxies {
name := p.Name()
if mat, _ := gb.excludeFilterReg.FindStringMatch(name); mat != nil {
continue
}
newProxies = append(newProxies, p)
}
proxies = newProxies
}
return proxies
}

View File

@ -228,6 +228,7 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
RoutingMark: option.RoutingMark,
},
option.Filter,
option.ExcludeFilter,
providers,
}),
strategyFn: strategyFn,

View File

@ -21,15 +21,16 @@ var (
type GroupCommonOption struct {
outbound.BasicOption
Name string `group:"name"`
Type string `group:"type"`
Proxies []string `group:"proxies,omitempty"`
Use []string `group:"use,omitempty"`
URL string `group:"url,omitempty"`
Interval int `group:"interval,omitempty"`
Lazy bool `group:"lazy,omitempty"`
DisableUDP bool `group:"disable-udp,omitempty"`
Filter string `group:"filter,omitempty"`
Name string `group:"name"`
Type string `group:"type"`
Proxies []string `group:"proxies,omitempty"`
Use []string `group:"use,omitempty"`
URL string `group:"url,omitempty"`
Interval int `group:"interval,omitempty"`
Lazy bool `group:"lazy,omitempty"`
DisableUDP bool `group:"disable-udp,omitempty"`
Filter string `group:"filter,omitempty"`
ExcludeFilter string `group:"exclude-filter,omitempty"`
}
func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {

View File

@ -185,6 +185,7 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re
RoutingMark: option.RoutingMark,
},
"",
"",
providers,
}),
}

View File

@ -99,6 +99,7 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider)
RoutingMark: option.RoutingMark,
},
option.Filter,
option.ExcludeFilter,
providers,
}),
selected: "COMPATIBLE",

View File

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

View File

@ -16,32 +16,19 @@ func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
return
}
ip, err := netip.ParseAddr(host)
if err != nil {
if ip, err := netip.ParseAddr(host); err != nil {
addr = &C.Metadata{
AddrType: C.AtypDomainName,
Host: host,
DstIP: netip.Addr{},
DstPort: port,
Host: host,
DstPort: port,
}
err = nil
return
} else if ip.Is4() {
} else {
addr = &C.Metadata{
AddrType: C.AtypIPv4,
Host: "",
DstIP: ip,
DstPort: port,
Host: "",
DstIP: ip,
DstPort: port,
}
return
}
addr = &C.Metadata{
AddrType: C.AtypIPv6,
Host: "",
DstIP: ip,
DstPort: port,
}
return
}

View File

@ -88,6 +88,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
break
}
proxy, err = outbound.NewHysteria(*hyOption)
case "wireguard":
hyOption := &outbound.WireGuardOption{}
err = decoder.Decode(mapping, hyOption)
if err != nil {
break
}
proxy, err = outbound.NewWireGuard(*hyOption)
default:
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
}

View File

@ -1,12 +1,16 @@
package provider
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/Dreamacro/clash/common/convert"
clashHttp "github.com/Dreamacro/clash/component/http"
"github.com/Dreamacro/clash/component/resource"
"github.com/Dreamacro/clash/log"
"github.com/dlclark/regexp2"
"net/http"
"runtime"
"strings"
"time"
@ -33,18 +37,20 @@ type ProxySetProvider struct {
type proxySetProvider struct {
*resource.Fetcher[[]C.Proxy]
proxies []C.Proxy
healthCheck *HealthCheck
version uint32
proxies []C.Proxy
healthCheck *HealthCheck
version uint32
subscriptionInfo *SubscriptionInfo
}
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"name": pp.Name(),
"type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(),
"updatedAt": pp.UpdatedAt,
"name": pp.Name(),
"type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(),
"updatedAt": pp.UpdatedAt,
"subscriptionInfo": pp.subscriptionInfo,
})
}
@ -97,6 +103,40 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
}
}
func (pp *proxySetProvider) getSubscriptionInfo() {
if pp.VehicleType() != types.HTTP {
return
}
go func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
defer cancel()
resp, err := clashHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
if err != nil {
return
}
defer resp.Body.Close()
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
if userInfoStr == "" {
resp2, err := clashHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil)
if err != nil {
return
}
defer resp2.Body.Close()
userInfoStr = strings.TrimSpace(resp2.Header.Get("subscription-userinfo"))
if userInfoStr == "" {
return
}
}
pp.subscriptionInfo, err = NewSubscriptionInfo(userInfoStr)
if err != nil {
log.Warnln("[Provider] get subscription-userinfo: %e", err)
}
}()
}
func stopProxyProvider(pd *ProxySetProvider) {
pd.healthCheck.close()
_ = pd.Fetcher.Destroy()
@ -128,6 +168,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, filterRegs, excludeFilterReg), proxiesOnUpdate(pd))
pd.Fetcher = fetcher
pd.getSubscriptionInfo()
wrapper := &ProxySetProvider{pd}
runtime.SetFinalizer(wrapper, stopProxyProvider)
return wrapper, nil
@ -218,6 +259,7 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
return func(elm []C.Proxy) {
pd.setProxies(elm)
pd.version += 1
pd.getSubscriptionInfo()
}
}

View File

@ -0,0 +1,57 @@
package provider
import (
"github.com/dlclark/regexp2"
"strconv"
"strings"
)
type SubscriptionInfo struct {
Upload int64
Download int64
Total int64
Expire int64
}
func NewSubscriptionInfo(str string) (si *SubscriptionInfo, err error) {
si = &SubscriptionInfo{}
str = strings.ToLower(str)
reTraffic := regexp2.MustCompile("upload=(\\d+); download=(\\d+); total=(\\d+)", 0)
reExpire := regexp2.MustCompile("expire=(\\d+)", 0)
match, err := reTraffic.FindStringMatch(str)
if err != nil || match == nil {
return nil, err
}
group := match.Groups()
si.Upload, err = str2uint64(group[1].String())
if err != nil {
return nil, err
}
si.Download, err = str2uint64(group[2].String())
if err != nil {
return nil, err
}
si.Total, err = str2uint64(group[3].String())
if err != nil {
return nil, err
}
match, _ = reExpire.FindStringMatch(str)
if match != nil {
group = match.Groups()
si.Expire, err = str2uint64(group[1].String())
if err != nil {
return nil, err
}
}
return
}
func str2uint64(str string) (int64, error) {
i, err := strconv.ParseInt(str, 10, 64)
return i, err
}

View File

@ -287,7 +287,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
}
}
ss := make(map[string]any, 20)
ss := make(map[string]any, 10)
ss["name"] = name
ss["type"] = scheme
@ -297,6 +297,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
ss["password"] = password
query := urlSS.Query()
ss["udp"] = true
if query.Get("udp-over-tcp") == "true" || query.Get("uot") == "1" {
ss["udp-over-tcp"] = true
}
if strings.Contains(query.Get("plugin"), "obfs") {
obfsParams := strings.Split(query.Get("plugin"), ";")
ss["plugin"] = "obfs"

View File

@ -6,13 +6,12 @@ import (
"fmt"
"net/netip"
"github.com/vishvananda/netlink"
"github.com/Dreamacro/clash/common/cmd"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/ebpf/redir"
"github.com/Dreamacro/clash/component/ebpf/tc"
C "github.com/Dreamacro/clash/constant"
"github.com/sagernet/netlink"
)
func GetAutoDetectInterface() (string, error) {

View File

@ -13,7 +13,7 @@ import (
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/rlimit"
"github.com/vishvananda/netlink"
"github.com/sagernet/netlink"
"golang.org/x/sys/unix"
"github.com/Dreamacro/clash/component/ebpf/byteorder"

View File

@ -11,7 +11,7 @@ import (
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/rlimit"
"github.com/vishvananda/netlink"
"github.com/sagernet/netlink"
"golang.org/x/sys/unix"
C "github.com/Dreamacro/clash/constant"

View File

@ -34,7 +34,7 @@ type Pool struct {
offset netip.Addr
cycle bool
mux sync.Mutex
host *trie.DomainTrie[bool]
host *trie.DomainTrie[struct{}]
ipnet *netip.Prefix
store store
}
@ -150,7 +150,7 @@ func (p *Pool) restoreState() {
type Options struct {
IPNet *netip.Prefix
Host *trie.DomainTrie[bool]
Host *trie.DomainTrie[struct{}]
// Size sets the maximum number of entries in memory
// and does not work if Persistence is true

View File

@ -128,8 +128,8 @@ func TestPool_CycleUsed(t *testing.T) {
func TestPool_Skip(t *testing.T) {
ipnet := netip.MustParsePrefix("192.168.0.1/29")
tree := trie.New[bool]()
tree.Insert("example.com", true)
tree := trie.New[struct{}]()
tree.Insert("example.com", struct{}{})
pools, tempfile, err := createPools(Options{
IPNet: &ipnet,
Size: 10,

View File

@ -127,7 +127,7 @@ func ResolveAllIPv6WithResolver(host string, r Resolver) ([]netip.Addr, error) {
}
if node := DefaultHosts.Search(host); node != nil {
if ip := node.Data; ip.Is6() {
if ip := node.Data(); ip.Is6() {
return []netip.Addr{ip}, nil
}
}
@ -154,15 +154,23 @@ func ResolveAllIPv6WithResolver(host string, r Resolver) ([]netip.Addr, error) {
return []netip.Addr{}, ErrIPNotFound
}
return []netip.Addr{netip.AddrFrom16(*(*[16]byte)(ipAddrs[rand.Intn(len(ipAddrs))]))}, nil
addrs := make([]netip.Addr, 0, len(ipAddrs))
for _, ipAddr := range ipAddrs {
addrs = append(addrs, nnip.IpToAddr(ipAddr))
}
rand.Shuffle(len(addrs), func(i, j int) {
addrs[i], addrs[j] = addrs[j], addrs[i]
})
return addrs, nil
}
return []netip.Addr{}, ErrIPNotFound
}
func ResolveAllIPv4WithResolver(host string, r Resolver) ([]netip.Addr, error) {
if node := DefaultHosts.Search(host); node != nil {
if ip := node.Data; ip.Is4() {
return []netip.Addr{node.Data}, nil
if ip := node.Data(); ip.Is4() {
return []netip.Addr{node.Data()}, nil
}
}
@ -188,19 +196,22 @@ func ResolveAllIPv4WithResolver(host string, r Resolver) ([]netip.Addr, error) {
return []netip.Addr{}, ErrIPNotFound
}
ip := ipAddrs[rand.Intn(len(ipAddrs))].To4()
if ip == nil {
return []netip.Addr{}, ErrIPVersion
addrs := make([]netip.Addr, 0, len(ipAddrs))
for _, ipAddr := range ipAddrs {
addrs = append(addrs, nnip.IpToAddr(ipAddr))
}
return []netip.Addr{netip.AddrFrom4(*(*[4]byte)(ip))}, nil
rand.Shuffle(len(addrs), func(i, j int) {
addrs[i], addrs[j] = addrs[j], addrs[i]
})
return addrs, nil
}
return []netip.Addr{}, ErrIPNotFound
}
func ResolveAllIPWithResolver(host string, r Resolver) ([]netip.Addr, error) {
if node := DefaultHosts.Search(host); node != nil {
return []netip.Addr{node.Data}, nil
return []netip.Addr{node.Data()}, nil
}
ip, err := netip.ParseAddr(host)
@ -219,12 +230,21 @@ func ResolveAllIPWithResolver(host string, r Resolver) ([]netip.Addr, error) {
}
if DefaultResolver == nil {
ipAddr, err := net.ResolveIPAddr("ip", host)
ipAddrs, err := net.DefaultResolver.LookupIP(context.Background(), "ip", host)
if err != nil {
return []netip.Addr{}, err
} else if len(ipAddrs) == 0 {
return []netip.Addr{}, ErrIPNotFound
}
addrs := make([]netip.Addr, 0, len(ipAddrs))
for _, ipAddr := range ipAddrs {
addrs = append(addrs, nnip.IpToAddr(ipAddr))
}
return []netip.Addr{nnip.IpToAddr(ipAddr.IP)}, nil
rand.Shuffle(len(addrs), func(i, j int) {
addrs[i], addrs[j] = addrs[j], addrs[i]
})
return addrs, nil
}
return []netip.Addr{}, ErrIPNotFound
}

View File

@ -35,6 +35,10 @@ func (f *Fetcher[V]) Name() string {
return f.name
}
func (f *Fetcher[V]) Vehicle() types.Vehicle {
return f.vehicle
}
func (f *Fetcher[V]) VehicleType() types.VehicleType {
return f.vehicle.Type()
}

View File

@ -2,7 +2,7 @@ package resource
import (
"context"
netHttp "github.com/Dreamacro/clash/component/http"
clashHttp "github.com/Dreamacro/clash/component/http"
types "github.com/Dreamacro/clash/constant/provider"
"io"
"net/http"
@ -35,6 +35,10 @@ type HTTPVehicle struct {
path string
}
func (h *HTTPVehicle) Url() string {
return h.url
}
func (h *HTTPVehicle) Type() types.VehicleType {
return types.HTTP
}
@ -46,7 +50,7 @@ func (h *HTTPVehicle) Path() string {
func (h *HTTPVehicle) Read() ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
resp, err := netHttp.HttpRequest(ctx, h.url, http.MethodGet, nil, nil)
resp, err := clashHttp.HttpRequest(ctx, h.url, http.MethodGet, nil, nil)
if err != nil {
return nil, err
}

View File

@ -31,8 +31,8 @@ type SnifferDispatcher struct {
sniffers []sniffer.Sniffer
forceDomain *trie.DomainTrie[bool]
skipSNI *trie.DomainTrie[bool]
forceDomain *trie.DomainTrie[struct{}]
skipSNI *trie.DomainTrie[struct{}]
portRanges *[]utils.Range[uint16]
skipList *cache.LruCache[string, uint8]
rwMux sync.RWMutex
@ -112,7 +112,6 @@ func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string) {
metadata.Host, host)
}
metadata.AddrType = C.AtypDomainName
metadata.Host = host
metadata.DNSMode = C.DNSNormal
}
@ -183,8 +182,8 @@ func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
return &dispatcher, nil
}
func NewSnifferDispatcher(needSniffer []sniffer.Type, forceDomain *trie.DomainTrie[bool],
skipSNI *trie.DomainTrie[bool], ports *[]utils.Range[uint16],
func NewSnifferDispatcher(needSniffer []sniffer.Type, forceDomain *trie.DomainTrie[struct{}],
skipSNI *trie.DomainTrie[struct{}], ports *[]utils.Range[uint16],
forceDnsMapping bool, parsePureIp bool) (*SnifferDispatcher, error) {
dispatcher := SnifferDispatcher{
enable: true,

View File

@ -17,7 +17,7 @@ var ErrInvalidDomain = errors.New("invalid domain")
// DomainTrie contains the main logic for adding and searching nodes for domain segments.
// support wildcard domain (e.g *.google.com)
type DomainTrie[T comparable] struct {
type DomainTrie[T any] struct {
root *Node[T]
}
@ -74,13 +74,13 @@ func (t *DomainTrie[T]) insert(parts []string, data T) {
for i := len(parts) - 1; i >= 0; i-- {
part := parts[i]
if !node.hasChild(part) {
node.addChild(part, newNode(getZero[T]()))
node.addChild(part, newNode[T]())
}
node = node.getChild(part)
}
node.Data = data
node.setData(data)
}
// Search is the most important part of the Trie.
@ -96,7 +96,7 @@ func (t *DomainTrie[T]) Search(domain string) *Node[T] {
n := t.search(t.root, parts)
if n == nil || n.Data == getZero[T]() {
if n.isEmpty() {
return nil
}
@ -109,13 +109,13 @@ func (t *DomainTrie[T]) search(node *Node[T], parts []string) *Node[T] {
}
if c := node.getChild(parts[len(parts)-1]); c != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != getZero[T]() {
if n := t.search(c, parts[:len(parts)-1]); !n.isEmpty() {
return n
}
}
if c := node.getChild(wildcard); c != nil {
if n := t.search(c, parts[:len(parts)-1]); n != nil && n.Data != getZero[T]() {
if n := t.search(c, parts[:len(parts)-1]); !n.isEmpty() {
return n
}
}
@ -124,6 +124,6 @@ func (t *DomainTrie[T]) search(node *Node[T], parts []string) *Node[T] {
}
// New returns a new, empty Trie.
func New[T comparable]() *DomainTrie[T] {
return &DomainTrie[T]{root: newNode[T](getZero[T]())}
func New[T any]() *DomainTrie[T] {
return &DomainTrie[T]{root: newNode[T]()}
}

View File

@ -23,7 +23,7 @@ func TestTrie_Basic(t *testing.T) {
node := tree.Search("example.com")
assert.NotNil(t, node)
assert.True(t, node.Data == localIP)
assert.True(t, node.Data() == localIP)
assert.NotNil(t, tree.Insert("", localIP))
assert.Nil(t, tree.Search(""))
assert.NotNil(t, tree.Search("localhost"))
@ -75,7 +75,7 @@ func TestTrie_Priority(t *testing.T) {
assertFn := func(domain string, data int) {
node := tree.Search(domain)
assert.NotNil(t, node)
assert.Equal(t, data, node.Data)
assert.Equal(t, data, node.Data())
}
for idx, domain := range domains {

View File

@ -1,9 +1,10 @@
package trie
// Node is the trie's node
type Node[T comparable] struct {
type Node[T any] struct {
children map[string]*Node[T]
Data T
inited bool
data T
}
func (n *Node[T]) getChild(s string) *Node[T] {
@ -18,14 +19,31 @@ func (n *Node[T]) addChild(s string, child *Node[T]) {
n.children[s] = child
}
func newNode[T comparable](data T) *Node[T] {
func (n *Node[T]) isEmpty() bool {
if n == nil || n.inited == false {
return true
}
return false
}
func (n *Node[T]) setData(data T) {
n.data = data
n.inited = true
}
func (n *Node[T]) Data() T {
return n.data
}
func newNode[T any]() *Node[T] {
return &Node[T]{
Data: data,
children: map[string]*Node[T]{},
inited: false,
data: getZero[T](),
}
}
func getZero[T comparable]() T {
func getZero[T any]() T {
var result T
return result
}

View File

@ -60,15 +60,19 @@ type General struct {
// Inbound config
type Inbound 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"`
Authentication []string `json:"authentication"`
AllowLan bool `json:"allow-lan"`
BindAddress string `json:"bind-address"`
InboundTfo bool `json:"inbound-tfo"`
Port int `json:"port"`
SocksPort int `json:"socks-port"`
RedirPort int `json:"redir-port"`
TProxyPort int `json:"tproxy-port"`
MixedPort int `json:"mixed-port"`
ShadowSocksConfig string `json:"ss-config"`
VmessConfig string `json:"vmess-config"`
TcpTunConfig string `json:"tcptun-config"`
UdpTunConfig string `json:"udptun-config"`
Authentication []string `json:"authentication"`
AllowLan bool `json:"allow-lan"`
BindAddress string `json:"bind-address"`
InboundTfo bool `json:"inbound-tfo"`
}
// Controller config
@ -121,20 +125,20 @@ type Tun struct {
RedirectToTun []string `yaml:"-" json:"-"`
MTU uint32 `yaml:"mtu" json:"mtu,omitempty"`
Inet4Address []ListenPrefix `yaml:"inet4-address" json:"inet4_address,omitempty"`
Inet6Address []ListenPrefix `yaml:"inet6-address" json:"inet6_address,omitempty"`
StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"`
Inet4RouteAddress []ListenPrefix `yaml:"inet4_route_address" json:"inet4_route_address,omitempty"`
Inet6RouteAddress []ListenPrefix `yaml:"inet6_route_address" json:"inet6_route_address,omitempty"`
IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"`
IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"`
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"`
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude_uid_range,omitempty"`
IncludeAndroidUser []int `yaml:"include-android-user" json:"include_android_user,omitempty"`
IncludePackage []string `yaml:"include-package" json:"include_package,omitempty"`
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
Inet4Address []ListenPrefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
Inet6Address []ListenPrefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
Inet4RouteAddress []ListenPrefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
Inet6RouteAddress []ListenPrefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
UDPTimeout int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
}
type ListenPrefix netip.Prefix
@ -197,9 +201,9 @@ type IPTables struct {
type Sniffer struct {
Enable bool
Sniffers []sniffer.Type
Reverses *trie.DomainTrie[bool]
ForceDomain *trie.DomainTrie[bool]
SkipDomain *trie.DomainTrie[bool]
Reverses *trie.DomainTrie[struct{}]
ForceDomain *trie.DomainTrie[struct{}]
SkipDomain *trie.DomainTrie[struct{}]
Ports *[]utils.Range[uint16]
ForceDnsMapping bool
ParsePureIp bool
@ -213,7 +217,6 @@ type Experimental struct {
// Config is clash config manager
type Config struct {
General *General
Tun *Tun
IPTables *IPTables
DNS *DNS
Experimental *Experimental
@ -285,6 +288,10 @@ type RawConfig struct {
RedirPort int `yaml:"redir-port"`
TProxyPort int `yaml:"tproxy-port"`
MixedPort int `yaml:"mixed-port"`
ShadowSocksConfig string `yaml:"ss-config"`
VmessConfig string `yaml:"vmess-config"`
TcpTunConfig string `yaml:"tcptun-config"`
UdpTunConfig string `yaml:"udptun-config"`
InboundTfo bool `yaml:"inbound-tfo"`
Authentication []string `yaml:"authentication"`
AllowLan bool `yaml:"allow-lan"`
@ -496,11 +503,10 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
}
config.DNS = dnsCfg
tunCfg, err := parseTun(rawCfg.Tun, config.General, dnsCfg)
err = parseTun(rawCfg.Tun, config.General)
if err != nil {
return nil, err
}
config.Tun = tunCfg
config.Users = parseAuthentication(rawCfg.Authentication)
@ -528,14 +534,18 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun
return &General{
Inbound: Inbound{
Port: cfg.Port,
SocksPort: cfg.SocksPort,
RedirPort: cfg.RedirPort,
TProxyPort: cfg.TProxyPort,
MixedPort: cfg.MixedPort,
AllowLan: cfg.AllowLan,
BindAddress: cfg.BindAddress,
InboundTfo: cfg.InboundTfo,
Port: cfg.Port,
SocksPort: cfg.SocksPort,
RedirPort: cfg.RedirPort,
TProxyPort: cfg.TProxyPort,
MixedPort: cfg.MixedPort,
ShadowSocksConfig: cfg.ShadowSocksConfig,
VmessConfig: cfg.VmessConfig,
TcpTunConfig: cfg.TcpTunConfig,
UdpTunConfig: cfg.UdpTunConfig,
AllowLan: cfg.AllowLan,
BindAddress: cfg.BindAddress,
InboundTfo: cfg.InboundTfo,
},
Controller: Controller{
ExternalController: cfg.ExternalController,
@ -865,7 +875,7 @@ func hostWithDefaultPort(host string, defPort string) (string, error) {
return net.JoinHostPort(hostname, port), nil
}
func parseNameServer(servers []string) ([]dns.NameServer, error) {
func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error) {
var nameservers []dns.NameServer
for idx, server := range servers {
@ -891,7 +901,15 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
addr, err = hostWithDefaultPort(u.Host, "853")
dnsNetType = "tcp-tls" // DNS over TLS
case "https":
clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path}
host := u.Host
if _, _, err := net.SplitHostPort(host); err != nil && strings.Contains(err.Error(), "missing port in address") {
host = net.JoinHostPort(host, "443")
} else {
if err!=nil{
return nil,err
}
}
clearURL := url.URL{Scheme: "https", Host: host, Path: u.Path}
addr = clearURL.String()
dnsNetType = "https" // DNS over HTTPS
if len(u.Fragment) != 0 {
@ -930,17 +948,18 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
ProxyAdapter: proxyAdapter,
Interface: dialer.DefaultInterface,
Params: params,
PreferH3: preferH3,
},
)
}
return nameservers, nil
}
func parseNameServerPolicy(nsPolicy map[string]string) (map[string]dns.NameServer, error) {
func parseNameServerPolicy(nsPolicy map[string]string, preferH3 bool) (map[string]dns.NameServer, error) {
policy := map[string]dns.NameServer{}
for domain, server := range nsPolicy {
nameservers, err := parseNameServer([]string{server})
nameservers, err := parseNameServer([]string{server}, preferH3)
if err != nil {
return nil, err
}
@ -1020,26 +1039,26 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr], rules []C.R
},
}
var err error
if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer); err != nil {
if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer, cfg.PreferH3); err != nil {
return nil, err
}
if dnsCfg.Fallback, err = parseNameServer(cfg.Fallback); err != nil {
if dnsCfg.Fallback, err = parseNameServer(cfg.Fallback, cfg.PreferH3); err != nil {
return nil, err
}
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy); err != nil {
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, cfg.PreferH3); err != nil {
return nil, err
}
if dnsCfg.ProxyServerNameserver, err = parseNameServer(cfg.ProxyServerNameserver); err != nil {
if dnsCfg.ProxyServerNameserver, err = parseNameServer(cfg.ProxyServerNameserver, cfg.PreferH3); err != nil {
return nil, err
}
if len(cfg.DefaultNameserver) == 0 {
return nil, errors.New("default nameserver should have at least one nameserver")
}
if dnsCfg.DefaultNameserver, err = parseNameServer(cfg.DefaultNameserver); err != nil {
if dnsCfg.DefaultNameserver, err = parseNameServer(cfg.DefaultNameserver, cfg.PreferH3); err != nil {
return nil, err
}
// check default nameserver is pure ip addr
@ -1055,35 +1074,36 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr], rules []C.R
}
}
fakeIPRange, err := netip.ParsePrefix(cfg.FakeIPRange)
T.SetFakeIPRange(fakeIPRange)
if cfg.EnhancedMode == C.DNSFakeIP {
ipnet, err := netip.ParsePrefix(cfg.FakeIPRange)
if err != nil {
return nil, err
}
var host *trie.DomainTrie[bool]
var host *trie.DomainTrie[struct{}]
// fake ip skip host filter
if len(cfg.FakeIPFilter) != 0 {
host = trie.New[bool]()
host = trie.New[struct{}]()
for _, domain := range cfg.FakeIPFilter {
_ = host.Insert(domain, true)
_ = host.Insert(domain, struct{}{})
}
}
if len(dnsCfg.Fallback) != 0 {
if host == nil {
host = trie.New[bool]()
host = trie.New[struct{}]()
}
for _, fb := range dnsCfg.Fallback {
if net.ParseIP(fb.Addr) != nil {
continue
}
_ = host.Insert(fb.Addr, true)
_ = host.Insert(fb.Addr, struct{}{})
}
}
pool, err := fakeip.New(fakeip.Options{
IPNet: &ipnet,
IPNet: &fakeIPRange,
Size: 1000,
Host: host,
Persistence: rawCfg.Profile.StoreFakeIP,
@ -1126,7 +1146,7 @@ func parseAuthentication(rawRecords []string) []auth.AuthUser {
return users
}
func parseTun(rawTun RawTun, general *General, dnsCfg *DNS) (*Tun, error) {
func parseTun(rawTun RawTun, general *General) error {
var dnsHijack []netip.AddrPort
for _, d := range rawTun.DNSHijack {
@ -1136,21 +1156,19 @@ func parseTun(rawTun RawTun, general *General, dnsCfg *DNS) (*Tun, error) {
d = strings.Replace(d, "any", "0.0.0.0", 1)
addrPort, err := netip.ParseAddrPort(d)
if err != nil {
return nil, fmt.Errorf("parse dns-hijack url error: %w", err)
return fmt.Errorf("parse dns-hijack url error: %w", err)
}
dnsHijack = append(dnsHijack, addrPort)
}
var tunAddressPrefix netip.Prefix
if dnsCfg.FakeIPRange != nil {
tunAddressPrefix = *dnsCfg.FakeIPRange.IPNet()
} else {
tunAddressPrefix := T.FakeIPRange()
if !tunAddressPrefix.IsValid() {
tunAddressPrefix = netip.MustParsePrefix("198.18.0.1/16")
}
tunAddressPrefix = netip.PrefixFrom(tunAddressPrefix.Addr(), 30)
return &Tun{
general.Tun = Tun{
Enable: rawTun.Enable,
Device: rawTun.Device,
Stack: rawTun.Stack,
@ -1174,7 +1192,9 @@ func parseTun(rawTun RawTun, general *General, dnsCfg *DNS) (*Tun, error) {
ExcludePackage: rawTun.ExcludePackage,
EndpointIndependentNat: rawTun.EndpointIndependentNat,
UDPTimeout: rawTun.UDPTimeout,
}, nil
}
return nil
}
func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
@ -1232,17 +1252,17 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
for st := range loadSniffer {
sniffer.Sniffers = append(sniffer.Sniffers, st)
}
sniffer.ForceDomain = trie.New[bool]()
sniffer.ForceDomain = trie.New[struct{}]()
for _, domain := range snifferRaw.ForceDomain {
err := sniffer.ForceDomain.Insert(domain, true)
err := sniffer.ForceDomain.Insert(domain, struct{}{})
if err != nil {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
}
}
sniffer.SkipDomain = trie.New[bool]()
sniffer.SkipDomain = trie.New[struct{}]()
for _, domain := range snifferRaw.SkipDomain {
err := sniffer.SkipDomain.Insert(domain, true)
err := sniffer.SkipDomain.Insert(domain, struct{}{})
if err != nil {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
}

View File

@ -31,6 +31,7 @@ const (
Vless
Trojan
Hysteria
WireGuard
)
const (
@ -165,6 +166,8 @@ func (at AdapterType) String() string {
return "Trojan"
case Hysteria:
return "Hysteria"
case WireGuard:
return "WireGuard"
case Relay:
return "Relay"
@ -199,3 +202,7 @@ type UDPPacket interface {
// LocalAddr returns the source IP/Port of packet
LocalAddr() net.Addr
}
type UDPPacketInAddr interface {
InAddr() net.Addr
}

View File

@ -114,3 +114,14 @@ func NewDNSPrefer(prefer string) DNSPrefer {
return DualStack
}
}
type HTTPVersion string
const (
// HTTPVersion11 is HTTP/1.1.
HTTPVersion11 HTTPVersion = "http/1.1"
// HTTPVersion2 is HTTP/2.
HTTPVersion2 HTTPVersion = "h2"
// HTTPVersion3 is HTTP/3.
HTTPVersion3 HTTPVersion = "h3"
)

View File

@ -1,7 +1,15 @@
package constant
import "net"
type Listener interface {
RawAddress() string
Address() string
Close() error
}
type AdvanceListener interface {
Close()
Config() string
HandleConn(conn net.Conn, in chan<- ConnContext)
}

View File

@ -6,14 +6,12 @@ import (
"net"
"net/netip"
"strconv"
"github.com/Dreamacro/clash/transport/socks5"
)
// Socks addr type
const (
AtypIPv4 = 1
AtypDomainName = 3
AtypIPv6 = 4
TCP NetWork = iota
UDP
ALLNet
@ -22,8 +20,12 @@ const (
HTTPS
SOCKS4
SOCKS5
SHADOWSOCKS
VMESS
REDIR
TPROXY
TCPTUN
UDPTUN
TUN
INNER
)
@ -55,10 +57,18 @@ func (t Type) String() string {
return "Socks4"
case SOCKS5:
return "Socks5"
case SHADOWSOCKS:
return "ShadowSocks"
case VMESS:
return "Vmess"
case REDIR:
return "Redir"
case TPROXY:
return "TProxy"
case TCPTUN:
return "TcpTun"
case UDPTUN:
return "UdpTun"
case TUN:
return "Tun"
case INNER:
@ -105,7 +115,8 @@ type Metadata struct {
DstIP netip.Addr `json:"destinationIP"`
SrcPort string `json:"sourcePort"`
DstPort string `json:"destinationPort"`
AddrType int `json:"-"`
InIP netip.Addr `json:"inboundIP"`
InPort string `json:"inboundPort"`
Host string `json:"host"`
DNSMode DNSMode `json:"dnsMode"`
Uid *int32 `json:"uid"`
@ -138,6 +149,17 @@ func (m *Metadata) SourceDetail() string {
}
}
func (m *Metadata) AddrType() int {
switch true {
case m.Host != "" || !m.DstIP.IsValid():
return socks5.AtypDomainName
case m.DstIP.Is4():
return socks5.AtypIPv4
default:
return socks5.AtypIPv6
}
}
func (m *Metadata) Resolved() bool {
return m.DstIP.IsValid()
}
@ -148,11 +170,6 @@ func (m *Metadata) Pure() *Metadata {
if (m.DNSMode == DNSMapping || m.DNSMode == DNSHosts) && m.DstIP.IsValid() {
copyM := *m
copyM.Host = ""
if copyM.DstIP.Is4() {
copyM.AddrType = AtypIPv4
} else {
copyM.AddrType = AtypIPv6
}
return &copyM
}

View File

@ -13,6 +13,7 @@ const (
SrcIPSuffix
SrcPort
DstPort
InPort
Process
ProcessPath
RuleSet
@ -52,6 +53,8 @@ func (rt RuleType) String() string {
return "SrcPort"
case DstPort:
return "DstPort"
case InPort:
return "InPort"
case Process:
return "Process"
case ProcessPath:

View File

@ -1,164 +1,730 @@
package dns
import (
"bytes"
"context"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
"github.com/lucas-clemente/quic-go"
"github.com/lucas-clemente/quic-go/http3"
D "github.com/miekg/dns"
"io"
"net"
"net/http"
"net/url"
"runtime"
"strconv"
"sync"
"time"
"github.com/Dreamacro/clash/component/dialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/lucas-clemente/quic-go"
"github.com/lucas-clemente/quic-go/http3"
"github.com/miekg/dns"
D "github.com/miekg/dns"
"golang.org/x/net/http2"
)
// Values to configure HTTP and HTTP/2 transport.
const (
// dotMimeType is the DoH mimetype that should be used.
dotMimeType = "application/dns-message"
// transportDefaultReadIdleTimeout is the default timeout for pinging
// idle connections in HTTP/2 transport.
transportDefaultReadIdleTimeout = 30 * time.Second
// transportDefaultIdleConnTimeout is the default timeout for idle
// connections in HTTP transport.
transportDefaultIdleConnTimeout = 5 * time.Minute
// dohMaxConnsPerHost controls the maximum number of connections for
// each host.
dohMaxConnsPerHost = 1
dialTimeout = 10 * time.Second
// dohMaxIdleConns controls the maximum number of connections being idle
// at the same time.
dohMaxIdleConns = 1
maxElapsedTime = time.Second * 30
)
type dohClient struct {
url string
transport http.RoundTripper
var DefaultHTTPVersions = []C.HTTPVersion{C.HTTPVersion11, C.HTTPVersion2}
// dnsOverHTTPS is a struct that implements the Upstream interface for the
// DNS-over-HTTPS protocol.
type dnsOverHTTPS struct {
// The Client's Transport typically has internal state (cached TCP
// connections), so Clients should be reused instead of created as
// needed. Clients are safe for concurrent use by multiple goroutines.
client *http.Client
clientMu sync.Mutex
// quicConfig is the QUIC configuration that is used if HTTP/3 is enabled
// for this upstream.
quicConfig *quic.Config
quicConfigGuard sync.Mutex
url *url.URL
r *Resolver
httpVersions []C.HTTPVersion
proxyAdapter string
}
func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return dc.ExchangeContext(context.Background(), m)
// type check
var _ dnsClient = (*dnsOverHTTPS)(nil)
// newDoH returns the DNS-over-HTTPS Upstream.
func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[string]string, proxyAdapter string) dnsClient {
u, _ := url.Parse(urlString)
httpVersions := DefaultHTTPVersions
if preferH3 {
httpVersions = append(httpVersions, C.HTTPVersion3)
}
if params["h3"] == "true" {
httpVersions = []C.HTTPVersion{C.HTTPVersion3}
}
doh := &dnsOverHTTPS{
url: u,
r: r,
quicConfig: &quic.Config{
KeepAlivePeriod: QUICKeepAlivePeriod,
TokenStore: newQUICTokenStore(),
},
httpVersions: httpVersions,
}
runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close)
return doh
}
func (dc *dohClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
// https://datatracker.ietf.org/doc/html/rfc8484#section-4.1
// In order to maximize cache friendliness, SHOULD use a DNS ID of 0 in every DNS request.
newM := *m
newM.Id = 0
req, err := dc.newRequest(&newM)
// Address implements the Upstream interface for *dnsOverHTTPS.
func (p *dnsOverHTTPS) Address() string { return p.url.String() }
func (p *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
// Quote from https://www.rfc-editor.org/rfc/rfc8484.html:
// In order to maximize HTTP cache friendliness, DoH clients using media
// formats that include the ID field from the DNS message header, such
// as "application/dns-message", SHOULD use a DNS ID of 0 in every DNS
// request.
id := m.Id
m.Id = 0
defer func() {
// Restore the original ID to not break compatibility with proxies.
m.Id = id
if msg != nil {
msg.Id = id
}
}()
// Check if there was already an active client before sending the request.
// We'll only attempt to re-connect if there was one.
client, isCached, err := p.getClient()
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to init http client: %w", err)
}
req = req.WithContext(ctx)
msg, err = dc.doRequest(req)
if err == nil {
msg.Id = m.Id
}
return
}
// Make the first attempt to send the DNS query.
msg, err = p.exchangeHTTPS(ctx, client, m)
// Make up to 2 attempts to re-create the HTTP client and send the request
// again. There are several cases (mostly, with QUIC) where this workaround
// is necessary to make HTTP client usable. We need to make 2 attempts in
// the case when the connection was closed (due to inactivity for example)
// AND the server refuses to open a 0-RTT connection.
for i := 0; isCached && p.shouldRetry(err) && i < 2; i++ {
client, err = p.resetClient(err)
if err != nil {
return nil, fmt.Errorf("failed to reset http client: %w", err)
}
msg, err = p.exchangeHTTPS(ctx, client, m)
}
// newRequest returns a new DoH request given a dns.Msg.
func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) {
buf, err := m.Pack()
if err != nil {
return nil, err
// If the request failed anyway, make sure we don't use this client.
_, resErr := p.resetClient(err)
return nil, fmt.Errorf("err:%v,resErr:%v", err, resErr)
}
req, err := http.NewRequest(http.MethodPost, dc.url, bytes.NewReader(buf))
if err != nil {
return req, err
}
req.Header.Set("content-type", dotMimeType)
req.Header.Set("accept", dotMimeType)
return req, nil
}
func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
client := &http.Client{Transport: dc.transport}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
buf, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
msg = &D.Msg{}
err = msg.Unpack(buf)
return msg, err
}
func newDoHClient(url string, r *Resolver, params map[string]string, proxyAdapter string) *dohClient {
useH3 := params["h3"] == "true"
TLCConfig := tlsC.GetDefaultTLSConfig()
var transport http.RoundTripper
if useH3 {
transport = &http3.RoundTripper{
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
// Exchange implements the Upstream interface for *dnsOverHTTPS.
func (p *dnsOverHTTPS) Exchange(m *dns.Msg) (*dns.Msg, error) {
return p.ExchangeContext(context.Background(), m)
}
ip, err := resolver.ResolveIPWithResolver(host, r)
if err != nil {
return nil, err
}
// Close implements the Upstream interface for *dnsOverHTTPS.
func (p *dnsOverHTTPS) Close() (err error) {
p.clientMu.Lock()
defer p.clientMu.Unlock()
portInt, err := strconv.Atoi(port)
if err != nil {
return nil, err
}
runtime.SetFinalizer(p, nil)
udpAddr := net.UDPAddr{
IP: net.ParseIP(ip.String()),
Port: portInt,
}
if p.client == nil {
return nil
}
var conn net.PacketConn
if proxyAdapter == "" {
conn, err = dialer.ListenPacket(ctx, "udp", "")
if err != nil {
return nil, err
}
} else {
if wrapConn, err := dialContextExtra(ctx, proxyAdapter, "udp", ip, port); err == nil {
if pc, ok := wrapConn.(*wrapPacketConn); ok {
conn = pc
} else {
return nil, fmt.Errorf("conn isn't wrapPacketConn")
}
} else {
return nil, err
}
}
return p.closeClient(p.client)
}
return quic.DialEarlyContext(ctx, conn, &udpAddr, host, tlsCfg, cfg)
},
TLSClientConfig: TLCConfig,
// closeClient cleans up resources used by client if necessary. Note, that at
// this point it should only be done for HTTP/3 as it may leak due to keep-alive
// connections.
func (p *dnsOverHTTPS) closeClient(client *http.Client) (err error) {
if isHTTP3(client) {
return client.Transport.(io.Closer).Close()
}
return nil
}
// exchangeHTTPS logs the request and its result and calls exchangeHTTPSClient.
func (p *dnsOverHTTPS) exchangeHTTPS(ctx context.Context, client *http.Client, req *dns.Msg) (resp *dns.Msg, err error) {
resp, err = p.exchangeHTTPSClient(ctx, client, req)
return resp, err
}
// exchangeHTTPSClient sends the DNS query to a DoH resolver using the specified
// http.Client instance.
func (p *dnsOverHTTPS) exchangeHTTPSClient(
ctx context.Context,
client *http.Client,
req *dns.Msg,
) (resp *dns.Msg, err error) {
buf, err := req.Pack()
if err != nil {
return nil, fmt.Errorf("packing message: %w", err)
}
// It appears, that GET requests are more memory-efficient with Golang
// implementation of HTTP/2.
method := http.MethodGet
if isHTTP3(client) {
// If we're using HTTP/3, use http3.MethodGet0RTT to force using 0-RTT.
method = http3.MethodGet0RTT
}
p.url.RawQuery = fmt.Sprintf("dns=%s", base64.RawURLEncoding.EncodeToString(buf))
httpReq, err := http.NewRequest(method, p.url.String(), nil)
if err != nil {
return nil, fmt.Errorf("creating http request to %s: %w", p.url, err)
}
httpReq.Header.Set("Accept", "application/dns-message")
httpReq.Header.Set("User-Agent", "")
_ = httpReq.WithContext(ctx)
httpResp, err := client.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("requesting %s: %w", p.url, err)
}
defer httpResp.Body.Close()
body, err := io.ReadAll(httpResp.Body)
if err != nil {
return nil, fmt.Errorf("reading %s: %w", p.url, err)
}
if httpResp.StatusCode != http.StatusOK {
return nil,
fmt.Errorf(
"expected status %d, got %d from %s",
http.StatusOK,
httpResp.StatusCode,
p.url,
)
}
resp = &dns.Msg{}
err = resp.Unpack(body)
if err != nil {
return nil, fmt.Errorf(
"unpacking response from %s: body is %s: %w",
p.url,
body,
err,
)
}
if resp.Id != req.Id {
err = dns.ErrId
}
return resp, err
}
// shouldRetry checks what error we have received and returns true if we should
// re-create the HTTP client and retry the request.
func (p *dnsOverHTTPS) shouldRetry(err error) (ok bool) {
if err == nil {
return false
}
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
// If this is a timeout error, trying to forcibly re-create the HTTP
// client instance. This is an attempt to fix an issue with DoH client
// stalling after a network change.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/3217.
return true
}
if isQUICRetryError(err) {
return true
}
return false
}
// resetClient triggers re-creation of the *http.Client that is used by this
// upstream. This method accepts the error that caused resetting client as
// depending on the error we may also reset the QUIC config.
func (p *dnsOverHTTPS) resetClient(resetErr error) (client *http.Client, err error) {
p.clientMu.Lock()
defer p.clientMu.Unlock()
if errors.Is(resetErr, quic.Err0RTTRejected) {
// Reset the TokenStore only if 0-RTT was rejected.
p.resetQUICConfig()
}
oldClient := p.client
if oldClient != nil {
closeErr := p.closeClient(oldClient)
if closeErr != nil {
log.Warnln("warning: failed to close the old http client: %v", closeErr)
}
}
log.Debugln("re-creating the http client due to %v", resetErr)
p.client, err = p.createClient()
return p.client, err
}
// getQUICConfig returns the QUIC config in a thread-safe manner. Note, that
// this method returns a pointer, it is forbidden to change its properties.
func (p *dnsOverHTTPS) getQUICConfig() (c *quic.Config) {
p.quicConfigGuard.Lock()
defer p.quicConfigGuard.Unlock()
return p.quicConfig
}
// resetQUICConfig Re-create the token store to make sure we're not trying to
// use invalid for 0-RTT.
func (p *dnsOverHTTPS) resetQUICConfig() {
p.quicConfigGuard.Lock()
defer p.quicConfigGuard.Unlock()
p.quicConfig = p.quicConfig.Clone()
p.quicConfig.TokenStore = newQUICTokenStore()
}
// getClient gets or lazily initializes an HTTP client (and transport) that will
// be used for this DoH resolver.
func (p *dnsOverHTTPS) getClient() (c *http.Client, isCached bool, err error) {
startTime := time.Now()
p.clientMu.Lock()
defer p.clientMu.Unlock()
if p.client != nil {
return p.client, true, nil
}
// Timeout can be exceeded while waiting for the lock. This happens quite
// often on mobile devices.
elapsed := time.Since(startTime)
if elapsed > maxElapsedTime {
return nil, false, fmt.Errorf("timeout exceeded: %s", elapsed)
}
log.Debugln("creating a new http client")
p.client, err = p.createClient()
return p.client, false, err
}
// createClient creates a new *http.Client instance. The HTTP protocol version
// will depend on whether HTTP3 is allowed and provided by this upstream. Note,
// that we'll attempt to establish a QUIC connection when creating the client in
// order to check whether HTTP3 is supported.
func (p *dnsOverHTTPS) createClient() (*http.Client, error) {
transport, err := p.createTransport()
if err != nil {
return nil, fmt.Errorf("initializing http transport: %w", err)
}
client := &http.Client{
Transport: transport,
Timeout: DefaultTimeout,
Jar: nil,
}
p.client = client
return p.client, nil
}
// createTransport initializes an HTTP transport that will be used specifically
// for this DoH resolver. This HTTP transport ensures that the HTTP requests
// will be sent exactly to the IP address got from the bootstrap resolver. Note,
// that this function will first attempt to establish a QUIC connection (if
// HTTP3 is enabled in the upstream options). If this attempt is successful,
// it returns an HTTP3 transport, otherwise it returns the H1/H2 transport.
func (p *dnsOverHTTPS) createTransport() (t http.RoundTripper, err error) {
tlsConfig := tlsC.GetGlobalFingerprintTLCConfig(
&tls.Config{
InsecureSkipVerify: false,
MinVersion: tls.VersionTLS12,
SessionTicketsDisabled: false,
})
var nextProtos []string
for _, v := range p.httpVersions {
nextProtos = append(nextProtos, string(v))
}
tlsConfig.NextProtos = nextProtos
dialContext := getDialHandler(p.r, p.proxyAdapter)
// First, we attempt to create an HTTP3 transport. If the probe QUIC
// connection is established successfully, we'll be using HTTP3 for this
// upstream.
transportH3, err := p.createTransportH3(tlsConfig, dialContext)
if err == nil {
log.Debugln("using HTTP/3 for this upstream: QUIC was faster")
return transportH3, nil
}
log.Debugln("using HTTP/2 for this upstream: %v", err)
if !p.supportsHTTP() {
return nil, errors.New("HTTP1/1 and HTTP2 are not supported by this upstream")
}
transport := &http.Transport{
TLSClientConfig: tlsConfig,
DisableCompression: true,
DialContext: dialContext,
IdleConnTimeout: transportDefaultIdleConnTimeout,
MaxConnsPerHost: dohMaxConnsPerHost,
MaxIdleConns: dohMaxIdleConns,
// Since we have a custom DialContext, we need to use this field to
// make golang http.Client attempt to use HTTP/2. Otherwise, it would
// only be used when negotiated on the TLS level.
ForceAttemptHTTP2: true,
}
// Explicitly configure transport to use HTTP/2.
//
// See https://github.com/AdguardTeam/dnsproxy/issues/11.
var transportH2 *http2.Transport
transportH2, err = http2.ConfigureTransports(transport)
if err != nil {
return nil, err
}
// Enable HTTP/2 pings on idle connections.
transportH2.ReadIdleTimeout = transportDefaultReadIdleTimeout
return transport, nil
}
// http3Transport is a wrapper over *http3.RoundTripper that tries to optimize
// its behavior. The main thing that it does is trying to force use a single
// connection to a host instead of creating a new one all the time. It also
// helps mitigate race issues with quic-go.
type http3Transport struct {
baseTransport *http3.RoundTripper
closed bool
mu sync.RWMutex
}
// type check
var _ http.RoundTripper = (*http3Transport)(nil)
// RoundTrip implements the http.RoundTripper interface for *http3Transport.
func (h *http3Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
h.mu.RLock()
defer h.mu.RUnlock()
if h.closed {
return nil, net.ErrClosed
}
// Try to use cached connection to the target host if it's available.
resp, err = h.baseTransport.RoundTripOpt(req, http3.RoundTripOpt{OnlyCachedConn: true})
if errors.Is(err, http3.ErrNoCachedConn) {
// If there are no cached connection, trigger creating a new one.
resp, err = h.baseTransport.RoundTrip(req)
}
return resp, err
}
// type check
var _ io.Closer = (*http3Transport)(nil)
// Close implements the io.Closer interface for *http3Transport.
func (h *http3Transport) Close() (err error) {
h.mu.Lock()
defer h.mu.Unlock()
h.closed = true
return h.baseTransport.Close()
}
// createTransportH3 tries to create an HTTP/3 transport for this upstream.
// We should be able to fall back to H1/H2 in case if HTTP/3 is unavailable or
// if it is too slow. In order to do that, this method will run two probes
// in parallel (one for TLS, the other one for QUIC) and if QUIC is faster it
// will create the *http3.RoundTripper instance.
func (doh *dnsOverHTTPS) createTransportH3(
tlsConfig *tls.Config,
dialContext dialHandler,
) (roundTripper http.RoundTripper, err error) {
if !doh.supportsH3() {
return nil, errors.New("HTTP3 support is not enabled")
}
addr, err := doh.probeH3(tlsConfig, dialContext)
if err != nil {
return nil, err
}
rt := &http3.RoundTripper{
Dial: func(
ctx context.Context,
// Ignore the address and always connect to the one that we got
// from the bootstrapper.
_ string,
tlsCfg *tls.Config,
cfg *quic.Config,
) (c quic.EarlyConnection, err error) {
return doh.dialQuic(ctx, addr, tlsCfg, cfg)
},
DisableCompression: true,
TLSClientConfig: tlsConfig,
QuicConfig: doh.getQUICConfig(),
}
return &http3Transport{baseTransport: rt}, nil
}
func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
ip, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
portInt, err := strconv.Atoi(port)
if err != nil {
return nil, err
}
udpAddr := net.UDPAddr{
IP: net.ParseIP(ip),
Port: portInt,
}
var conn net.PacketConn
if doh.proxyAdapter == "" {
conn, err = dialer.ListenPacket(ctx, "udp", "")
if err != nil {
return nil, err
}
} else {
transport = &http.Transport{
ForceAttemptHTTP2: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
if wrapConn, err := dialContextExtra(ctx, doh.proxyAdapter, "udp", udpAddr.AddrPort().Addr(), port); err == nil {
if pc, ok := wrapConn.(*wrapPacketConn); ok {
conn = pc
} else {
return nil, fmt.Errorf("conn isn't wrapPacketConn")
}
} else {
return nil, err
}
}
return quic.DialEarlyContext(ctx, conn, &udpAddr, doh.url.Host, tlsCfg, cfg)
}
ip, err := resolver.ResolveIPWithResolver(host, r)
if err != nil {
return nil, err
}
// probeH3 runs a test to check whether QUIC is faster than TLS for this
// upstream. If the test is successful it will return the address that we
// should use to establish the QUIC connections.
func (p *dnsOverHTTPS) probeH3(
tlsConfig *tls.Config,
dialContext dialHandler,
) (addr string, err error) {
// We're using bootstrapped address instead of what's passed to the function
// it does not create an actual connection, but it helps us determine
// what IP is actually reachable (when there are v4/v6 addresses).
rawConn, err := dialContext(context.Background(), "udp", p.url.Host)
if err != nil {
return "", fmt.Errorf("failed to dial: %w", err)
}
// It's never actually used.
_ = rawConn.Close()
if proxyAdapter == "" {
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port))
} else {
return dialContextExtra(ctx, proxyAdapter, "tcp", ip, port)
}
},
TLSClientConfig: TLCConfig,
udpConn, ok := rawConn.(*net.UDPConn)
if !ok {
return "", fmt.Errorf("not a UDP connection to %s", p.Address())
}
addr = udpConn.RemoteAddr().String()
// Avoid spending time on probing if this upstream only supports HTTP/3.
if p.supportsH3() && !p.supportsHTTP() {
return addr, nil
}
// Use a new *tls.Config with empty session cache for probe connections.
// Surprisingly, this is really important since otherwise it invalidates
// the existing cache.
// TODO(ameshkov): figure out why the sessions cache invalidates here.
probeTLSCfg := tlsConfig.Clone()
probeTLSCfg.ClientSessionCache = nil
// Do not expose probe connections to the callbacks that are passed to
// the bootstrap options to avoid side-effects.
// TODO(ameshkov): consider exposing, somehow mark that this is a probe.
probeTLSCfg.VerifyPeerCertificate = nil
probeTLSCfg.VerifyConnection = nil
// Run probeQUIC and probeTLS in parallel and see which one is faster.
chQuic := make(chan error, 1)
chTLS := make(chan error, 1)
go p.probeQUIC(addr, probeTLSCfg, chQuic)
go p.probeTLS(dialContext, probeTLSCfg, chTLS)
select {
case quicErr := <-chQuic:
if quicErr != nil {
// QUIC failed, return error since HTTP3 was not preferred.
return "", quicErr
}
// Return immediately, QUIC was faster.
return addr, quicErr
case tlsErr := <-chTLS:
if tlsErr != nil {
// Return immediately, TLS failed.
log.Debugln("probing TLS: %v", tlsErr)
return addr, nil
}
return "", errors.New("TLS was faster than QUIC, prefer it")
}
}
// probeQUIC attempts to establish a QUIC connection to the specified address.
// We run probeQUIC and probeTLS in parallel and see which one is faster.
func (p *dnsOverHTTPS) probeQUIC(addr string, tlsConfig *tls.Config, ch chan error) {
startTime := time.Now()
timeout := DefaultTimeout
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(timeout))
defer cancel()
conn, err := p.dialQuic(ctx, addr, tlsConfig, p.getQUICConfig())
if err != nil {
ch <- fmt.Errorf("opening QUIC connection to %s: %w", p.Address(), err)
return
}
// Ignore the error since there's no way we can use it for anything useful.
_ = conn.CloseWithError(QUICCodeNoError, "")
ch <- nil
elapsed := time.Now().Sub(startTime)
log.Debugln("elapsed on establishing a QUIC connection: %s", elapsed)
}
// probeTLS attempts to establish a TLS connection to the specified address. We
// run probeQUIC and probeTLS in parallel and see which one is faster.
func (p *dnsOverHTTPS) probeTLS(dialContext dialHandler, tlsConfig *tls.Config, ch chan error) {
startTime := time.Now()
conn, err := p.tlsDial(dialContext, "tcp", tlsConfig)
if err != nil {
ch <- fmt.Errorf("opening TLS connection: %w", err)
return
}
// Ignore the error since there's no way we can use it for anything useful.
_ = conn.Close()
ch <- nil
elapsed := time.Now().Sub(startTime)
log.Debugln("elapsed on establishing a TLS connection: %s", elapsed)
}
// supportsH3 returns true if HTTP/3 is supported by this upstream.
func (p *dnsOverHTTPS) supportsH3() (ok bool) {
for _, v := range p.supportedHTTPVersions() {
if v == C.HTTPVersion3 {
return true
}
}
return &dohClient{
url: url,
transport: transport,
}
return false
}
// supportsHTTP returns true if HTTP/1.1 or HTTP2 is supported by this upstream.
func (p *dnsOverHTTPS) supportsHTTP() (ok bool) {
for _, v := range p.supportedHTTPVersions() {
if v == C.HTTPVersion11 || v == C.HTTPVersion2 {
return true
}
}
return false
}
// supportedHTTPVersions returns the list of supported HTTP versions.
func (p *dnsOverHTTPS) supportedHTTPVersions() (v []C.HTTPVersion) {
v = p.httpVersions
if v == nil {
v = DefaultHTTPVersions
}
return v
}
// isHTTP3 checks if the *http.Client is an HTTP/3 client.
func isHTTP3(client *http.Client) (ok bool) {
_, ok = client.Transport.(*http3Transport)
return ok
}
// tlsDial is basically the same as tls.DialWithDialer, but we will call our own
// dialContext function to get connection.
func (doh *dnsOverHTTPS) tlsDial(dialContext dialHandler, network string, config *tls.Config) (*tls.Conn, error) {
// We're using bootstrapped address instead of what's passed
// to the function.
rawConn, err := dialContext(context.Background(), network, doh.url.Host)
if err != nil {
return nil, err
}
// We want the timeout to cover the whole process: TCP connection and
// TLS handshake dialTimeout will be used as connection deadLine.
conn := tls.Client(rawConn, config)
err = conn.SetDeadline(time.Now().Add(dialTimeout))
if err != nil {
// Must not happen in normal circumstances.
panic(fmt.Errorf("cannot set deadline: %w", err))
}
err = conn.Handshake()
if err != nil {
defer conn.Close()
return nil, err
}
return conn, nil
}

View File

@ -1,134 +1,307 @@
package dns
import (
"bytes"
"context"
"crypto/tls"
"encoding/binary"
"errors"
"fmt"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
"github.com/lucas-clemente/quic-go"
"net"
"net/netip"
"runtime"
"strconv"
"sync"
"time"
"github.com/Dreamacro/clash/component/dialer"
tlsC "github.com/Dreamacro/clash/component/tls"
"github.com/lucas-clemente/quic-go"
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns"
)
const NextProtoDQ = "doq"
const (
// QUICCodeNoError is used when the connection or stream needs to be closed,
// but there is no error to signal.
QUICCodeNoError = quic.ApplicationErrorCode(0)
// QUICCodeInternalError signals that the DoQ implementation encountered
// an internal error and is incapable of pursuing the transaction or the
// connection.
QUICCodeInternalError = quic.ApplicationErrorCode(1)
// QUICKeepAlivePeriod is the value that we pass to *quic.Config and that
// controls the period with with keep-alive frames are being sent to the
// connection. We set it to 20s as it would be in the quic-go@v0.27.1 with
// KeepAlive field set to true This value is specified in
// https://pkg.go.dev/github.com/lucas-clemente/quic-go/internal/protocol#MaxKeepAliveInterval.
//
// TODO(ameshkov): Consider making it configurable.
QUICKeepAlivePeriod = time.Second * 20
DefaultTimeout = time.Second * 5
)
var bytesPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
type dialHandler func(ctx context.Context, network, addr string) (net.Conn, error)
// dnsOverQUIC is a struct that implements the Upstream interface for the
// DNS-over-QUIC protocol (spec: https://www.rfc-editor.org/rfc/rfc9250.html).
type dnsOverQUIC struct {
// quicConfig is the QUIC configuration that is used for establishing
// connections to the upstream. This configuration includes the TokenStore
// that needs to be stored for the lifetime of dnsOverQUIC since we can
// re-create the connection.
quicConfig *quic.Config
quicConfigGuard sync.Mutex
// conn is the current active QUIC connection. It can be closed and
// re-opened when needed.
conn quic.Connection
connMu sync.RWMutex
// bytesPool is a *sync.Pool we use to store byte buffers in. These byte
// buffers are used to read responses from the upstream.
bytesPool *sync.Pool
bytesPoolGuard sync.Mutex
type quicClient struct {
addr string
r *Resolver
connection quic.Connection
proxyAdapter string
udp net.PacketConn
sync.RWMutex // protects connection and bytesPool
r *Resolver
}
func newDOQ(r *Resolver, addr, proxyAdapter string) *quicClient {
return &quicClient{
// type check
var _ dnsClient = (*dnsOverQUIC)(nil)
// newDoQ returns the DNS-over-QUIC Upstream.
func newDoQ(resolver *Resolver, addr string, adapter string) (dnsClient, error) {
doq := &dnsOverQUIC{
addr: addr,
r: r,
proxyAdapter: proxyAdapter,
proxyAdapter: adapter,
r: resolver,
quicConfig: &quic.Config{
KeepAlivePeriod: QUICKeepAlivePeriod,
TokenStore: newQUICTokenStore(),
},
}
runtime.SetFinalizer(doq, (*dnsOverQUIC).Close)
return doq, nil
}
func (dc *quicClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return dc.ExchangeContext(context.Background(), m)
}
// Address implements the Upstream interface for *dnsOverQUIC.
func (p *dnsOverQUIC) Address() string { return p.addr }
func (p *dnsOverQUIC) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
// When sending queries over a QUIC connection, the DNS Message ID MUST be
// set to zero.
id := m.Id
m.Id = 0
defer func() {
// Restore the original ID to not break compatibility with proxies.
m.Id = id
if msg != nil {
msg.Id = id
}
}()
// Check if there was already an active conn before sending the request.
// We'll only attempt to re-connect if there was one.
hasConnection := p.hasConnection()
// Make the first attempt to send the DNS query.
msg, err = p.exchangeQUIC(ctx, m)
// Make up to 2 attempts to re-open the QUIC connection and send the request
// again. There are several cases where this workaround is necessary to
// make DoQ usable. We need to make 2 attempts in the case when the
// connection was closed (due to inactivity for example) AND the server
// refuses to open a 0-RTT connection.
for i := 0; hasConnection && p.shouldRetry(err) && i < 2; i++ {
log.Debugln("re-creating the QUIC connection and retrying due to %v", err)
// Close the active connection to make sure we'll try to re-connect.
p.closeConnWithError(err)
// Retry sending the request.
msg, err = p.exchangeQUIC(ctx, m)
}
func (dc *quicClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
stream, err := dc.openStream(ctx)
if err != nil {
return nil, fmt.Errorf("failed to open new stream to %s", dc.addr)
// If we're unable to exchange messages, make sure the connection is
// closed and signal about an internal error.
p.closeConnWithError(err)
}
buf, err := m.Pack()
return msg, err
}
// Exchange implements the Upstream interface for *dnsOverQUIC.
func (p *dnsOverQUIC) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return p.ExchangeContext(context.Background(), m)
}
// Close implements the Upstream interface for *dnsOverQUIC.
func (p *dnsOverQUIC) Close() (err error) {
p.connMu.Lock()
defer p.connMu.Unlock()
runtime.SetFinalizer(p, nil)
if p.conn != nil {
err = p.conn.CloseWithError(QUICCodeNoError, "")
}
return err
}
// exchangeQUIC attempts to open a QUIC connection, send the DNS message
// through it and return the response it got from the server.
func (p *dnsOverQUIC) exchangeQUIC(ctx context.Context, msg *D.Msg) (resp *D.Msg, err error) {
var conn quic.Connection
conn, err = p.getConnection(true)
if err != nil {
return nil, err
}
_, err = stream.Write(buf)
var buf []byte
buf, err = msg.Pack()
if err != nil {
return nil, fmt.Errorf("failed to pack DNS message for DoQ: %w", err)
}
var stream quic.Stream
stream, err = p.openStream(ctx, conn)
if err != nil {
return nil, err
}
_, err = stream.Write(AddPrefix(buf))
if err != nil {
return nil, fmt.Errorf("failed to write to a QUIC stream: %w", err)
}
// The client MUST send the DNS query over the selected stream, and MUST
// indicate through the STREAM FIN mechanism that no further data will
// be sent on that stream.
// stream.Close() -- closes the write-direction of the stream.
// be sent on that stream. Note, that stream.Close() closes the
// write-direction of the stream, but does not prevent reading from it.
_ = stream.Close()
respBuf := bytesPool.Get().(*bytes.Buffer)
defer bytesPool.Put(respBuf)
defer respBuf.Reset()
n, err := respBuf.ReadFrom(stream)
if err != nil && n == 0 {
return nil, err
}
reply := new(D.Msg)
err = reply.Unpack(respBuf.Bytes())
if err != nil {
return nil, err
}
return reply, nil
return p.readMsg(stream)
}
func isActive(s quic.Connection) bool {
select {
case <-s.Context().Done():
return false
default:
return true
}
// AddPrefix adds a 2-byte prefix with the DNS message length.
func AddPrefix(b []byte) (m []byte) {
m = make([]byte, 2+len(b))
binary.BigEndian.PutUint16(m, uint16(len(b)))
copy(m[2:], b)
return m
}
// getConnection - opens or returns an existing quic.Connection
// useCached - if true and cached connection exists, return it right away
// otherwise - forcibly creates a new connection
func (dc *quicClient) getConnection(ctx context.Context) (quic.Connection, error) {
var connection quic.Connection
dc.RLock()
connection = dc.connection
// shouldRetry checks what error we received and decides whether it is required
// to re-open the connection and retry sending the request.
func (p *dnsOverQUIC) shouldRetry(err error) (ok bool) {
return isQUICRetryError(err)
}
if connection != nil && isActive(connection) {
dc.RUnlock()
return connection, nil
}
// getBytesPool returns (creates if needed) a pool we store byte buffers in.
func (p *dnsOverQUIC) getBytesPool() (pool *sync.Pool) {
p.bytesPoolGuard.Lock()
defer p.bytesPoolGuard.Unlock()
dc.RUnlock()
if p.bytesPool == nil {
p.bytesPool = &sync.Pool{
New: func() interface{} {
b := make([]byte, MaxMsgSize)
dc.Lock()
defer dc.Unlock()
connection = dc.connection
if connection != nil {
if isActive(connection) {
return connection, nil
} else {
_ = connection.CloseWithError(quic.ApplicationErrorCode(0), "")
return &b
},
}
}
var err error
connection, err = dc.openConnection(ctx)
dc.connection = connection
return connection, err
return p.bytesPool
}
func (dc *quicClient) openConnection(ctx context.Context) (quic.Connection, error) {
if dc.udp != nil {
_ = dc.udp.Close()
// getConnection opens or returns an existing quic.Connection. useCached
// argument controls whether we should try to use the existing cached
// connection. If it is false, we will forcibly create a new connection and
// close the existing one if needed.
func (p *dnsOverQUIC) getConnection(useCached bool) (quic.Connection, error) {
var conn quic.Connection
p.connMu.RLock()
conn = p.conn
if conn != nil && useCached {
p.connMu.RUnlock()
return conn, nil
}
if conn != nil {
// we're recreating the connection, let's create a new one.
_ = conn.CloseWithError(QUICCodeNoError, "")
}
p.connMu.RUnlock()
p.connMu.Lock()
defer p.connMu.Unlock()
var err error
conn, err = p.openConnection()
if err != nil {
return nil, err
}
p.conn = conn
return conn, nil
}
// hasConnection returns true if there's an active QUIC connection.
func (p *dnsOverQUIC) hasConnection() (ok bool) {
p.connMu.Lock()
defer p.connMu.Unlock()
return p.conn != nil
}
// getQUICConfig returns the QUIC config in a thread-safe manner. Note, that
// this method returns a pointer, it is forbidden to change its properties.
func (p *dnsOverQUIC) getQUICConfig() (c *quic.Config) {
p.quicConfigGuard.Lock()
defer p.quicConfigGuard.Unlock()
return p.quicConfig
}
// resetQUICConfig re-creates the tokens store as we may need to use a new one
// if we failed to connect.
func (p *dnsOverQUIC) resetQUICConfig() {
p.quicConfigGuard.Lock()
defer p.quicConfigGuard.Unlock()
p.quicConfig = p.quicConfig.Clone()
p.quicConfig.TokenStore = newQUICTokenStore()
}
// openStream opens a new QUIC stream for the specified connection.
func (p *dnsOverQUIC) openStream(ctx context.Context, conn quic.Connection) (quic.Stream, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
stream, err := conn.OpenStreamSync(ctx)
if err == nil {
return stream, nil
}
// We can get here if the old QUIC connection is not valid anymore. We
// should try to re-create the connection again in this case.
newConn, err := p.getConnection(false)
if err != nil {
return nil, err
}
// Open a new stream.
return newConn.OpenStreamSync(ctx)
}
// openConnection opens a new QUIC connection.
func (doq *dnsOverQUIC) openConnection() (conn quic.Connection, err error) {
tlsConfig := tlsC.GetGlobalFingerprintTLCConfig(
&tls.Config{
InsecureSkipVerify: false,
@ -137,42 +310,45 @@ func (dc *quicClient) openConnection(ctx context.Context) (quic.Connection, erro
},
SessionTicketsDisabled: false,
})
quicConfig := &quic.Config{
ConnectionIDLength: 12,
HandshakeIdleTimeout: time.Second * 8,
MaxIncomingStreams: 4,
KeepAlivePeriod: 10 * time.Second,
MaxIdleTimeout: time.Second * 120,
}
log.Debugln("opening new connection to %s", dc.addr)
var (
udp net.PacketConn
err error
)
host, port, err := net.SplitHostPort(dc.addr)
// we're using bootstrapped address instead of what's passed to the function
// it does not create an actual connection, but it helps us determine
// what IP is actually reachable (when there're v4/v6 addresses).
ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
rawConn, err := getDialHandler(doq.r, doq.proxyAdapter)(ctx, "udp", doq.addr)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to open a QUIC connection: %w", err)
}
// It's never actually used
_ = rawConn.Close()
cancel()
udpConn, ok := rawConn.(*net.UDPConn)
if !ok {
return nil, fmt.Errorf("failed to open connection to %s", doq.addr)
}
ip, err := resolver.ResolveIPv4WithResolver(host, dc.r)
addr := udpConn.RemoteAddr().String()
ip, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
p, err := strconv.Atoi(port)
udpAddr := net.UDPAddr{IP: ip.AsSlice(), Port: p}
if dc.proxyAdapter == "" {
udpAddr := net.UDPAddr{IP: net.ParseIP(ip), Port: p}
var udp net.PacketConn
if doq.proxyAdapter == "" {
udp, err = dialer.ListenPacket(ctx, "udp", "")
if err != nil {
return nil, err
}
} else {
conn, err := dialContextExtra(ctx, dc.proxyAdapter, "udp", ip, port)
ipAddr, err := netip.ParseAddr(ip)
if err != nil {
return nil, err
}
conn, err := dialContextExtra(ctx, doq.proxyAdapter, "udp", ipAddr, port)
if err != nil {
return nil, err
}
@ -185,21 +361,158 @@ func (dc *quicClient) openConnection(ctx context.Context) (quic.Connection, erro
udp = wrapConn
}
session, err := quic.DialContext(ctx, udp, &udpAddr, host, tlsConfig, quicConfig)
if err != nil {
return nil, fmt.Errorf("failed to open QUIC connection: %w", err)
}
dc.udp = udp
return session, nil
}
func (dc *quicClient) openStream(ctx context.Context) (quic.Stream, error) {
session, err := dc.getConnection(ctx)
ctx, cancel = context.WithTimeout(context.Background(), DefaultTimeout)
defer cancel()
host, _, err := net.SplitHostPort(doq.addr)
if err != nil {
return nil, err
}
// open a new stream
return session.OpenStreamSync(ctx)
conn, err = quic.DialContext(ctx, udp, &udpAddr, host, tlsConfig, doq.getQUICConfig())
if err != nil {
return nil, fmt.Errorf("opening quic connection to %s: %w", doq.addr, err)
}
return conn, nil
}
// closeConnWithError closes the active connection with error to make sure that
// new queries were processed in another connection. We can do that in the case
// of a fatal error.
func (p *dnsOverQUIC) closeConnWithError(err error) {
p.connMu.Lock()
defer p.connMu.Unlock()
if p.conn == nil {
// Do nothing, there's no active conn anyways.
return
}
code := QUICCodeNoError
if err != nil {
code = QUICCodeInternalError
}
if errors.Is(err, quic.Err0RTTRejected) {
// Reset the TokenStore only if 0-RTT was rejected.
p.resetQUICConfig()
}
err = p.conn.CloseWithError(code, "")
if err != nil {
log.Errorln("failed to close the conn: %v", err)
}
p.conn = nil
}
// readMsg reads the incoming DNS message from the QUIC stream.
func (p *dnsOverQUIC) readMsg(stream quic.Stream) (m *D.Msg, err error) {
pool := p.getBytesPool()
bufPtr := pool.Get().(*[]byte)
defer pool.Put(bufPtr)
respBuf := *bufPtr
n, err := stream.Read(respBuf)
if err != nil && n == 0 {
return nil, fmt.Errorf("reading response from %s: %w", p.Address(), err)
}
// All DNS messages (queries and responses) sent over DoQ connections MUST
// be encoded as a 2-octet length field followed by the message content as
// specified in [RFC1035].
// IMPORTANT: Note, that we ignore this prefix here as this implementation
// does not support receiving multiple messages over a single connection.
m = new(D.Msg)
err = m.Unpack(respBuf[2:])
if err != nil {
return nil, fmt.Errorf("unpacking response from %s: %w", p.Address(), err)
}
return m, nil
}
// newQUICTokenStore creates a new quic.TokenStore that is necessary to have
// in order to benefit from 0-RTT.
func newQUICTokenStore() (s quic.TokenStore) {
// You can read more on address validation here:
// https://datatracker.ietf.org/doc/html/rfc9000#section-8.1
// Setting maxOrigins to 1 and tokensPerOrigin to 10 assuming that this is
// more than enough for the way we use it (one connection per upstream).
return quic.NewLRUTokenStore(1, 10)
}
// isQUICRetryError checks the error and determines whether it may signal that
// we should re-create the QUIC connection. This requirement is caused by
// quic-go issues, see the comments inside this function.
// TODO(ameshkov): re-test when updating quic-go.
func isQUICRetryError(err error) (ok bool) {
var qAppErr *quic.ApplicationError
if errors.As(err, &qAppErr) && qAppErr.ErrorCode == 0 {
// This error is often returned when the server has been restarted,
// and we try to use the same connection on the client-side. It seems,
// that the old connections aren't closed immediately on the server-side
// and that's why one can run into this.
// In addition to that, quic-go HTTP3 client implementation does not
// clean up dead connections (this one is specific to DoH3 upstream):
// https://github.com/lucas-clemente/quic-go/issues/765
return true
}
var qIdleErr *quic.IdleTimeoutError
if errors.As(err, &qIdleErr) {
// This error means that the connection was closed due to being idle.
// In this case we should forcibly re-create the QUIC connection.
// Reproducing is rather simple, stop the server and wait for 30 seconds
// then try to send another request via the same upstream.
return true
}
var resetErr *quic.StatelessResetError
if errors.As(err, &resetErr) {
// A stateless reset is sent when a server receives a QUIC packet that
// it doesn't know how to decrypt. For instance, it may happen when
// the server was recently rebooted. We should reconnect and try again
// in this case.
return true
}
var qTransportError *quic.TransportError
if errors.As(err, &qTransportError) && qTransportError.ErrorCode == quic.NoError {
// A transport error with the NO_ERROR error code could be sent by the
// server when it considers that it's time to close the connection.
// For example, Google DNS eventually closes an active connection with
// the NO_ERROR code and "Connection max age expired" message:
// https://github.com/AdguardTeam/dnsproxy/issues/283
return true
}
if errors.Is(err, quic.Err0RTTRejected) {
// This error happens when we try to establish a 0-RTT connection with
// a token the server is no more aware of. This can be reproduced by
// restarting the QUIC server (it will clear its tokens cache). The
// next connection attempt will return this error until the client's
// tokens cache is purged.
return true
}
return false
}
func getDialHandler(r *Resolver, proxyAdapter string) dialHandler {
return func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
ip, err := r.ResolveIP(host)
if err != nil {
return nil, err
}
if len(proxyAdapter) == 0 {
return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port), dialer.WithDirect())
} else {
return dialContextExtra(ctx, proxyAdapter, network, ip.Unmap(), port, dialer.WithDirect())
}
}
}

View File

@ -71,13 +71,13 @@ type fallbackDomainFilter interface {
}
type domainFilter struct {
tree *trie.DomainTrie[bool]
tree *trie.DomainTrie[struct{}]
}
func NewDomainFilter(domains []string) *domainFilter {
df := domainFilter{tree: trie.New[bool]()}
df := domainFilter{tree: trie.New[struct{}]()}
for _, domain := range domains {
_ = df.tree.Insert(domain, true)
_ = df.tree.Insert(domain, struct{}{})
}
return &df
}

View File

@ -37,7 +37,7 @@ func withHosts(hosts *trie.DomainTrie[netip.Addr], mapping *cache.LruCache[netip
return next(ctx, r)
}
ip := record.Data
ip := record.Data()
msg := r.Copy()
if ip.Is4() && q.Qtype == D.TypeA {

View File

@ -245,7 +245,7 @@ func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
return nil
}
p := record.Data
p := record.Data()
return p.GetData()
}
@ -355,6 +355,7 @@ type NameServer struct {
Interface *atomic.String
ProxyAdapter string
Params map[string]string
PreferH3 bool
}
type FallbackFilter struct {

View File

@ -19,6 +19,10 @@ import (
D "github.com/miekg/dns"
)
const (
MaxMsgSize = 65535
)
func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) {
var ttl uint32
switch {
@ -59,13 +63,17 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
for _, s := range servers {
switch s.Net {
case "https":
ret = append(ret, newDoHClient(s.Addr, resolver, s.Params, s.ProxyAdapter))
ret = append(ret, newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter))
continue
case "dhcp":
ret = append(ret, newDHCPClient(s.Addr))
continue
case "quic":
ret = append(ret, newDOQ(resolver, s.Addr, s.ProxyAdapter))
if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter); err == nil {
ret = append(ret, doq)
}else{
log.Fatalln("DoQ format error: %v",err)
}
continue
}
@ -156,17 +164,11 @@ func dialContextExtra(ctx context.Context, adapterName string, network string, d
networkType = C.UDP
}
addrType := C.AtypIPv4
if dstIP.Is6() {
addrType = C.AtypIPv6
}
metadata := &C.Metadata{
NetWork: networkType,
AddrType: addrType,
Host: "",
DstIP: dstIP,
DstPort: port,
NetWork: networkType,
Host: "",
DstIP: dstIP,
DstPort: port,
}
adapter, ok := tunnel.Proxies()[adapterName]

View File

@ -40,12 +40,40 @@ hosts:
# Tun 配置
tun:
enable: false
stack: system # gvisor
stack: system # gvisor / lwip
dns-hijack:
- 198.18.0.2:53 # 需要劫持的 DNS
- 0.0.0.0:53 # 需要劫持的 DNS
# auto-detect-interface: true # 自动识别出口网卡
# auto-route: true # 配置路由表
# mtu: 9000 # 最大传输单元
# strict_route: true # 将所有连接路由到tun来防止泄漏但你的设备将无法其他设备被访问
inet4_route_address: # 启用 auto_route 时使用自定义路由而不是默认路由
- 0.0.0.0/1
- 128.0.0.0/1
inet6_route_address: # 启用 auto_route 时使用自定义路由而不是默认路由
- '::/1'
- '8000::/1'
# endpoint_independent_nat: false # 启用独立于端点的 NAT
# include_uid: # UID 规则仅在 Linux 下被支持,并且需要 auto_route
# - 0
# include_uid_range: # 限制被路由的的用户范围
# - 1000-99999
# exclude_uid: # 排除路由的的用户
#- 1000
# exclude_uid_range: # 排除路由的的用户范围
# - 1000-99999
# Android 用户和应用规则仅在 Android 下被支持
# 并且需要 auto_route
# include_android_user: # 限制被路由的 Android 用户
# - 0
# - 10
# include_package: # 限制被路由的 Android 应用包名
# - com.android.chrome
# exclude_package: # 排除被路由的 Android 应用包名
# - com.android.captiveportallogin
#ebpf配置
ebpf:
auto-redir: # redirect 模式,仅支持 TCP
@ -413,6 +441,16 @@ proxies:
#disable_mtu_discovery: false
# fingerprint: xxxx
- name: "wg"
type: wireguard
server: 162.159.192.1
port: 2480
ip: 172.16.0.2
ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5
private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
udp: true
# ShadowsocksR
# The supported ciphers (encryption methods): all stream ciphers in ss
# The supported obfses:

36
go.mod
View File

@ -17,61 +17,59 @@ require (
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c
github.com/lucas-clemente/quic-go v0.29.1
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
github.com/metacubex/sing-wireguard v0.0.0-20221109114053-16c22adda03c
github.com/miekg/dns v1.1.50
github.com/oschwald/geoip2-golang v1.8.0
github.com/sagernet/sing v0.0.0-20220929000216-9a83e35b7186
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
github.com/sagernet/sing v0.0.0-20221008120626-60a9910eefe4
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6
github.com/sagernet/sing-tun v0.0.0-20221012082254-488c3b75f6fd
github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f
github.com/sagernet/sing-vmess v0.0.0-20221109021549-b446d5bdddf0
github.com/sagernet/wireguard-go v0.0.0-20221108054404-7c2acadba17c
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.0
github.com/vishvananda/netlink v1.2.1-beta.2
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837
go.etcd.io/bbolt v1.3.6
go.uber.org/atomic v1.10.0
go.uber.org/automaxprocs v1.5.1
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
golang.org/x/crypto v0.1.0
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9
golang.org/x/net v0.0.0-20221004154528-8021a29435af
golang.org/x/net v0.1.0
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875
golang.org/x/sys v0.1.0
google.golang.org/protobuf v1.28.1
gopkg.in/yaml.v3 v3.0.1
)
replace github.com/vishvananda/netlink => github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820
replace github.com/lucas-clemente/quic-go => github.com/HyNetwork/quic-go v0.30.1-0.20221105180419-83715d7269a8
replace github.com/lucas-clemente/quic-go => github.com/tobyxdd/quic-go v0.28.1-0.20220706211558-7780039ad599
replace github.com/sagernet/sing-tun => github.com/MetaCubeX/sing-tun v0.0.0-20221105124245-542e9b56a6dc
require (
github.com/ajg/form v1.5.1 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/klauspost/cpuid/v2 v2.1.1 // indirect
github.com/marten-seemann/qpack v0.2.1 // indirect
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/marten-seemann/qpack v0.3.0 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e // indirect
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 // indirect
github.com/vishvananda/netns v0.0.0-20220913150850-18c4f4234207 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/tools v0.1.12 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect
lukechampine.com/blake3 v1.1.7 // indirect
)

283
go.sum
View File

@ -1,29 +1,16 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820 h1:fGKWZ25VApYnuPZoNeqdH/nZtHa2XMajwH6Yj/OgoVc=
github.com/MetaCubeX/netlink v1.2.0-beta.0.20220529072258-d6853f887820/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/HyNetwork/quic-go v0.30.1-0.20221105180419-83715d7269a8 h1:FBo40lMrk1bZZzJRJx8U+bQUPhLDGTUJ/Q5NV5BbO4Q=
github.com/HyNetwork/quic-go v0.30.1-0.20221105180419-83715d7269a8/go.mod h1:ssOrRsOmdxa768Wr78vnh2B8JozgLsMzG/g+0qEC7uk=
github.com/MetaCubeX/sing-tun v0.0.0-20221105124245-542e9b56a6dc h1:B1vmR4cwDjJQ6YM8NkuXmt9vIYOdV/+qA+F3UAFE3YY=
github.com/MetaCubeX/sing-tun v0.0.0-20221105124245-542e9b56a6dc/go.mod h1:1u3pjXA9HmH7kRiBJqM3C/zPxrxnCLd3svmqtub/RFU=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/cilium/ebpf v0.9.3 h1:5KtxXZU+scyERvkJMEm16TbScVvuuMrlhPly78ZMbSc=
github.com/cilium/ebpf v0.9.3/go.mod h1:w27N4UjpaQ9X/DGrSugxUG+H+NhgntDuPb5lCzxCn8A=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/database64128/tfo-go v1.1.2 h1:GwxtJp09BdUTVEoeT421t231eNZoGOCRkklbl4WI1kU=
github.com/database64128/tfo-go v1.1.2/go.mod h1:jgrSUPyOvTGQyn6irCOpk7L2W/q/0VLZZcovQiMi+bI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -31,48 +18,24 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc=
github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -82,59 +45,37 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c h1:OCFM4+DXTWfNlyeoddrTwdup/ztkGSyAMR2UGcPckNQ=
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0=
github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR7s0bLKJeYlQ=
github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM=
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU=
github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE=
github.com/marten-seemann/qpack v0.3.0/go.mod h1:cGfKPBiP4a9EQdxCwEwI/GEeWAsjSekBvx/X8mh58+g=
github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI=
github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE=
github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
@ -142,42 +83,21 @@ 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/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/metacubex/sing-wireguard v0.0.0-20221109114053-16c22adda03c h1:VHtXDny/TNOF7YDT9d9Qkr+x6K1O4cejXLlyPUXDeXQ=
github.com/metacubex/sing-wireguard v0.0.0-20221109114053-16c22adda03c/go.mod h1:fULJ451x1/XlpIhl+Oo+EPGKla9tFZaqT5dKLrZ+NvM=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
github.com/oschwald/geoip2-golang v1.8.0 h1:KfjYB8ojCEn/QLqsDU0AzrJ3R5Qa9vFlx3z6SLNcKTs=
github.com/oschwald/geoip2-golang v1.8.0/go.mod h1:R7bRvYjOeaoenAp9sKRS8GX5bJWcZ0laWO5+DauEktw=
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e h1:5CFRo8FJbCuf5s/eTBdZpmMbn8Fe2eSMLNAYfKanA34=
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e/go.mod h1:qbt0dWObotCfcjAJJ9AxtFPNSDUfZF+6dCpgKEOBn/g=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
@ -185,105 +105,53 @@ github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
github.com/sagernet/sing v0.0.0-20220929000216-9a83e35b7186 h1:ZDlgH6dTozS3ODaYq1GxCj+H8NvYESaex90iX72gadw=
github.com/sagernet/sing v0.0.0-20220929000216-9a83e35b7186/go.mod h1:zvgDYKI+vCAW9RyfyrKTgleI+DOa8lzHMPC7VZo3OL4=
github.com/sagernet/sing v0.0.0-20221008120626-60a9910eefe4 h1:LO7xMvMGhYmjQg2vjhTzsODyzs9/WLYu5Per+/8jIeo=
github.com/sagernet/sing v0.0.0-20221008120626-60a9910eefe4/go.mod h1:zvgDYKI+vCAW9RyfyrKTgleI+DOa8lzHMPC7VZo3OL4=
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6 h1:JJfDeYYhWunvtxsU/mOVNTmFQmnzGx9dY034qG6G3g4=
github.com/sagernet/sing-shadowsocks v0.0.0-20220819002358-7461bb09a8f6/go.mod h1:EX3RbZvrwAkPI2nuGa78T2iQXmrkT+/VQtskjou42xM=
github.com/sagernet/sing-tun v0.0.0-20221012082254-488c3b75f6fd h1:TtoZDwg09Cpqi+gCmCtL6w4oEUZ5lHz+vHIjdr1UBNY=
github.com/sagernet/sing-tun v0.0.0-20221012082254-488c3b75f6fd/go.mod h1:1u3pjXA9HmH7kRiBJqM3C/zPxrxnCLd3svmqtub/RFU=
github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f h1:xyJ3Wbibcug4DxLi/FCHX2Td667SfieyZv645b8+eEE=
github.com/sagernet/sing-vmess v0.0.0-20220921140858-b6a1bdee672f/go.mod h1:bwhAdSNET1X+j9DOXGj9NIQR39xgcWIk1rOQ9lLD+gM=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sagernet/sing-vmess v0.0.0-20221109021549-b446d5bdddf0 h1:z3kuD3hPNdEq7/wVy5lwE21f+8ZTazBtR81qswxJoCc=
github.com/sagernet/sing-vmess v0.0.0-20221109021549-b446d5bdddf0/go.mod h1:bwhAdSNET1X+j9DOXGj9NIQR39xgcWIk1rOQ9lLD+gM=
github.com/sagernet/wireguard-go v0.0.0-20221108054404-7c2acadba17c h1:qP3ZOHnjZalvqbjundbXiv/YrNlo3HOgrKc+S1QGs0U=
github.com/sagernet/wireguard-go v0.0.0-20221108054404-7c2acadba17c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tobyxdd/quic-go v0.28.1-0.20220706211558-7780039ad599 h1:We+z04jRpTGxFggeGWf+GbinhlIk1I1kMMEgujhUfiA=
github.com/tobyxdd/quic-go v0.28.1-0.20220706211558-7780039ad599/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0=
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIzeqnPzQcsLqqvL6vEfTPinME=
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.0-20220913150850-18c4f4234207 h1:nn7SOQy8xCu3iXNv7oiBhhEQtbWdnEOMnuKBlHvrqIM=
github.com/vishvananda/netns v0.0.0-20220913150850-18c4f4234207/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 h1:AHhUwwFJGl27E46OpdJHplZkK09m7aETNBNzhT6t15M=
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837/go.mod h1:YJTRELIWrGxR1s8xcEBgxcxBfwQfMGjdvNLTjN9XFgY=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/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-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4=
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/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=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
@ -291,95 +159,55 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
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-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4=
golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 h1:cu5kTvlzcw1Q5S9f5ip1/cpiB4nXvw1XYzFPGgzLUOY=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20210615035016-665e8c7367d1/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-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM=
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab h1:eHo2TTVBaAPw9lDGK2Gb9GyPMXT6g7O63W6sx3ylbzU=
golang.org/x/text v0.3.8-0.20220124021120-d1c84af989ab/go.mod h1:EFNZuWvGYxIRUEX+K8UmCFwYmZjqcrnq15ZuVldZkZ0=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
@ -388,56 +216,17 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View File

@ -86,7 +86,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
loadRuleProvider(cfg.RuleProviders)
updateGeneral(cfg.General, force)
updateIPTables(cfg)
updateTun(cfg.Tun)
updateTun(cfg.General)
updateExperimental(cfg)
log.SetLevel(cfg.General.LogLevel)
@ -105,14 +105,18 @@ func GetGeneral() *config.General {
general := &config.General{
Inbound: config.Inbound{
Port: ports.Port,
SocksPort: ports.SocksPort,
RedirPort: ports.RedirPort,
TProxyPort: ports.TProxyPort,
MixedPort: ports.MixedPort,
Authentication: authenticator,
AllowLan: P.AllowLan(),
BindAddress: P.BindAddress(),
Port: ports.Port,
SocksPort: ports.SocksPort,
RedirPort: ports.RedirPort,
TProxyPort: ports.TProxyPort,
MixedPort: ports.MixedPort,
ShadowSocksConfig: ports.ShadowSocksConfig,
VmessConfig: ports.VmessConfig,
TcpTunConfig: ports.TcpTunConfig,
UdpTunConfig: ports.UdpTunConfig,
Authentication: authenticator,
AllowLan: P.AllowLan(),
BindAddress: P.BindAddress(),
},
Mode: tunnel.Mode(),
LogLevel: log.Level(),
@ -258,9 +262,12 @@ func loadProxyProvider(proxyProviders map[string]provider.ProxyProvider) {
wg.Wait()
}
func updateTun(tun *config.Tun) {
P.ReCreateTun(tun, tunnel.TCPIn(), tunnel.UDPIn())
P.ReCreateRedirToTun(tun.RedirectToTun)
func updateTun(general *config.General) {
if general == nil {
return
}
P.ReCreateTun(general.Tun, tunnel.TCPIn(), tunnel.UDPIn())
P.ReCreateRedirToTun(general.Tun.RedirectToTun)
}
func updateSniffer(sniffer *config.Sniffer) {
@ -339,6 +346,10 @@ func updateGeneral(general *config.General, force bool) {
P.ReCreateAutoRedir(general.EBpf.AutoRedir, tcpIn, udpIn)
P.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn)
P.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
P.ReCreateShadowSocks(general.ShadowSocksConfig, tcpIn, udpIn)
P.ReCreateVmess(general.VmessConfig, tcpIn, udpIn)
P.ReCreateTcpTun(general.TcpTunConfig, tcpIn, udpIn)
P.ReCreateUdpTun(general.UdpTunConfig, tcpIn, udpIn)
}
func updateUsers(users []auth.AuthUser) {
@ -400,7 +411,7 @@ func updateIPTables(cfg *config.Config) {
}
}()
if cfg.Tun.Enable {
if cfg.General.Tun.Enable {
err = fmt.Errorf("when tun is enabled, iptables cannot be set automatically")
return
}

View File

@ -1,14 +1,16 @@
package route
import (
"github.com/Dreamacro/clash/component/dialer"
"net/http"
"net/netip"
"path/filepath"
"sync"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/config"
"github.com/Dreamacro/clash/constant"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/hub/executor"
P "github.com/Dreamacro/clash/listener"
"github.com/Dreamacro/clash/log"
@ -33,20 +35,50 @@ 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"`
Tun *config.Tun `json:"tun"`
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"`
Sniffing *bool `json:"sniffing"`
TcpConcurrent *bool `json:"tcp-concurrent"`
InterfaceName *string `json:"interface-name"`
Port *int `json:"port"`
SocksPort *int `json:"socks-port"`
RedirPort *int `json:"redir-port"`
TProxyPort *int `json:"tproxy-port"`
MixedPort *int `json:"mixed-port"`
Tun *tunSchema `json:"tun"`
ShadowSocksConfig *string `json:"ss-config"`
VmessConfig *string `json:"vmess-config"`
TcptunConfig *string `json:"tcptun-config"`
UdptunConfig *string `json:"udptun-config"`
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"`
Sniffing *bool `json:"sniffing"`
TcpConcurrent *bool `json:"tcp-concurrent"`
InterfaceName *string `json:"interface-name"`
}
type tunSchema 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"`
AutoDetectInterface *bool `yaml:"auto-detect-interface" json:"auto-detect-interface"`
//RedirectToTun []string `yaml:"-" json:"-"`
MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"`
//Inet4Address *[]config.ListenPrefix `yaml:"inet4-address" json:"inet4-address,omitempty"`
Inet6Address *[]config.ListenPrefix `yaml:"inet6-address" json:"inet6-address,omitempty"`
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
Inet4RouteAddress *[]config.ListenPrefix `yaml:"inet4-route-address" json:"inet4-route-address,omitempty"`
Inet6RouteAddress *[]config.ListenPrefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"`
IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
ExcludeUIDRange *[]string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
IncludeAndroidUser *[]int `yaml:"include-android-user" json:"include-android-user,omitempty"`
IncludePackage *[]string `yaml:"include-package" json:"include-package,omitempty"`
ExcludePackage *[]string `yaml:"exclude-package" json:"exclude-package,omitempty"`
EndpointIndependentNat *bool `yaml:"endpoint-independent-nat" json:"endpoint-independent-nat,omitempty"`
UDPTimeout *int64 `yaml:"udp-timeout" json:"udp-timeout,omitempty"`
}
func getConfigs(w http.ResponseWriter, r *http.Request) {
@ -62,6 +94,72 @@ func pointerOrDefault(p *int, def int) int {
return def
}
func pointerOrDefaultString(p *string, def string) string {
if p != nil {
return *p
}
return def
}
func pointerOrDefaultTun(p *tunSchema, def config.Tun) config.Tun {
if p != nil {
def.Enable = p.Enable
if p.Device != nil {
def.Device = *p.Device
}
if p.Stack != nil {
def.Stack = *p.Stack
}
if p.DNSHijack != nil {
def.DNSHijack = *p.DNSHijack
}
if p.AutoRoute != nil {
def.AutoRoute = *p.AutoRoute
}
if p.AutoDetectInterface != nil {
def.AutoDetectInterface = *p.AutoDetectInterface
}
if p.MTU != nil {
def.MTU = *p.MTU
}
//if p.Inet4Address != nil {
// def.Inet4Address = *p.Inet4Address
//}
if p.Inet6Address != nil {
def.Inet6Address = *p.Inet6Address
}
if p.IncludeUID != nil {
def.IncludeUID = *p.IncludeUID
}
if p.IncludeUIDRange != nil {
def.IncludeUIDRange = *p.IncludeUIDRange
}
if p.ExcludeUID != nil {
def.ExcludeUID = *p.ExcludeUID
}
if p.ExcludeUIDRange != nil {
def.ExcludeUIDRange = *p.ExcludeUIDRange
}
if p.IncludeAndroidUser != nil {
def.IncludeAndroidUser = *p.IncludeAndroidUser
}
if p.IncludePackage != nil {
def.IncludePackage = *p.IncludePackage
}
if p.ExcludePackage != nil {
def.ExcludePackage = *p.ExcludePackage
}
if p.EndpointIndependentNat != nil {
def.EndpointIndependentNat = *p.EndpointIndependentNat
}
if p.UDPTimeout != nil {
def.UDPTimeout = *p.UDPTimeout
}
}
return def
}
func patchConfigs(w http.ResponseWriter, r *http.Request) {
general := &configSchema{}
if err := render.DecodeJSON(r.Body, general); err != nil {
@ -100,6 +198,11 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tcpIn, udpIn)
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn)
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tcpIn, udpIn)
P.ReCreateTun(pointerOrDefaultTun(general.Tun, P.LastTunConf), tcpIn, udpIn)
P.ReCreateShadowSocks(pointerOrDefaultString(general.ShadowSocksConfig, ports.ShadowSocksConfig), tcpIn, udpIn)
P.ReCreateVmess(pointerOrDefaultString(general.VmessConfig, ports.VmessConfig), tcpIn, udpIn)
P.ReCreateTcpTun(pointerOrDefaultString(general.TcptunConfig, ports.TcpTunConfig), tcpIn, udpIn)
P.ReCreateUdpTun(pointerOrDefaultString(general.UdptunConfig, ports.UdpTunConfig), tcpIn, udpIn)
if general.Mode != nil {
tunnel.SetMode(*general.Mode)

View File

@ -2,7 +2,6 @@ package proxy
import (
"fmt"
"github.com/Dreamacro/clash/listener/sing_tun"
"golang.org/x/exp/slices"
"net"
"sort"
@ -18,30 +17,37 @@ import (
"github.com/Dreamacro/clash/listener/inner"
"github.com/Dreamacro/clash/listener/mixed"
"github.com/Dreamacro/clash/listener/redir"
"github.com/Dreamacro/clash/listener/sing_shadowsocks"
"github.com/Dreamacro/clash/listener/sing_tun"
"github.com/Dreamacro/clash/listener/sing_vmess"
"github.com/Dreamacro/clash/listener/socks"
"github.com/Dreamacro/clash/listener/tproxy"
"github.com/Dreamacro/clash/listener/tunnel"
"github.com/Dreamacro/clash/log"
)
var (
allowLan = false
bindAddress = "*"
lastTunConf *config.Tun
inboundTfo = false
socksListener *socks.Listener
socksUDPListener *socks.UDPListener
httpListener *http.Listener
redirListener *redir.Listener
redirUDPListener *tproxy.UDPListener
tproxyListener *tproxy.Listener
tproxyUDPListener *tproxy.UDPListener
mixedListener *mixed.Listener
mixedUDPLister *socks.UDPListener
tunLister *sing_tun.Listener
autoRedirListener *autoredir.Listener
autoRedirProgram *ebpf.TcEBpfProgram
tcProgram *ebpf.TcEBpfProgram
socksListener *socks.Listener
socksUDPListener *socks.UDPListener
httpListener *http.Listener
redirListener *redir.Listener
redirUDPListener *tproxy.UDPListener
tproxyListener *tproxy.Listener
tproxyUDPListener *tproxy.UDPListener
mixedListener *mixed.Listener
mixedUDPLister *socks.UDPListener
tunLister *sing_tun.Listener
shadowSocksListener C.AdvanceListener
vmessListener *sing_vmess.Listener
tcpTunListener *tunnel.Listener
udpTunListener *tunnel.UdpListener
autoRedirListener *autoredir.Listener
autoRedirProgram *ebpf.TcEBpfProgram
tcProgram *ebpf.TcEBpfProgram
// lock for recreate function
socksMux sync.Mutex
@ -50,25 +56,35 @@ var (
tproxyMux sync.Mutex
mixedMux sync.Mutex
tunMux sync.Mutex
ssMux sync.Mutex
vmessMux sync.Mutex
tcpTunMux sync.Mutex
udpTunMux sync.Mutex
autoRedirMux sync.Mutex
tcMux sync.Mutex
LastTunConf config.Tun
)
type Ports 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"`
Port int `json:"port"`
SocksPort int `json:"socks-port"`
RedirPort int `json:"redir-port"`
TProxyPort int `json:"tproxy-port"`
MixedPort int `json:"mixed-port"`
ShadowSocksConfig string `json:"ss-config"`
VmessConfig string `json:"vmess-config"`
TcpTunConfig string `json:"tcptun-config"`
UdpTunConfig string `json:"udptun-config"`
}
func GetTunConf() config.Tun {
if lastTunConf == nil {
if tunLister == nil {
return config.Tun{
Enable: false,
}
}
return *lastTunConf
return tunLister.Config()
}
func AllowLan() bool {
@ -234,6 +250,156 @@ func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
log.Infoln("Redirect proxy listening at: %s", redirListener.Address())
}
func ReCreateShadowSocks(shadowSocksConfig string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
ssMux.Lock()
defer ssMux.Unlock()
var err error
defer func() {
if err != nil {
log.Errorln("Start ShadowSocks server error: %s", err.Error())
}
}()
shouldIgnore := false
if shadowSocksListener != nil {
if shadowSocksListener.Config() != shadowSocksConfig {
shadowSocksListener.Close()
shadowSocksListener = nil
} else {
shouldIgnore = true
}
}
if shouldIgnore {
return
}
if len(shadowSocksConfig) == 0 {
return
}
listener, err := sing_shadowsocks.New(shadowSocksConfig, tcpIn, udpIn)
if err != nil {
return
}
shadowSocksListener = listener
return
}
func ReCreateVmess(vmessConfig string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
vmessMux.Lock()
defer vmessMux.Unlock()
var err error
defer func() {
if err != nil {
log.Errorln("Start Vmess server error: %s", err.Error())
}
}()
shouldIgnore := false
if vmessListener != nil {
if vmessListener.Config() != vmessConfig {
vmessListener.Close()
vmessListener = nil
} else {
shouldIgnore = true
}
}
if shouldIgnore {
return
}
if len(vmessConfig) == 0 {
return
}
listener, err := sing_vmess.New(vmessConfig, tcpIn, udpIn)
if err != nil {
return
}
vmessListener = listener
return
}
func ReCreateTcpTun(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
tcpTunMux.Lock()
defer tcpTunMux.Unlock()
shouldIgnore := false
var err error
defer func() {
if err != nil {
log.Errorln("Start TcpTun server error: %s", err.Error())
}
}()
if tcpTunListener != nil {
if tcpTunListener.Config() != config {
tcpTunListener.Close()
tcpTunListener = nil
} else {
shouldIgnore = true
}
}
if shouldIgnore {
return
}
tcpListener, err := tunnel.New(config, tcpIn)
if err != nil {
return
}
tcpTunListener = tcpListener
return
}
func ReCreateUdpTun(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
udpTunMux.Lock()
defer udpTunMux.Unlock()
shouldIgnore := false
var err error
defer func() {
if err != nil {
log.Errorln("Start UdpTun server error: %s", err.Error())
}
}()
if udpTunListener != nil {
if udpTunListener.Config() != config {
udpTunListener.Close()
udpTunListener = nil
} else {
shouldIgnore = true
}
}
if shouldIgnore {
return
}
udpListener, err := tunnel.NewUdp(config, udpIn)
if err != nil {
return
}
udpTunListener = udpListener
return
}
func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) {
tproxyMux.Lock()
defer tproxyMux.Unlock()
@ -335,9 +501,12 @@ 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, 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()
defer func() {
LastTunConf = tunConf
tunMux.Unlock()
}()
var err error
defer func() {
@ -347,7 +516,7 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *
}
}()
if !hasTunConfigChange(tunConf) {
if !hasTunConfigChange(&tunConf) {
if tunLister != nil {
tunLister.FlushDefaultInterface()
}
@ -360,9 +529,7 @@ func ReCreateTun(tunConf *config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *
return
}
tunLister, err = sing_tun.New(*tunConf, tcpIn, udpIn)
lastTunConf = tunConf
tunLister, err = sing_tun.New(tunConf, tcpIn, udpIn)
}
func ReCreateRedirToTun(ifaceNames []string) {
@ -382,11 +549,13 @@ func ReCreateRedirToTun(ifaceNames []string) {
return
}
if lastTunConf == nil || !lastTunConf.Enable {
tunConf := GetTunConf()
if !tunConf.Enable {
return
}
program, err := ebpf.NewTcEBpfProgram(nicArr, lastTunConf.Device)
program, err := ebpf.NewTcEBpfProgram(nicArr, tunConf.Device)
if err != nil {
log.Errorln("Attached tc ebpf program error: %v", err)
return
@ -486,6 +655,22 @@ func GetPorts() *Ports {
ports.MixedPort = port
}
if shadowSocksListener != nil {
ports.ShadowSocksConfig = shadowSocksListener.Config()
}
if vmessListener != nil {
ports.VmessConfig = vmessListener.Config()
}
if tcpTunListener != nil {
ports.TcpTunConfig = tcpTunListener.Config()
}
if udpTunListener != nil {
ports.UdpTunConfig = udpTunListener.Config()
}
return ports
}
@ -509,37 +694,82 @@ func genAddr(host string, port int, allowLan bool) string {
}
func hasTunConfigChange(tunConf *config.Tun) bool {
if lastTunConf == nil {
if LastTunConf.Enable != tunConf.Enable ||
LastTunConf.Device != tunConf.Device ||
LastTunConf.Stack != tunConf.Stack ||
LastTunConf.AutoRoute != tunConf.AutoRoute ||
LastTunConf.AutoDetectInterface != tunConf.AutoDetectInterface ||
LastTunConf.MTU != tunConf.MTU ||
LastTunConf.StrictRoute != tunConf.StrictRoute ||
LastTunConf.EndpointIndependentNat != tunConf.EndpointIndependentNat ||
LastTunConf.UDPTimeout != tunConf.UDPTimeout {
return true
}
if len(lastTunConf.DNSHijack) != len(tunConf.DNSHijack) {
if len(LastTunConf.DNSHijack) != len(tunConf.DNSHijack) {
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
}
}
sort.Slice(tunConf.Inet4Address, func(i, j int) bool {
return tunConf.Inet4Address[i].Build().String() < tunConf.Inet4Address[j].Build().String()
})
if lastTunConf.Enable != tunConf.Enable ||
lastTunConf.Device != tunConf.Device ||
lastTunConf.Stack != tunConf.Stack ||
lastTunConf.AutoRoute != tunConf.AutoRoute ||
lastTunConf.AutoDetectInterface != tunConf.AutoDetectInterface {
return true
}
sort.Slice(tunConf.Inet6Address, func(i, j int) bool {
return tunConf.Inet6Address[i].Build().String() < tunConf.Inet6Address[j].Build().String()
})
if slices.Equal(tunConf.Inet4Address, lastTunConf.Inet4Address) && slices.Equal(tunConf.Inet6Address, lastTunConf.Inet6Address) {
sort.Slice(tunConf.Inet4RouteAddress, func(i, j int) bool {
return tunConf.Inet4RouteAddress[i].Build().String() < tunConf.Inet4RouteAddress[j].Build().String()
})
sort.Slice(tunConf.Inet6RouteAddress, func(i, j int) bool {
return tunConf.Inet6RouteAddress[i].Build().String() < tunConf.Inet6RouteAddress[j].Build().String()
})
sort.Slice(tunConf.IncludeUID, func(i, j int) bool {
return tunConf.IncludeUID[i] < tunConf.IncludeUID[j]
})
sort.Slice(tunConf.IncludeUIDRange, func(i, j int) bool {
return tunConf.IncludeUIDRange[i] < tunConf.IncludeUIDRange[j]
})
sort.Slice(tunConf.ExcludeUID, func(i, j int) bool {
return tunConf.ExcludeUID[i] < tunConf.ExcludeUID[j]
})
sort.Slice(tunConf.ExcludeUIDRange, func(i, j int) bool {
return tunConf.ExcludeUIDRange[i] < tunConf.ExcludeUIDRange[j]
})
sort.Slice(tunConf.IncludeAndroidUser, func(i, j int) bool {
return tunConf.IncludeAndroidUser[i] < tunConf.IncludeAndroidUser[j]
})
sort.Slice(tunConf.IncludePackage, func(i, j int) bool {
return tunConf.IncludePackage[i] < tunConf.IncludePackage[j]
})
sort.Slice(tunConf.ExcludePackage, func(i, j int) bool {
return tunConf.ExcludePackage[i] < tunConf.ExcludePackage[j]
})
if !slices.Equal(tunConf.DNSHijack, LastTunConf.DNSHijack) ||
!slices.Equal(tunConf.Inet4Address, LastTunConf.Inet4Address) ||
!slices.Equal(tunConf.Inet6Address, LastTunConf.Inet6Address) ||
!slices.Equal(tunConf.Inet4RouteAddress, LastTunConf.Inet4RouteAddress) ||
!slices.Equal(tunConf.Inet6RouteAddress, LastTunConf.Inet6RouteAddress) ||
!slices.Equal(tunConf.IncludeUID, LastTunConf.IncludeUID) ||
!slices.Equal(tunConf.IncludeUIDRange, LastTunConf.IncludeUIDRange) ||
!slices.Equal(tunConf.ExcludeUID, LastTunConf.ExcludeUID) ||
!slices.Equal(tunConf.ExcludeUIDRange, LastTunConf.ExcludeUIDRange) ||
!slices.Equal(tunConf.IncludeAndroidUser, LastTunConf.IncludeAndroidUser) ||
!slices.Equal(tunConf.IncludePackage, LastTunConf.IncludePackage) ||
!slices.Equal(tunConf.ExcludePackage, LastTunConf.ExcludePackage) {
return true
}
@ -551,5 +781,5 @@ func Cleanup(wait bool) {
tunLister.Close()
tunLister = nil
}
lastTunConf = nil
LastTunConf = config.Tun{}
}

105
listener/shadowsocks/tcp.go Normal file
View File

@ -0,0 +1,105 @@
package shadowsocks
import (
"net"
"strings"
"github.com/Dreamacro/clash/adapter/inbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/transport/shadowsocks/core"
"github.com/Dreamacro/clash/transport/socks5"
)
type Listener struct {
closed bool
config string
listeners []net.Listener
udpListeners []*UDPListener
pickCipher core.Cipher
}
var _listener *Listener
func New(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (*Listener, error) {
addr, cipher, password, err := ParseSSURL(config)
if err != nil {
return nil, err
}
pickCipher, err := core.PickCipher(cipher, nil, password)
if err != nil {
return nil, err
}
sl := &Listener{false, config, nil, nil, pickCipher}
_listener = sl
for _, addr := range strings.Split(addr, ",") {
addr := addr
//UDP
ul, err := NewUDP(addr, pickCipher, udpIn)
if err != nil {
return nil, err
}
sl.udpListeners = append(sl.udpListeners, ul)
//TCP
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
sl.listeners = append(sl.listeners, l)
go func() {
log.Infoln("ShadowSocks proxy listening at: %s", l.Addr().String())
for {
c, err := l.Accept()
if err != nil {
if sl.closed {
break
}
continue
}
_ = c.(*net.TCPConn).SetKeepAlive(true)
go sl.HandleConn(c, tcpIn)
}
}()
}
return sl, nil
}
func (l *Listener) Close() {
l.closed = true
for _, lis := range l.listeners {
_ = lis.Close()
}
for _, lis := range l.udpListeners {
_ = lis.Close()
}
}
func (l *Listener) Config() string {
return l.config
}
func (l *Listener) HandleConn(conn net.Conn, in chan<- C.ConnContext) {
conn = l.pickCipher.StreamConn(conn)
target, err := socks5.ReadAddr(conn, make([]byte, socks5.MaxAddrLen))
if err != nil {
_ = conn.Close()
return
}
in <- inbound.NewSocket(target, conn, C.SHADOWSOCKS)
}
func HandleShadowSocks(conn net.Conn, in chan<- C.ConnContext) bool {
if _listener != nil && _listener.pickCipher != nil {
go _listener.HandleConn(conn, in)
return true
}
return false
}

View File

@ -0,0 +1,76 @@
package shadowsocks
import (
"net"
"github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/common/sockopt"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/transport/shadowsocks/core"
"github.com/Dreamacro/clash/transport/socks5"
)
type UDPListener struct {
packetConn net.PacketConn
closed bool
}
func NewUDP(addr string, pickCipher core.Cipher, in chan<- *inbound.PacketAdapter) (*UDPListener, error) {
l, err := net.ListenPacket("udp", addr)
if err != nil {
return nil, err
}
err = sockopt.UDPReuseaddr(l.(*net.UDPConn))
if err != nil {
log.Warnln("Failed to Reuse UDP Address: %s", err)
}
sl := &UDPListener{l, false}
conn := pickCipher.PacketConn(l)
go func() {
for {
buf := pool.Get(pool.RelayBufferSize)
n, remoteAddr, err := conn.ReadFrom(buf)
if err != nil {
pool.Put(buf)
if sl.closed {
break
}
continue
}
handleSocksUDP(conn, in, buf[:n], remoteAddr)
}
}()
return sl, nil
}
func (l *UDPListener) Close() error {
l.closed = true
return l.packetConn.Close()
}
func handleSocksUDP(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, addr net.Addr) {
tgtAddr := socks5.SplitAddr(buf)
if tgtAddr == nil {
// Unresolved UDP packet, return buffer to the pool
pool.Put(buf)
return
}
target := socks5.ParseAddr(tgtAddr.String())
payload := buf[len(tgtAddr):]
packet := &packet{
pc: pc,
rAddr: addr,
payload: payload,
bufRef: buf,
}
select {
case in <- inbound.NewPacket(target, packet, C.SHADOWSOCKS):
default:
}
}

View File

@ -0,0 +1,59 @@
package shadowsocks
import (
"bytes"
"errors"
"net"
"net/url"
"github.com/Dreamacro/clash/common/pool"
"github.com/Dreamacro/clash/transport/socks5"
)
type packet struct {
pc net.PacketConn
rAddr net.Addr
payload []byte
bufRef []byte
}
func (c *packet) Data() []byte {
return c.payload
}
// WriteBack wirtes UDP packet with source(ip, port) = `addr`
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
if addr == nil {
err = errors.New("address is invalid")
return
}
packet := bytes.Join([][]byte{socks5.ParseAddrToSocksAddr(addr), b}, []byte{})
return c.pc.WriteTo(packet, c.rAddr)
}
// LocalAddr returns the source IP/Port of UDP Packet
func (c *packet) LocalAddr() net.Addr {
return c.rAddr
}
func (c *packet) Drop() {
pool.Put(c.bufRef)
}
func (c *packet) InAddr() net.Addr {
return c.pc.LocalAddr()
}
func ParseSSURL(s string) (addr, cipher, password string, err error) {
u, err := url.Parse(s)
if err != nil {
return
}
addr = u.Host
if u.User != nil {
cipher = u.User.Username()
password, _ = u.User.Password()
}
return
}

View File

@ -0,0 +1,160 @@
package sing_shadowsocks
import (
"context"
"fmt"
"net"
"strings"
"github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/common/sockopt"
C "github.com/Dreamacro/clash/constant"
embedSS "github.com/Dreamacro/clash/listener/shadowsocks"
"github.com/Dreamacro/clash/listener/sing"
"github.com/Dreamacro/clash/log"
shadowsocks "github.com/sagernet/sing-shadowsocks"
"github.com/sagernet/sing-shadowsocks/shadowaead"
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/metadata"
)
type Listener struct {
closed bool
config string
listeners []net.Listener
udpListeners []net.PacketConn
service shadowsocks.Service
}
var _listener *Listener
func New(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (C.AdvanceListener, error) {
addr, cipher, password, err := embedSS.ParseSSURL(config)
if err != nil {
return nil, err
}
udpTimeout := int64(sing.UDPTimeout.Seconds())
h := &sing.ListenerHandler{
TcpIn: tcpIn,
UdpIn: udpIn,
Type: C.SHADOWSOCKS,
}
sl := &Listener{false, config, nil, nil, nil}
switch {
case cipher == shadowsocks.MethodNone:
sl.service = shadowsocks.NewNoneService(udpTimeout, h)
case common.Contains(shadowaead.List, cipher):
sl.service, err = shadowaead.NewService(cipher, nil, password, udpTimeout, h)
case common.Contains(shadowaead_2022.List, cipher):
sl.service, err = shadowaead_2022.NewServiceWithPassword(cipher, password, udpTimeout, h)
default:
err = fmt.Errorf("shadowsocks: unsupported method: %s", cipher)
return embedSS.New(config, tcpIn, udpIn)
}
if err != nil {
return nil, err
}
_listener = sl
for _, addr := range strings.Split(addr, ",") {
addr := addr
//UDP
ul, err := net.ListenPacket("udp", addr)
if err != nil {
return nil, err
}
err = sockopt.UDPReuseaddr(ul.(*net.UDPConn))
if err != nil {
log.Warnln("Failed to Reuse UDP Address: %s", err)
}
sl.udpListeners = append(sl.udpListeners, ul)
go func() {
conn := bufio.NewPacketConn(ul)
for {
buff := buf.NewPacket()
remoteAddr, err := conn.ReadPacket(buff)
if err != nil {
buff.Release()
if sl.closed {
break
}
continue
}
_ = sl.service.NewPacket(context.TODO(), conn, buff, metadata.Metadata{
Protocol: "shadowsocks",
Source: remoteAddr,
})
}
}()
//TCP
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
sl.listeners = append(sl.listeners, l)
go func() {
log.Infoln("ShadowSocks proxy listening at: %s", l.Addr().String())
for {
c, err := l.Accept()
if err != nil {
if sl.closed {
break
}
continue
}
_ = c.(*net.TCPConn).SetKeepAlive(true)
go sl.HandleConn(c, tcpIn)
}
}()
}
return sl, nil
}
func (l *Listener) Close() {
l.closed = true
for _, lis := range l.listeners {
_ = lis.Close()
}
for _, lis := range l.udpListeners {
_ = lis.Close()
}
}
func (l *Listener) Config() string {
return l.config
}
func (l *Listener) HandleConn(conn net.Conn, in chan<- C.ConnContext) {
err := l.service.NewConnection(context.TODO(), conn, metadata.Metadata{
Protocol: "shadowsocks",
Source: metadata.ParseSocksaddr(conn.RemoteAddr().String()),
})
if err != nil {
_ = conn.Close()
return
}
}
func HandleShadowSocks(conn net.Conn, in chan<- C.ConnContext) bool {
if _listener != nil && _listener.service != nil {
go _listener.HandleConn(conn, in)
return true
}
return embedSS.HandleShadowSocks(conn, in)
}

View File

@ -69,6 +69,7 @@ func New(options config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
tunName := options.Device
if tunName == "" {
tunName = CalculateInterfaceName(InterfaceName)
options.Device = tunName
}
tunMTU := options.MTU
if tunMTU == 0 {
@ -203,11 +204,15 @@ func New(options config.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.P
if err != nil {
return
}
err = l.tunStack.Start()
if err != nil {
return
}
log.Infoln("Tun adapter listening at: %s(%s,%s), mtu: %d, auto route: %v, ip stack: %s",
//l.openAndroidHotspot(tunOptions)
log.Infoln("[TUN] Tun adapter listening at: %s(%s,%s), mtu: %d, auto route: %v, ip stack: %s",
tunName, tunOptions.Inet4Address, tunOptions.Inet6Address, tunMTU, options.AutoRoute, options.Stack)
return
}
@ -218,9 +223,9 @@ func (l *Listener) FlushDefaultInterface() {
for _, destination := range []netip.Addr{netip.IPv4Unspecified(), netip.IPv6Unspecified(), netip.MustParseAddr("1.1.1.1")} {
autoDetectInterfaceName := l.defaultInterfaceMonitor.DefaultInterfaceName(destination)
if autoDetectInterfaceName == l.tunName {
log.Warnln("Auto detect interface by %s get same name with tun", destination.String())
log.Warnln("[TUN] Auto detect interface by %s get same name with tun", destination.String())
} else if autoDetectInterfaceName == "" || autoDetectInterfaceName == "<nil>" {
log.Warnln("Auto detect interface by %s get empty name.", destination.String())
log.Warnln("[TUN] Auto detect interface by %s get empty name.", destination.String())
} else {
targetInterface = autoDetectInterfaceName
if old := dialer.DefaultInterface.Load(); old != targetInterface {

View File

@ -1,7 +1,11 @@
package sing_tun
import (
"github.com/Dreamacro/clash/log"
"github.com/sagernet/netlink"
tun "github.com/sagernet/sing-tun"
"golang.org/x/sys/unix"
"runtime"
)
func (l *Listener) buildAndroidRules(tunOptions *tun.Options) error {
@ -21,3 +25,25 @@ func (l *Listener) buildAndroidRules(tunOptions *tun.Options) error {
func (h *ListenerHandler) OnPackagesUpdated(packages int, sharedUsers int) {
return
}
func (l *Listener) openAndroidHotspot(tunOptions tun.Options) {
if runtime.GOOS == "android" && tunOptions.AutoRoute {
priority := 9000
if len(tunOptions.ExcludedRanges()) > 0 {
priority++
}
if tunOptions.InterfaceMonitor.AndroidVPNEnabled() {
priority++
}
it := netlink.NewRule()
it.Priority = priority
it.IifName = tunOptions.Name
it.Table = 254 //main
it.Family = unix.AF_INET
it.SuppressPrefixlen = 0
err := netlink.RuleAdd(it)
if err != nil {
log.Warnln("[TUN] add AndroidHotspot rule error")
}
}
}

View File

@ -9,3 +9,4 @@ import (
func (l *Listener) buildAndroidRules(tunOptions *tun.Options) error {
return nil
}
func (l *Listener) openAndroidHotspot(tunOptions tun.Options) {}

View File

@ -0,0 +1,126 @@
package sing_vmess
import (
"context"
"net"
"net/url"
"strings"
"github.com/Dreamacro/clash/adapter/inbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/listener/sing"
"github.com/Dreamacro/clash/log"
vmess "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common/metadata"
)
type Listener struct {
closed bool
config string
listeners []net.Listener
service *vmess.Service[string]
}
var _listener *Listener
func New(config string, tcpIn chan<- C.ConnContext, udpIn chan<- *inbound.PacketAdapter) (*Listener, error) {
addr, username, password, err := parseVmessURL(config)
if err != nil {
return nil, err
}
h := &sing.ListenerHandler{
TcpIn: tcpIn,
UdpIn: udpIn,
Type: C.VMESS,
}
service := vmess.NewService[string](h)
err = service.UpdateUsers([]string{username}, []string{password}, []int{1})
if err != nil {
return nil, err
}
err = service.Start()
if err != nil {
return nil, err
}
sl := &Listener{false, config, nil, service}
_listener = sl
for _, addr := range strings.Split(addr, ",") {
addr := addr
//TCP
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
sl.listeners = append(sl.listeners, l)
go func() {
log.Infoln("Vmess proxy listening at: %s", l.Addr().String())
for {
c, err := l.Accept()
if err != nil {
if sl.closed {
break
}
continue
}
_ = c.(*net.TCPConn).SetKeepAlive(true)
go sl.HandleConn(c, tcpIn)
}
}()
}
return sl, nil
}
func (l *Listener) Close() {
l.closed = true
for _, lis := range l.listeners {
_ = lis.Close()
}
_ = l.service.Close()
}
func (l *Listener) Config() string {
return l.config
}
func (l *Listener) HandleConn(conn net.Conn, in chan<- C.ConnContext) {
err := l.service.NewConnection(context.TODO(), conn, metadata.Metadata{
Protocol: "vmess",
Source: metadata.ParseSocksaddr(conn.RemoteAddr().String()),
})
if err != nil {
_ = conn.Close()
return
}
}
func HandleVmess(conn net.Conn, in chan<- C.ConnContext) bool {
if _listener != nil && _listener.service != nil {
go _listener.HandleConn(conn, in)
return true
}
return false
}
func parseVmessURL(s string) (addr, username, password string, err error) {
u, err := url.Parse(s)
if err != nil {
return
}
addr = u.Host
if u.User != nil {
username = u.User.Username()
password, _ = u.User.Password()
}
return
}

View File

@ -35,3 +35,7 @@ func (c *packet) LocalAddr() net.Addr {
func (c *packet) Drop() {
pool.Put(c.bufRef)
}
func (c *packet) InAddr() net.Addr {
return c.pc.LocalAddr()
}

View File

@ -7,6 +7,7 @@ import (
)
type packet struct {
pc net.PacketConn
lAddr *net.UDPAddr
buf []byte
}
@ -35,3 +36,7 @@ func (c *packet) LocalAddr() net.Addr {
func (c *packet) Drop() {
pool.Put(c.buf)
}
func (c *packet) InAddr() net.Addr {
return c.pc.LocalAddr()
}

View File

@ -81,6 +81,7 @@ func NewUDP(addr string, in chan<- *inbound.PacketAdapter) (*UDPListener, error)
func handlePacketConn(pc net.PacketConn, in chan<- *inbound.PacketAdapter, buf []byte, lAddr *net.UDPAddr, rAddr *net.UDPAddr) {
target := socks5.ParseAddrToSocksAddr(rAddr)
pkt := &packet{
pc: pc,
lAddr: lAddr,
buf: buf,
}

68
listener/tunnel/tcp.go Normal file
View File

@ -0,0 +1,68 @@
package tunnel
import (
"net"
"github.com/Dreamacro/clash/adapter/inbound"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/transport/socks5"
)
type Listener struct {
closed bool
config string
listeners []net.Listener
}
func New(config string, in chan<- C.ConnContext) (*Listener, error) {
tl := &Listener{false, config, nil}
pl := PairList{}
err := pl.Set(config)
if err != nil {
return nil, err
}
for _, p := range pl {
addr := p[0]
target := p[1]
go func() {
tgt := socks5.ParseAddr(target)
if tgt == nil {
log.Errorln("invalid target address %q", target)
return
}
l, err := net.Listen("tcp", addr)
if err != nil {
return
}
tl.listeners = append(tl.listeners, l)
log.Infoln("TCP tunnel %s <-> %s", l.Addr().String(), target)
for {
c, err := l.Accept()
if err != nil {
if tl.closed {
break
}
continue
}
_ = c.(*net.TCPConn).SetKeepAlive(true)
in <- inbound.NewSocket(tgt, c, C.TCPTUN)
}
}()
}
return tl, nil
}
func (l *Listener) Close() {
l.closed = true
for _, lis := range l.listeners {
_ = lis.Close()
}
}
func (l *Listener) Config() string {
return l.config
}

79
listener/tunnel/udp.go Normal file
View File

@ -0,0 +1,79 @@
package tunnel
import (
"net"
"github.com/Dreamacro/clash/adapter/inbound"
"github.com/Dreamacro/clash/common/pool"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/transport/socks5"
)
type UdpListener struct {
closed bool
config string
listeners []net.PacketConn
}
func NewUdp(config string, in chan<- *inbound.PacketAdapter) (*UdpListener, error) {
ul := &UdpListener{false, config, nil}
pl := PairList{}
err := pl.Set(config)
if err != nil {
return nil, err
}
for _, p := range pl {
addr := p[0]
target := p[1]
go func() {
tgt := socks5.ParseAddr(target)
if tgt == nil {
log.Errorln("invalid target address %q", target)
return
}
l, err := net.ListenPacket("udp", addr)
if err != nil {
return
}
ul.listeners = append(ul.listeners, l)
log.Infoln("Udp tunnel %s <-> %s", l.LocalAddr().String(), target)
for {
buf := pool.Get(pool.RelayBufferSize)
n, remoteAddr, err := l.ReadFrom(buf)
if err != nil {
pool.Put(buf)
if ul.closed {
break
}
continue
}
packet := &packet{
pc: l,
rAddr: remoteAddr,
payload: buf[:n],
bufRef: buf,
}
select {
case in <- inbound.NewPacket(tgt, packet, C.UDPTUN):
default:
}
}
}()
}
return ul, nil
}
func (l *UdpListener) Close() {
l.closed = true
for _, lis := range l.listeners {
_ = lis.Close()
}
}
func (l *UdpListener) Config() string {
return l.config
}

63
listener/tunnel/utils.go Normal file
View File

@ -0,0 +1,63 @@
package tunnel
import (
"errors"
"net"
"strings"
"github.com/Dreamacro/clash/common/pool"
)
type PairList [][2]string // key1=val1,key2=val2,...
func (l PairList) String() string {
s := make([]string, len(l))
for i, pair := range l {
s[i] = pair[0] + "=" + pair[1]
}
return strings.Join(s, ",")
}
func (l *PairList) Set(s string) error {
for _, item := range strings.Split(s, ",") {
pair := strings.Split(item, "=")
if len(pair) != 2 {
return nil
}
*l = append(*l, [2]string{pair[0], pair[1]})
}
return nil
}
type packet struct {
pc net.PacketConn
rAddr net.Addr
payload []byte
bufRef []byte
}
func (c *packet) Data() []byte {
return c.payload
}
// WriteBack wirtes UDP packet with source(ip, port) = `addr`
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
if addr == nil {
err = errors.New("address is invalid")
return
}
packet := b
return c.pc.WriteTo(packet, c.rAddr)
}
// LocalAddr returns the source IP/Port of UDP Packet
func (c *packet) LocalAddr() net.Addr {
return c.rAddr
}
func (c *packet) Drop() {
pool.Put(c.bufRef)
}
func (c *packet) InAddr() net.Addr {
return c.pc.LocalAddr()
}

View File

@ -19,9 +19,6 @@ func (d *Domain) RuleType() C.RuleType {
}
func (d *Domain) Match(metadata *C.Metadata) (bool, string) {
if metadata.AddrType != C.AtypDomainName {
return false, ""
}
return metadata.Host == d.domain, d.adapter
}

View File

@ -19,9 +19,6 @@ func (dk *DomainKeyword) RuleType() C.RuleType {
}
func (dk *DomainKeyword) Match(metadata *C.Metadata) (bool, string) {
if metadata.AddrType != C.AtypDomainName {
return false, ""
}
domain := metadata.Host
return strings.Contains(domain, dk.keyword), dk.adapter
}

View File

@ -19,9 +19,6 @@ func (ds *DomainSuffix) RuleType() C.RuleType {
}
func (ds *DomainSuffix) Match(metadata *C.Metadata) (bool, string) {
if metadata.AddrType != C.AtypDomainName {
return false, ""
}
domain := metadata.Host
return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix, ds.adapter
}

View File

@ -24,10 +24,6 @@ func (gs *GEOSITE) RuleType() C.RuleType {
}
func (gs *GEOSITE) Match(metadata *C.Metadata) (bool, string) {
if metadata.AddrType != C.AtypDomainName {
return false, ""
}
domain := metadata.Host
return gs.matcher.ApplyDomain(domain), gs.adapter
}

View File

@ -13,22 +13,23 @@ type Port struct {
*Base
adapter string
port string
isSource bool
ruleType C.RuleType
portList []utils.Range[uint16]
}
func (p *Port) RuleType() C.RuleType {
if p.isSource {
return C.SrcPort
}
return C.DstPort
return p.ruleType
}
func (p *Port) Match(metadata *C.Metadata) (bool, string) {
if p.isSource {
return p.matchPortReal(metadata.SrcPort), p.adapter
targetPort := metadata.DstPort
switch p.ruleType {
case C.InPort:
targetPort = metadata.InPort
case C.SrcPort:
targetPort = metadata.SrcPort
}
return p.matchPortReal(metadata.DstPort), p.adapter
return p.matchPortReal(targetPort), p.adapter
}
func (p *Port) Adapter() string {
@ -51,7 +52,7 @@ func (p *Port) matchPortReal(portRef string) bool {
return false
}
func NewPort(port string, adapter string, isSource bool) (*Port, error) {
func NewPort(port string, adapter string, ruleType C.RuleType) (*Port, error) {
ports := strings.Split(port, "/")
if len(ports) > 28 {
return nil, fmt.Errorf("%s, too many ports to use, maximum support 28 ports", errPayload.Error())
@ -95,7 +96,7 @@ func NewPort(port string, adapter string, isSource bool) (*Port, error) {
Base: &Base{},
adapter: adapter,
port: port,
isSource: isSource,
ruleType: ruleType,
portList: portRange,
}, nil
}

View File

@ -33,9 +33,11 @@ func ParseRule(tp, payload, target string, params []string, subRules *map[string
case "SRC-IP-SUFFIX":
parsed, parseErr = RC.NewIPSuffix(payload, target, true, true)
case "SRC-PORT":
parsed, parseErr = RC.NewPort(payload, target, true)
parsed, parseErr = RC.NewPort(payload, target, C.SrcPort)
case "DST-PORT":
parsed, parseErr = RC.NewPort(payload, target, false)
parsed, parseErr = RC.NewPort(payload, target, C.DstPort)
case "IN-PORT":
parsed, parseErr = RC.NewPort(payload, target, C.InPort)
case "PROCESS-NAME":
parsed, parseErr = RC.NewProcess(payload, target, true)
case "PROCESS-PATH":
@ -73,10 +75,9 @@ func TestAND(t *testing.T) {
assert.Equal(t, "DIRECT", and.adapter)
assert.Equal(t, false, and.ShouldResolveIP())
m, _ := and.Match(&C.Metadata{
Host: "baidu.com",
AddrType: C.AtypDomainName,
NetWork: C.TCP,
DstPort: "20000",
Host: "baidu.com",
NetWork: C.TCP,
DstPort: "20000",
})
assert.Equal(t, true, m)

View File

@ -32,9 +32,11 @@ func ParseRule(tp, payload, target string, params []string, subRules *map[string
case "SRC-IP-SUFFIX":
parsed, parseErr = RC.NewIPSuffix(payload, target, true, true)
case "SRC-PORT":
parsed, parseErr = RC.NewPort(payload, target, true)
parsed, parseErr = RC.NewPort(payload, target, C.SrcPort)
case "DST-PORT":
parsed, parseErr = RC.NewPort(payload, target, false)
parsed, parseErr = RC.NewPort(payload, target, C.DstPort)
case "IN-PORT":
parsed, parseErr = RC.NewPort(payload, target, C.InPort)
case "PROCESS-NAME":
parsed, parseErr = RC.NewProcess(payload, target, true)
case "PROCESS-PATH":

View File

@ -9,7 +9,7 @@ import (
type domainStrategy struct {
count int
domainRules *trie.DomainTrie[bool]
domainRules *trie.DomainTrie[struct{}]
}
func (d *domainStrategy) Match(metadata *C.Metadata) bool {
@ -25,11 +25,11 @@ func (d *domainStrategy) ShouldResolveIP() bool {
}
func (d *domainStrategy) OnUpdate(rules []string) {
domainTrie := trie.New[bool]()
domainTrie := trie.New[struct{}]()
count := 0
for _, rule := range rules {
actualDomain, _ := idna.ToASCII(rule)
err := domainTrie.Insert(actualDomain, true)
err := domainTrie.Insert(actualDomain, struct{}{})
if err != nil {
log.Warnln("invalid domain:[%s]", rule)
} else {

View File

@ -555,9 +555,8 @@ func testPacketConnTimeout(t *testing.T, pc net.PacketConn) error {
func testSuit(t *testing.T, proxy C.ProxyAdapter) {
assert.NoError(t, testPingPongWithConn(t, func() net.Conn {
conn, err := proxy.DialContext(context.Background(), &C.Metadata{
Host: localIP.String(),
DstPort: "10001",
AddrType: socks5.AtypDomainName,
Host: localIP.String(),
DstPort: "10001",
})
require.NoError(t, err)
return conn
@ -565,9 +564,8 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
assert.NoError(t, testLargeDataWithConn(t, func() net.Conn {
conn, err := proxy.DialContext(context.Background(), &C.Metadata{
Host: localIP.String(),
DstPort: "10001",
AddrType: socks5.AtypDomainName,
Host: localIP.String(),
DstPort: "10001",
})
require.NoError(t, err)
return conn
@ -578,10 +576,9 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
}
pc, err := proxy.ListenPacketContext(context.Background(), &C.Metadata{
NetWork: C.UDP,
DstIP: localIP,
DstPort: "10001",
AddrType: socks5.AtypIPv4,
NetWork: C.UDP,
DstIP: localIP,
DstPort: "10001",
})
require.NoError(t, err)
defer pc.Close()
@ -589,10 +586,9 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
assert.NoError(t, testPingPongWithPacketConn(t, pc))
pc, err = proxy.ListenPacketContext(context.Background(), &C.Metadata{
NetWork: C.UDP,
DstIP: localIP,
DstPort: "10001",
AddrType: socks5.AtypIPv4,
NetWork: C.UDP,
DstIP: localIP,
DstPort: "10001",
})
require.NoError(t, err)
defer pc.Close()
@ -600,10 +596,9 @@ func testSuit(t *testing.T, proxy C.ProxyAdapter) {
assert.NoError(t, testLargeDataWithPacketConn(t, pc))
pc, err = proxy.ListenPacketContext(context.Background(), &C.Metadata{
NetWork: C.UDP,
DstIP: localIP,
DstPort: "10001",
AddrType: socks5.AtypIPv4,
NetWork: C.UDP,
DstIP: localIP,
DstPort: "10001",
})
require.NoError(t, err)
defer pc.Close()
@ -639,9 +634,8 @@ func benchmarkProxy(b *testing.B, proxy C.ProxyAdapter) {
}()
conn, err := proxy.DialContext(context.Background(), &C.Metadata{
Host: localIP.String(),
DstPort: "10001",
AddrType: socks5.AtypDomainName,
Host: localIP.String(),
DstPort: "10001",
})
require.NoError(b, err)

View File

@ -31,7 +31,7 @@ var (
proxies = make(map[string]C.Proxy)
providers map[string]provider.ProxyProvider
ruleProviders map[string]provider.RuleProvider
sniffingEnable bool
sniffingEnable = false
configMux sync.RWMutex
// Outbound Rule
@ -41,8 +41,18 @@ var (
udpTimeout = 60 * time.Second
alwaysFindProcess = false
fakeIPRange netip.Prefix
)
func SetFakeIPRange(p netip.Prefix) {
fakeIPRange = p
}
func FakeIPRange() netip.Prefix {
return fakeIPRange
}
func SetSniffing(b bool) {
if sniffer.Dispatcher.Enable() {
configMux.Lock()
@ -108,7 +118,7 @@ func UpdateProxies(newProxies map[string]C.Proxy, newProviders map[string]provid
func UpdateSniffer(dispatcher *sniffer.SnifferDispatcher) {
configMux.Lock()
sniffer.Dispatcher = dispatcher
sniffingEnable = true
sniffingEnable = dispatcher.Enable()
configMux.Unlock()
}
@ -159,11 +169,6 @@ func preHandleMetadata(metadata *C.Metadata) error {
if ip, err := netip.ParseAddr(metadata.Host); err == nil {
metadata.DstIP = ip
metadata.Host = ""
if ip.Is4() {
metadata.AddrType = C.AtypIPv4
} else {
metadata.AddrType = C.AtypIPv6
}
}
// preprocess enhanced-mode metadata
@ -171,14 +176,13 @@ func preHandleMetadata(metadata *C.Metadata) error {
host, exist := resolver.FindHostByIP(metadata.DstIP)
if exist {
metadata.Host = host
metadata.AddrType = C.AtypDomainName
metadata.DNSMode = C.DNSMapping
if resolver.FakeIPEnabled() {
metadata.DstIP = netip.Addr{}
metadata.DNSMode = C.DNSFakeIP
} else if node := resolver.DefaultHosts.Search(host); node != nil {
// redir-host should lookup the hosts
metadata.DstIP = node.Data
metadata.DstIP = node.Data()
}
} else if resolver.IsFakeIP(metadata.DstIP) {
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
@ -334,9 +338,11 @@ func handleTCPConn(connCtx C.ConnContext) {
dialMetadata := metadata
if len(metadata.Host) > 0 {
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
dialMetadata.DstIP = node.Data
dialMetadata.DNSMode = C.DNSHosts
dialMetadata = dialMetadata.Pure()
if dstIp := node.Data(); !FakeIPRange().Contains(dstIp) {
dialMetadata.DstIP = dstIp
dialMetadata.DNSMode = C.DNSHosts
dialMetadata = dialMetadata.Pure()
}
}
}
@ -388,7 +394,7 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
)
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
metadata.DstIP = node.Data
metadata.DstIP = node.Data()
resolved = true
}