Compare commits

..

10 Commits

Author SHA1 Message Date
8ee452f544 chore: format code 2023-03-12 14:58:27 +08:00
d0b95e91f5 chore: try use domain mapping when normal dns 2023-03-12 14:50:21 +08:00
6cee7213c4 chore: use cname in tunnel 2023-03-12 14:44:24 +08:00
c555cec490 chore: reuse code 2023-03-12 14:37:46 +08:00
a510909228 chore: adjust logic 2023-03-12 14:14:38 +08:00
0273a6bb9d fix: flatten cname 2023-03-12 12:41:32 +08:00
4b1e00d3aa chore: unified parse mixed string and array 2023-03-12 12:08:16 +08:00
17fea4b459 chore: update hosts demo 2023-03-12 11:40:05 +08:00
5ecdefcfb5 chore: append local address via clash 2023-03-12 11:27:17 +08:00
2f69b64d82 feat: host support domain and multiple ips 2023-03-12 10:53:38 +08:00
163 changed files with 5925 additions and 4453 deletions

View File

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

View File

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

View File

@ -1 +0,0 @@
git log --pretty=format:"* %s by @%an" v1.14.x..v1.14.y | sort -f | uniq > release.md

View File

@ -15,15 +15,6 @@ do
elif [[ $FILENAME =~ "windows-4.0-amd64" ]];then elif [[ $FILENAME =~ "windows-4.0-amd64" ]];then
echo "rename windows amd64 $FILENAME" echo "rename windows amd64 $FILENAME"
mv $FILENAME clash.meta-windows-amd64-cgo.exe mv $FILENAME clash.meta-windows-amd64-cgo.exe
elif [[ $FILENAME =~ "clash.meta-linux-arm-5" ]];then
echo "rename clash.meta-linux-arm-5 $FILENAME"
mv $FILENAME clash.meta-linux-armv5-cgo
elif [[ $FILENAME =~ "clash.meta-linux-arm-6" ]];then
echo "rename clash.meta-linux-arm-6 $FILENAME"
mv $FILENAME clash.meta-linux-armv6-cgo
elif [[ $FILENAME =~ "clash.meta-linux-arm-7" ]];then
echo "rename clash.meta-linux-arm-7 $FILENAME"
mv $FILENAME clash.meta-linux-armv7-cgo
elif [[ $FILENAME =~ "linux" ]];then elif [[ $FILENAME =~ "linux" ]];then
echo "rename linux $FILENAME" echo "rename linux $FILENAME"
mv $FILENAME $FILENAME-cgo mv $FILENAME $FILENAME-cgo

View File

@ -5,14 +5,17 @@ on:
paths-ignore: paths-ignore:
- "docs/**" - "docs/**"
- "README.md" - "README.md"
- ".github/ISSUE_TEMPLATE/**"
branches: branches:
- Alpha - Alpha
- Beta
- Meta
tags: tags:
- "v*" - "v*"
pull_request_target: pull_request_target:
branches: branches:
- Alpha - Alpha
- Beta
- Meta
concurrency: concurrency:
group: ${{ github.ref }}-${{ github.workflow }} group: ${{ github.ref }}-${{ github.workflow }}

View File

@ -1,15 +0,0 @@
name: Delete old workflow runs
on:
schedule:
- cron: "0 0 * * SUN"
jobs:
del_runs:
runs-on: ubuntu-latest
steps:
- name: Delete workflow runs
uses: GitRML/delete-workflow-runs@main
with:
token: ${{ secrets.AUTH_PAT }}
repository: ${{ github.repository }}
retain_days: 30

View File

@ -30,7 +30,8 @@
- Comprehensive HTTP RESTful API controller - Comprehensive HTTP RESTful API controller
## Wiki ## Wiki
Configuration examples can be found at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml), while documentation can be found [Clash.Meta Wiki](https://clash-meta.wiki).
Documentation and configuring examples are available on [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki) and [Clash.Meta Wiki](https://docs.metacubex.one/).
## Build ## Build

View File

@ -4,16 +4,16 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/Dreamacro/clash/common/queue"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
"net" "net"
"net/http" "net/http"
"net/netip" "net/netip"
"net/url" "net/url"
"time" "time"
"github.com/Dreamacro/clash/common/atomic" "go.uber.org/atomic"
"github.com/Dreamacro/clash/common/queue"
"github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant"
) )
var UnifiedDelay = atomic.NewBool(false) var UnifiedDelay = atomic.NewBool(false)

View File

@ -42,8 +42,6 @@ func NewInner(conn net.Conn, dst string, host string) *context.ConnContext {
if host == "" { if host == "" {
if ip, err := netip.ParseAddr(h); err == nil { if ip, err := netip.ParseAddr(h); err == nil {
metadata.DstIP = ip metadata.DstIP = ip
} else {
metadata.Host = h
} }
} }
} }

View File

@ -3,9 +3,9 @@ package outbound
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"net" "net"
"strings" "strings"
"syscall"
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/common/utils"
@ -34,7 +34,12 @@ func (b *Base) Name() string {
// Id implements C.ProxyAdapter // Id implements C.ProxyAdapter
func (b *Base) Id() string { func (b *Base) Id() string {
if b.id == "" { if b.id == "" {
b.id = utils.NewUUIDV6().String() id, err := utils.UnsafeUUIDGenerator.NewV6()
if err != nil {
b.id = b.name
} else {
b.id = id.String()
}
} }
return b.id return b.id
@ -47,31 +52,31 @@ func (b *Base) Type() C.AdapterType {
// StreamConn implements C.ProxyAdapter // StreamConn implements C.ProxyAdapter
func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (b *Base) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return c, C.ErrNotSupport return c, errors.New("no support")
} }
func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
return nil, C.ErrNotSupport return nil, errors.New("no support")
} }
// DialContextWithDialer implements C.ProxyAdapter // DialContextWithDialer implements C.ProxyAdapter
func (b *Base) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) { func (b *Base) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
return nil, C.ErrNotSupport return nil, errors.New("no support")
} }
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
return nil, C.ErrNotSupport return nil, errors.New("no support")
} }
// ListenPacketWithDialer implements C.ProxyAdapter // ListenPacketWithDialer implements C.ProxyAdapter
func (b *Base) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) { func (b *Base) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
return nil, C.ErrNotSupport return nil, errors.New("no support")
} }
// SupportWithDialer implements C.ProxyAdapter // SupportWithDialer implements C.ProxyAdapter
func (b *Base) SupportWithDialer() C.NetWork { func (b *Base) SupportWithDialer() bool {
return C.InvalidNet return false
} }
// SupportUOT implements C.ProxyAdapter // SupportUOT implements C.ProxyAdapter
@ -94,11 +99,6 @@ func (b *Base) SupportTFO() bool {
return b.tfo return b.tfo
} }
// IsL3Protocol implements C.ProxyAdapter
func (b *Base) IsL3Protocol(metadata *C.Metadata) bool {
return false
}
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (b *Base) MarshalJSON() ([]byte, error) { func (b *Base) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]string{ return json.Marshal(map[string]string{
@ -151,7 +151,6 @@ type BasicOption struct {
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"` Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"` RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"` IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"`
DialerProxy string `proxy:"dialer-proxy,omitempty"` // don't apply this option into groups, but can set a group name in a proxy
} }
type BaseOption struct { type BaseOption struct {
@ -204,26 +203,13 @@ func (c *conn) Upstream() any {
return c.ExtendedConn return c.ExtendedConn
} }
func (c *conn) WriterReplaceable() bool {
return true
}
func (c *conn) ReaderReplaceable() bool {
return true
}
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn { func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
if _, ok := c.(syscall.Conn); !ok { // exclusion system conn like *net.TCPConn
c = N.NewDeadlineConn(c) // most conn from outbound can't handle readDeadline correctly
}
return &conn{N.NewExtendedConn(c), []string{a.Name()}, parseRemoteDestination(a.Addr())} return &conn{N.NewExtendedConn(c), []string{a.Name()}, parseRemoteDestination(a.Addr())}
} }
type packetConn struct { type packetConn struct {
net.PacketConn net.PacketConn
chain C.Chain chain C.Chain
adapterName string
connID string
actualRemoteDestination string actualRemoteDestination string
} }
@ -241,16 +227,8 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name()) c.chain = append(c.chain, a.Name())
} }
func (c *packetConn) LocalAddr() net.Addr {
lAddr := c.PacketConn.LocalAddr()
return N.NewCustomAddr(c.adapterName, c.connID, lAddr) // make quic-go's connMultiplexer happy
}
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn { func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
if _, ok := pc.(syscall.Conn); !ok { // exclusion system conn like *net.UDPConn return &packetConn{pc, []string{a.Name()}, parseRemoteDestination(a.Addr())}
pc = N.NewDeadlinePacketConn(pc) // most conn from outbound can't handle readDeadline correctly
}
return &packetConn{pc, []string{a.Name()}, a.Name(), utils.NewUUIDV4().String(), parseRemoteDestination(a.Addr())}
} }
func parseRemoteDestination(addr string) string { func parseRemoteDestination(addr string) string {

View File

@ -2,7 +2,6 @@ package outbound
import ( import (
"context" "context"
"errors"
"net" "net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
@ -27,14 +26,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
if !metadata.Resolved() {
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DefaultResolver)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", metadata.DstIP), "", d.Base.DialOptions(opts...)...) pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", metadata.DstIP), "", d.Base.DialOptions(opts...)...)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -14,7 +14,6 @@ import (
"strconv" "strconv"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
@ -67,12 +66,6 @@ func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...di
// DialContextWithDialer implements C.ProxyAdapter // DialContextWithDialer implements C.ProxyAdapter
func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) { func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(h.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(h.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", h.addr) c, err := dialer.DialContext(ctx, "tcp", h.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", h.addr, err) return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
@ -92,8 +85,8 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
} }
// SupportWithDialer implements C.ProxyAdapter // SupportWithDialer implements C.ProxyAdapter
func (h *Http) SupportWithDialer() C.NetWork { func (h *Http) SupportWithDialer() bool {
return C.TCP return true
} }
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {

View File

@ -20,7 +20,6 @@ import (
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
@ -29,7 +28,6 @@ import (
"github.com/Dreamacro/clash/transport/hysteria/obfs" "github.com/Dreamacro/clash/transport/hysteria/obfs"
"github.com/Dreamacro/clash/transport/hysteria/pmtud_fix" "github.com/Dreamacro/clash/transport/hysteria/pmtud_fix"
"github.com/Dreamacro/clash/transport/hysteria/transport" "github.com/Dreamacro/clash/transport/hysteria/transport"
"github.com/Dreamacro/clash/transport/hysteria/utils"
) )
const ( const (
@ -48,12 +46,21 @@ var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`)
type Hysteria struct { type Hysteria struct {
*Base *Base
option *HysteriaOption
client *core.Client client *core.Client
} }
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), h.genHdc(ctx, opts...)) hdc := hyDialerWithContext{
ctx: context.Background(),
hyDialer: func(network string) (net.PacketConn, error) {
return dialer.ListenPacket(ctx, network, "", h.Base.DialOptions(opts...)...)
},
remoteAddr: func(addr string) (net.Addr, error) {
return resolveUDPAddrWithPrefer(ctx, "udp", addr, h.prefer)
},
}
tcpConn, err := h.client.DialTCP(metadata.RemoteAddress(), &hdc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -62,32 +69,20 @@ func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts .
} }
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
udpConn, err := h.client.DialUDP(h.genHdc(ctx, opts...)) hdc := hyDialerWithContext{
if err != nil {
return nil, err
}
return newPacketConn(&hyPacketConn{udpConn}, h), nil
}
func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
return &hyDialerWithContext{
ctx: context.Background(), ctx: context.Background(),
hyDialer: func(network string) (net.PacketConn, error) { hyDialer: func(network string) (net.PacketConn, error) {
var err error return dialer.ListenPacket(ctx, network, "", h.Base.DialOptions(opts...)...)
var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...)
if len(h.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(h.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
rAddrPort, _ := netip.ParseAddrPort(h.Addr())
return cDialer.ListenPacket(ctx, network, "", rAddrPort)
}, },
remoteAddr: func(addr string) (net.Addr, error) { remoteAddr: func(addr string) (net.Addr, error) {
return resolveUDPAddrWithPrefer(ctx, "udp", addr, h.prefer) return resolveUDPAddrWithPrefer(ctx, "udp", addr, h.prefer)
}, },
} }
udpConn, err := h.client.DialUDP(&hdc)
if err != nil {
return nil, err
}
return newPacketConn(&hyPacketConn{udpConn}, h), nil
} }
type HysteriaOption struct { type HysteriaOption struct {
@ -263,7 +258,6 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
}, },
option: &option,
client: client, client: client,
}, nil }, nil
} }

View File

@ -11,16 +11,12 @@ import (
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/restls"
obfs "github.com/Dreamacro/clash/transport/simple-obfs" obfs "github.com/Dreamacro/clash/transport/simple-obfs"
shadowtls "github.com/Dreamacro/clash/transport/sing-shadowtls" shadowtls "github.com/Dreamacro/clash/transport/sing-shadowtls"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin" v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
restlsC "github.com/3andne/restls-client-go"
shadowsocks "github.com/metacubex/sing-shadowsocks" shadowsocks "github.com/metacubex/sing-shadowsocks"
"github.com/metacubex/sing-shadowsocks/shadowimpl" "github.com/metacubex/sing-shadowsocks/shadowimpl"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
@ -38,22 +34,19 @@ type ShadowSocks struct {
obfsOption *simpleObfsOption obfsOption *simpleObfsOption
v2rayOption *v2rayObfs.Option v2rayOption *v2rayObfs.Option
shadowTLSOption *shadowtls.ShadowTLSOption shadowTLSOption *shadowtls.ShadowTLSOption
restlsConfig *restlsC.Config
} }
type ShadowSocksOption struct { type ShadowSocksOption struct {
BasicOption BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
Password string `proxy:"password"` Password string `proxy:"password"`
Cipher string `proxy:"cipher"` Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
Plugin string `proxy:"plugin,omitempty"` Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]any `proxy:"plugin-opts,omitempty"` PluginOpts map[string]any `proxy:"plugin-opts,omitempty"`
UDPOverTCP bool `proxy:"udp-over-tcp,omitempty"` UDPOverTCP bool `proxy:"udp-over-tcp,omitempty"`
UDPOverTCPVersion int `proxy:"udp-over-tcp-version,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
} }
type simpleObfsOption struct { type simpleObfsOption struct {
@ -73,30 +66,31 @@ type v2rayObfsOption struct {
} }
type shadowTLSOption struct { type shadowTLSOption struct {
Password string `obfs:"password"` Password string `obfs:"password"`
Host string `obfs:"host"` Host string `obfs:"host"`
Fingerprint string `obfs:"fingerprint,omitempty"` Fingerprint string `obfs:"fingerprint,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` ClientFingerprint string `obfs:"client-fingerprint,omitempty"`
Version int `obfs:"version,omitempty"` SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
} Version int `obfs:"version,omitempty"`
type restlsOption struct {
Password string `obfs:"password"`
Host string `obfs:"host"`
VersionHint string `obfs:"version-hint"`
RestlsScript string `obfs:"restls-script,omitempty"`
} }
// StreamConn implements C.ProxyAdapter // StreamConn implements C.ProxyAdapter
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
// fix tls handshake not timeout switch ss.obfsMode {
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) case shadowtls.Mode:
defer cancel() // fix tls handshake not timeout
return ss.StreamConnContext(ctx, c, metadata) ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
defer cancel()
var err error
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
if err != nil {
return nil, err
}
}
return ss.streamConn(c, metadata)
} }
func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (ss *ShadowSocks) streamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
useEarly := false
switch ss.obfsMode { switch ss.obfsMode {
case "tls": case "tls":
c = obfs.NewTLSObfs(c, ss.obfsOption.Host) c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
@ -109,31 +103,15 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
case shadowtls.Mode:
var err error
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
if err != nil {
return nil, err
}
useEarly = true
case restls.Mode:
var err error
c, err = restls.NewRestls(ctx, c, ss.restlsConfig)
if err != nil {
return nil, fmt.Errorf("%s (restls) connect error: %w", ss.addr, err)
}
useEarly = true
} }
useEarly = useEarly || N.NeedHandshake(c)
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP { if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
uotDestination := uot.RequestDestination(uint8(ss.option.UDPOverTCPVersion)) if N.NeedHandshake(c) {
if useEarly { return ss.method.DialEarlyConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443")), nil
return ss.method.DialEarlyConn(c, uotDestination), nil
} else { } else {
return ss.method.DialConn(c, uotDestination) return ss.method.DialConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443"))
} }
} }
if useEarly { if N.NeedHandshake(c) {
return ss.method.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil return ss.method.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
} else { } else {
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
@ -147,12 +125,6 @@ func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, op
// DialContextWithDialer implements C.ProxyAdapter // DialContextWithDialer implements C.ProxyAdapter
func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) { func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(ss.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", ss.addr) c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
@ -163,7 +135,15 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
c, err = ss.StreamConnContext(ctx, c, metadata) switch ss.obfsMode {
case shadowtls.Mode:
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
if err != nil {
return nil, err
}
}
c, err = ss.streamConn(c, metadata)
return NewConn(c, ss), err return NewConn(c, ss), err
} }
@ -174,18 +154,12 @@ func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Meta
// ListenPacketWithDialer implements C.ProxyAdapter // ListenPacketWithDialer implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) { func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if len(ss.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
if ss.option.UDPOverTCP { if ss.option.UDPOverTCP {
tcpConn, err := ss.DialContextWithDialer(ctx, dialer, metadata) tcpConn, err := ss.DialContextWithDialer(ctx, dialer, metadata)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return ss.ListenPacketOnStreamConn(ctx, tcpConn, metadata) return newPacketConn(uot.NewClientConn(tcpConn), ss), nil
} }
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer) addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer)
if err != nil { if err != nil {
@ -196,35 +170,21 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
if err != nil { if err != nil {
return nil, err return nil, err
} }
pc = ss.method.DialPacketConn(bufio.NewBindPacketConn(pc, addr)) pc = ss.method.DialPacketConn(&bufio.BindPacketConn{PacketConn: pc, Addr: addr})
return newPacketConn(pc, ss), nil return newPacketConn(pc, ss), nil
} }
// SupportWithDialer implements C.ProxyAdapter // SupportWithDialer implements C.ProxyAdapter
func (ss *ShadowSocks) SupportWithDialer() C.NetWork { func (ss *ShadowSocks) SupportWithDialer() bool {
return C.ALLNet return true
} }
// ListenPacketOnStreamConn implements C.ProxyAdapter // ListenPacketOnStreamConn implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { func (ss *ShadowSocks) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
if ss.option.UDPOverTCP { if ss.option.UDPOverTCP {
// ss uot use stream-oriented udp with a special address, so we need a net.UDPAddr return newPacketConn(uot.NewClientConn(c), ss), nil
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
destination := M.SocksaddrFromNet(metadata.UDPAddr())
if ss.option.UDPOverTCPVersion == uot.LegacyVersion {
return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), ss), nil
} else {
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), ss), nil
}
} }
return nil, C.ErrNotSupport return nil, errors.New("no support")
} }
// SupportUOT implements C.ProxyAdapter // SupportUOT implements C.ProxyAdapter
@ -242,7 +202,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
var v2rayOption *v2rayObfs.Option var v2rayOption *v2rayObfs.Option
var obfsOption *simpleObfsOption var obfsOption *simpleObfsOption
var shadowTLSOpt *shadowtls.ShadowTLSOption var shadowTLSOpt *shadowtls.ShadowTLSOption
var restlsConfig *restlsC.Config
obfsMode := "" obfsMode := ""
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
@ -291,30 +250,10 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
Password: opt.Password, Password: opt.Password,
Host: opt.Host, Host: opt.Host,
Fingerprint: opt.Fingerprint, Fingerprint: opt.Fingerprint,
ClientFingerprint: option.ClientFingerprint, ClientFingerprint: opt.ClientFingerprint,
SkipCertVerify: opt.SkipCertVerify, SkipCertVerify: opt.SkipCertVerify,
Version: opt.Version, Version: opt.Version,
} }
} else if option.Plugin == restls.Mode {
obfsMode = restls.Mode
restlsOpt := &restlsOption{}
if err := decoder.Decode(option.PluginOpts, restlsOpt); err != nil {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
}
restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
restlsConfig.SessionTicketsDisabled = true
if err != nil {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
}
}
switch option.UDPOverTCPVersion {
case uot.Version, uot.LegacyVersion:
case 0:
option.UDPOverTCPVersion = uot.Version
default:
return nil, fmt.Errorf("ss %s unknown udp over tcp protocol version: %d", addr, option.UDPOverTCPVersion)
} }
return &ShadowSocks{ return &ShadowSocks{
@ -335,7 +274,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
v2rayOption: v2rayOption, v2rayOption: v2rayOption,
obfsOption: obfsOption, obfsOption: obfsOption,
shadowTLSOption: shadowTLSOpt, shadowTLSOption: shadowTLSOpt,
restlsConfig: restlsConfig,
}, nil }, nil
} }

View File

@ -7,7 +7,6 @@ import (
"strconv" "strconv"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/shadowsocks/core" "github.com/Dreamacro/clash/transport/shadowsocks/core"
"github.com/Dreamacro/clash/transport/shadowsocks/shadowaead" "github.com/Dreamacro/clash/transport/shadowsocks/shadowaead"
@ -18,7 +17,6 @@ import (
type ShadowSocksR struct { type ShadowSocksR struct {
*Base *Base
option *ShadowSocksROption
cipher core.Cipher cipher core.Cipher
obfs obfs.Obfs obfs obfs.Obfs
protocol protocol.Protocol protocol protocol.Protocol
@ -67,12 +65,6 @@ func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata,
// DialContextWithDialer implements C.ProxyAdapter // DialContextWithDialer implements C.ProxyAdapter
func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) { func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(ssr.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(ssr.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", ssr.addr) c, err := dialer.DialContext(ctx, "tcp", ssr.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err) return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
@ -94,12 +86,6 @@ func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Me
// ListenPacketWithDialer implements C.ProxyAdapter // ListenPacketWithDialer implements C.ProxyAdapter
func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) { func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if len(ssr.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(ssr.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ssr.addr, ssr.prefer) addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ssr.addr, ssr.prefer)
if err != nil { if err != nil {
return nil, err return nil, err
@ -116,8 +102,8 @@ func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Di
} }
// SupportWithDialer implements C.ProxyAdapter // SupportWithDialer implements C.ProxyAdapter
func (ssr *ShadowSocksR) SupportWithDialer() C.NetWork { func (ssr *ShadowSocksR) SupportWithDialer() bool {
return C.ALLNet return true
} }
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) { func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
@ -182,7 +168,6 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
}, },
option: &option,
cipher: coreCiph, cipher: coreCiph,
obfs: obfs, obfs: obfs,
protocol: protocol, protocol: protocol,

View File

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

View File

@ -8,7 +8,6 @@ import (
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
obfs "github.com/Dreamacro/clash/transport/simple-obfs" obfs "github.com/Dreamacro/clash/transport/simple-obfs"
"github.com/Dreamacro/clash/transport/snell" "github.com/Dreamacro/clash/transport/snell"
@ -16,7 +15,6 @@ import (
type Snell struct { type Snell struct {
*Base *Base
option *SnellOption
psk []byte psk []byte
pool *snell.Pool pool *snell.Pool
obfsOption *simpleObfsOption obfsOption *simpleObfsOption
@ -85,12 +83,6 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
// DialContextWithDialer implements C.ProxyAdapter // DialContextWithDialer implements C.ProxyAdapter
func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) { func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(s.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(s.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", s.addr) c, err := dialer.DialContext(ctx, "tcp", s.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", s.addr, err) return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
@ -112,13 +104,6 @@ func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
// ListenPacketWithDialer implements C.ProxyAdapter // ListenPacketWithDialer implements C.ProxyAdapter
func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.PacketConn, error) { func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.PacketConn, error) {
var err error
if len(s.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(s.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", s.addr) c, err := dialer.DialContext(ctx, "tcp", s.addr)
if err != nil { if err != nil {
return nil, err return nil, err
@ -136,8 +121,8 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
} }
// SupportWithDialer implements C.ProxyAdapter // SupportWithDialer implements C.ProxyAdapter
func (s *Snell) SupportWithDialer() C.NetWork { func (s *Snell) SupportWithDialer() bool {
return C.ALLNet return true
} }
// SupportUOT implements C.ProxyAdapter // SupportUOT implements C.ProxyAdapter
@ -187,7 +172,6 @@ func NewSnell(option SnellOption) (*Snell, error) {
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
}, },
option: &option,
psk: psk, psk: psk,
obfsOption: obfsOption, obfsOption: obfsOption,
version: option.Version, version: option.Version,
@ -195,15 +179,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
if option.Version == snell.Version2 { if option.Version == snell.Version2 {
s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) { s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) {
var err error c, err := dialer.DialContext(ctx, "tcp", addr, s.Base.DialOptions()...)
var cDialer C.Dialer = dialer.NewDialer(s.Base.DialOptions()...)
if len(s.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(s.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(ctx, "tcp", addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -10,7 +10,6 @@ import (
"strconv" "strconv"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
@ -18,7 +17,6 @@ import (
type Socks5 struct { type Socks5 struct {
*Base *Base
option *Socks5Option
user string user string
pass string pass string
tls bool tls bool
@ -72,12 +70,6 @@ func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
// DialContextWithDialer implements C.ProxyAdapter // DialContextWithDialer implements C.ProxyAdapter
func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) { func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(ss.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", ss.addr) c, err := dialer.DialContext(ctx, "tcp", ss.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
@ -97,20 +89,13 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
} }
// SupportWithDialer implements C.ProxyAdapter // SupportWithDialer implements C.ProxyAdapter
func (ss *Socks5) SupportWithDialer() C.NetWork { func (ss *Socks5) SupportWithDialer() bool {
return C.TCP return true
} }
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
var cDialer C.Dialer = dialer.NewDialer(ss.Base.DialOptions(opts...)...) c, err := dialer.DialContext(ctx, "tcp", ss.addr, ss.Base.DialOptions(opts...)...)
if len(ss.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(ss.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(ctx, "tcp", ss.addr)
if err != nil { if err != nil {
err = fmt.Errorf("%s connect error: %w", ss.addr, err) err = fmt.Errorf("%s connect error: %w", ss.addr, err)
return return
@ -202,7 +187,6 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
}, },
option: &option,
user: option.UserName, user: option.UserName,
pass: option.Password, pass: option.Password,
tls: option.TLS, tls: option.TLS,

View File

@ -8,8 +8,8 @@ import (
"net/http" "net/http"
"strconv" "strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/gun"
@ -105,7 +105,7 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
return c, err return c, err
} }
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)) err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
return c, err return N.NewExtendedConn(c), err
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@ -135,12 +135,6 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
// DialContextWithDialer implements C.ProxyAdapter // DialContextWithDialer implements C.ProxyAdapter
func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) { func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(t.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", t.addr) c, err := dialer.DialContext(ctx, "tcp", t.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
@ -185,12 +179,6 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
// ListenPacketWithDialer implements C.ProxyAdapter // ListenPacketWithDialer implements C.ProxyAdapter
func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) { func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if len(t.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", t.addr) c, err := dialer.DialContext(ctx, "tcp", t.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
@ -214,8 +202,8 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
} }
// SupportWithDialer implements C.ProxyAdapter // SupportWithDialer implements C.ProxyAdapter
func (t *Trojan) SupportWithDialer() C.NetWork { func (t *Trojan) SupportWithDialer() bool {
return C.ALLNet return true
} }
// ListenPacketOnStreamConn implements C.ProxyAdapter // ListenPacketOnStreamConn implements C.ProxyAdapter
@ -283,15 +271,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
if option.Network == "grpc" { if option.Network == "grpc" {
dialFn := func(network, addr string) (net.Conn, error) { dialFn := func(network, addr string) (net.Conn, error) {
var err error c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...)
var cDialer C.Dialer = dialer.NewDialer(t.Base.DialOptions()...)
if len(t.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(t.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(context.Background(), "tcp", t.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
} }

View File

@ -16,7 +16,6 @@ import (
"github.com/metacubex/quic-go" "github.com/metacubex/quic-go"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/tuic" "github.com/Dreamacro/clash/transport/tuic"
@ -24,7 +23,6 @@ import (
type Tuic struct { type Tuic struct {
*Base *Base
option *TuicOption
client *tuic.PoolClient client *tuic.PoolClient
} }
@ -44,17 +42,16 @@ type TuicOption struct {
DisableSni bool `proxy:"disable-sni,omitempty"` DisableSni bool `proxy:"disable-sni,omitempty"`
MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"` MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"`
FastOpen bool `proxy:"fast-open,omitempty"` FastOpen bool `proxy:"fast-open,omitempty"`
MaxOpenStreams int `proxy:"max-open-streams,omitempty"` MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
CustomCA string `proxy:"ca,omitempty"` CustomCA string `proxy:"ca,omitempty"`
CustomCAString string `proxy:"ca-str,omitempty"` CustomCAString string `proxy:"ca-str,omitempty"`
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"` ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
ReceiveWindow int `proxy:"recv-window,omitempty"` ReceiveWindow int `proxy:"recv-window,omitempty"`
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"` DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"` SNI string `proxy:"sni,omitempty"`
SNI string `proxy:"sni,omitempty"`
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@ -86,8 +83,8 @@ func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, meta
} }
// SupportWithDialer implements C.ProxyAdapter // SupportWithDialer implements C.ProxyAdapter
func (t *Tuic) SupportWithDialer() C.NetWork { func (t *Tuic) SupportWithDialer() bool {
return C.ALLNet return true
} }
func (t *Tuic) dial(ctx context.Context, opts ...dialer.Option) (pc net.PacketConn, addr net.Addr, err error) { func (t *Tuic) dial(ctx context.Context, opts ...dialer.Option) (pc net.PacketConn, addr net.Addr, err error) {
@ -95,12 +92,6 @@ func (t *Tuic) dial(ctx context.Context, opts ...dialer.Option) (pc net.PacketCo
} }
func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (pc net.PacketConn, addr net.Addr, err error) { func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (pc net.PacketConn, addr net.Addr, err error) {
if len(t.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
if err != nil {
return nil, nil, err
}
}
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", t.addr, t.prefer) udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", t.addr, t.prefer)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -184,15 +175,6 @@ func NewTuic(option TuicOption) (*Tuic, error) {
option.MaxOpenStreams = 100 option.MaxOpenStreams = 100
} }
if option.MaxDatagramFrameSize == 0 {
option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + tuic.PacketOverHead
}
if option.MaxDatagramFrameSize > 1400 {
option.MaxDatagramFrameSize = 1400
}
option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - tuic.PacketOverHead
// ensure server's incoming stream can handle correctly, increase to 1.1x // ensure server's incoming stream can handle correctly, increase to 1.1x
quicMaxOpenStreams := int64(option.MaxOpenStreams) quicMaxOpenStreams := int64(option.MaxOpenStreams)
quicMaxOpenStreams = quicMaxOpenStreams + int64(math.Ceil(float64(quicMaxOpenStreams)/10.0)) quicMaxOpenStreams = quicMaxOpenStreams + int64(math.Ceil(float64(quicMaxOpenStreams)/10.0))
@ -205,7 +187,6 @@ func NewTuic(option TuicOption) (*Tuic, error) {
MaxIncomingUniStreams: quicMaxOpenStreams, MaxIncomingUniStreams: quicMaxOpenStreams,
KeepAlivePeriod: time.Duration(option.HeartbeatInterval) * time.Millisecond, KeepAlivePeriod: time.Duration(option.HeartbeatInterval) * time.Millisecond,
DisablePathMTUDiscovery: option.DisableMTUDiscovery, DisablePathMTUDiscovery: option.DisableMTUDiscovery,
MaxDatagramFrameSize: int64(option.MaxDatagramFrameSize),
EnableDatagrams: true, EnableDatagrams: true,
} }
if option.ReceiveWindowConn == 0 { if option.ReceiveWindowConn == 0 {
@ -238,7 +219,6 @@ func NewTuic(option TuicOption) (*Tuic, error) {
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
}, },
option: &option,
} }
clientMaxOpenStreams := int64(option.MaxOpenStreams) clientMaxOpenStreams := int64(option.MaxOpenStreams)

View File

@ -14,7 +14,6 @@ import (
"github.com/Dreamacro/clash/common/convert" "github.com/Dreamacro/clash/common/convert"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -169,35 +168,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return nil, err return nil, err
} }
return v.streamConn(c, metadata) return v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
}
func (v *Vless) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
if metadata.NetWork == C.UDP {
if v.option.PacketAddr {
metadata = &C.Metadata{
NetWork: C.UDP,
Host: packetaddr.SeqPacketMagicAddress,
DstPort: "443",
}
} else {
metadata = &C.Metadata{ // a clear metadata only contains ip
NetWork: C.UDP,
DstIP: metadata.DstIP,
DstPort: metadata.DstPort,
}
}
conn, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
if v.option.PacketAddr {
conn = packetaddr.NewBindConn(conn)
}
} else {
conn, err = v.client.StreamConn(c, parseVlessAddr(metadata, false))
}
if err != nil {
conn = nil
}
return
} }
func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) { func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) {
@ -267,12 +238,6 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
// DialContextWithDialer implements C.ProxyAdapter // DialContextWithDialer implements C.ProxyAdapter
func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) { func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(v.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", v.addr) c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
@ -299,6 +264,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
} }
metadata.DstIP = ip metadata.DstIP = ip
} }
var c net.Conn var c net.Conn
// gun transport // gun transport
if v.transport != nil && len(opts) == 0 { if v.transport != nil && len(opts) == 0 {
@ -310,25 +276,27 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
c, err = v.streamConn(c, metadata) if v.option.PacketAddr {
packetAddrMetadata := *metadata // make a copy
packetAddrMetadata.Host = packetaddr.SeqPacketMagicAddress
packetAddrMetadata.DstPort = "443"
c, err = v.client.StreamConn(c, parseVlessAddr(&packetAddrMetadata, false))
} else {
c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
}
if err != nil { if err != nil {
return nil, fmt.Errorf("new vless client error: %v", err) return nil, fmt.Errorf("new vless client error: %v", err)
} }
return v.ListenPacketOnStreamConn(ctx, c, metadata) return v.ListenPacketOnStreamConn(c, metadata)
} }
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata) return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
} }
// ListenPacketWithDialer implements C.ProxyAdapter // ListenPacketWithDialer implements C.ProxyAdapter
func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) { func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if len(v.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr // vless use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host) ip, err := resolver.ResolveIP(ctx, metadata.Host)
@ -337,7 +305,6 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
} }
metadata.DstIP = ip metadata.DstIP = ip
} }
c, err := dialer.DialContext(ctx, "tcp", v.addr) c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
@ -347,39 +314,39 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
c, err = v.StreamConn(c, metadata) if v.option.PacketAddr {
packetAddrMetadata := *metadata // make a copy
packetAddrMetadata.Host = packetaddr.SeqPacketMagicAddress
packetAddrMetadata.DstPort = "443"
c, err = v.StreamConn(c, &packetAddrMetadata)
} else {
c, err = v.StreamConn(c, metadata)
}
if err != nil { if err != nil {
return nil, fmt.Errorf("new vless client error: %v", err) return nil, fmt.Errorf("new vless client error: %v", err)
} }
return v.ListenPacketOnStreamConn(ctx, c, metadata) return v.ListenPacketOnStreamConn(c, metadata)
} }
// SupportWithDialer implements C.ProxyAdapter // SupportWithDialer implements C.ProxyAdapter
func (v *Vless) SupportWithDialer() C.NetWork { func (v *Vless) SupportWithDialer() bool {
return C.ALLNet return true
} }
// ListenPacketOnStreamConn implements C.ProxyAdapter // ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { func (v *Vless) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
if v.option.XUDP { if v.option.XUDP {
return newPacketConn(&threadSafePacketConn{ return newPacketConn(&threadSafePacketConn{
PacketConn: vmessSing.NewXUDPConn(c, M.SocksaddrFromNet(metadata.UDPAddr())), PacketConn: vmessSing.NewXUDPConn(c, M.ParseSocksaddr(metadata.RemoteAddress())),
}, v), nil }, v), nil
} else if v.option.PacketAddr { } else if v.option.PacketAddr {
return newPacketConn(&threadSafePacketConn{ return newPacketConn(&threadSafePacketConn{
PacketConn: packetaddr.NewConn(&vlessPacketConn{ PacketConn: packetaddr.NewConn(&vlessPacketConn{
Conn: c, rAddr: metadata.UDPAddr(), Conn: c, rAddr: metadata.UDPAddr(),
}, M.SocksaddrFromNet(metadata.UDPAddr())), }, M.ParseSocksaddr(metadata.RemoteAddress())),
}, v), nil }, v), nil
} }
return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
@ -537,9 +504,6 @@ func NewVless(option VlessOption) (*Vless, error) {
option.XUDP = true option.XUDP = true
} }
} }
if option.XUDP {
option.PacketAddr = false
}
client, err := vless.NewClient(option.UUID, addons, option.FlowShow) client, err := vless.NewClient(option.UUID, addons, option.FlowShow)
if err != nil { if err != nil {
@ -574,15 +538,7 @@ func NewVless(option VlessOption) (*Vless, error) {
} }
case "grpc": case "grpc":
dialFn := func(network, addr string) (net.Conn, error) { dialFn := func(network, addr string) (net.Conn, error) {
var err error c, err := dialer.DialContext(context.Background(), "tcp", v.addr, v.Base.DialOptions()...)
var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
if len(v.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(v.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(context.Background(), "tcp", v.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }

View File

@ -13,7 +13,6 @@ import (
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -217,42 +216,27 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return v.streamConn(c, metadata)
}
func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
if metadata.NetWork == C.UDP { if metadata.NetWork == C.UDP {
if v.option.XUDP { if v.option.XUDP {
if N.NeedHandshake(c) { if N.NeedHandshake(c) {
conn = v.client.DialEarlyXUDPPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr())) return v.client.DialEarlyXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
} else { } else {
conn, err = v.client.DialXUDPPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr())) return v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} }
} else if v.option.PacketAddr {
if N.NeedHandshake(c) {
conn = v.client.DialEarlyPacketConn(c, M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
} else {
conn, err = v.client.DialPacketConn(c, M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
}
conn = packetaddr.NewBindConn(conn)
} else { } else {
if N.NeedHandshake(c) { if N.NeedHandshake(c) {
conn = v.client.DialEarlyPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr())) return v.client.DialEarlyPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
} else { } else {
conn, err = v.client.DialPacketConn(c, M.SocksaddrFromNet(metadata.UDPAddr())) return v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} }
} }
} else { } else {
if N.NeedHandshake(c) { if N.NeedHandshake(c) {
conn = v.client.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) return v.client.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
} else { } else {
conn, err = v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) return v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} }
} }
if err != nil {
conn = nil
}
return
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@ -279,12 +263,6 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
// DialContextWithDialer implements C.ProxyAdapter // DialContextWithDialer implements C.ProxyAdapter
func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) { func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
if len(v.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
c, err := dialer.DialContext(ctx, "tcp", v.addr) c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
@ -308,6 +286,14 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
} }
metadata.DstIP = ip metadata.DstIP = ip
} }
if v.option.PacketAddr {
_metadata := *metadata // make a copy
metadata = &_metadata
metadata.Host = packetaddr.SeqPacketMagicAddress
metadata.DstPort = "443"
}
var c net.Conn var c net.Conn
// gun transport // gun transport
if v.transport != nil && len(opts) == 0 { if v.transport != nil && len(opts) == 0 {
@ -319,24 +305,30 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
c, err = v.streamConn(c, metadata) if v.option.XUDP {
if N.NeedHandshake(c) {
c = v.client.DialEarlyXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} else {
c, err = v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
}
} else {
if N.NeedHandshake(c) {
c = v.client.DialEarlyPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} else {
c, err = v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
}
}
if err != nil { if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err) return nil, fmt.Errorf("new vmess client error: %v", err)
} }
return v.ListenPacketOnStreamConn(ctx, c, metadata) return v.ListenPacketOnStreamConn(c, metadata)
} }
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata) return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
} }
// ListenPacketWithDialer implements C.ProxyAdapter // ListenPacketWithDialer implements C.ProxyAdapter
func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) { func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if len(v.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(v.option.DialerProxy, dialer)
if err != nil {
return nil, err
}
}
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr // vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host) ip, err := resolver.ResolveIP(ctx, metadata.Host)
@ -359,26 +351,19 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil { if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err) return nil, fmt.Errorf("new vmess client error: %v", err)
} }
return v.ListenPacketOnStreamConn(ctx, c, metadata) return v.ListenPacketOnStreamConn(c, metadata)
} }
// SupportWithDialer implements C.ProxyAdapter // SupportWithDialer implements C.ProxyAdapter
func (v *Vmess) SupportWithDialer() C.NetWork { func (v *Vmess) SupportWithDialer() bool {
return C.ALLNet return true
} }
// ListenPacketOnStreamConn implements C.ProxyAdapter // ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vmess) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { func (v *Vmess) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr if v.option.PacketAddr {
if !metadata.Resolved() { return newPacketConn(&threadSafePacketConn{PacketConn: packetaddr.NewBindConn(c)}, v), nil
ip, err := resolver.ResolveIP(ctx, metadata.Host) } else if pc, ok := c.(net.PacketConn); ok {
if err != nil {
return nil, errors.New("can't resolve ip")
}
metadata.DstIP = ip
}
if pc, ok := c.(net.PacketConn); ok {
return newPacketConn(&threadSafePacketConn{PacketConn: pc}, v), nil return newPacketConn(&threadSafePacketConn{PacketConn: pc}, v), nil
} }
return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil return newPacketConn(&vmessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
@ -443,15 +428,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
} }
case "grpc": case "grpc":
dialFn := func(network, addr string) (net.Conn, error) { dialFn := func(network, addr string) (net.Conn, error) {
var err error c, err := dialer.DialContext(context.Background(), "tcp", v.addr, v.Base.DialOptions()...)
var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
if len(v.option.DialerProxy) > 0 {
cDialer, err = proxydialer.NewByName(v.option.DialerProxy, cDialer)
if err != nil {
return nil, err
}
}
c, err := cDialer.DialContext(context.Background(), "tcp", v.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
@ -509,9 +486,9 @@ type vmessPacketConn struct {
// WriteTo implments C.PacketConn.WriteTo // WriteTo implments C.PacketConn.WriteTo
// Since VMess doesn't support full cone NAT by design, we verify if addr matches uc.rAddr, and drop the packet if not. // Since VMess doesn't support full cone NAT by design, we verify if addr matches uc.rAddr, and drop the packet if not.
func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { func (uc *vmessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
allowedAddr := uc.rAddr allowedAddr := uc.rAddr.(*net.UDPAddr)
destAddr := addr destAddr := addr.(*net.UDPAddr)
if allowedAddr.String() != destAddr.String() { if !(allowedAddr.IP.Equal(destAddr.IP) && allowedAddr.Port == destAddr.Port) {
return 0, ErrUDPRemoteAddrMismatch return 0, ErrUDPRemoteAddrMismatch
} }
uc.access.Lock() uc.access.Lock()

View File

@ -15,10 +15,8 @@ import (
CN "github.com/Dreamacro/clash/common/net" CN "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/dns"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
wireguard "github.com/metacubex/sing-wireguard" wireguard "github.com/metacubex/sing-wireguard"
@ -39,97 +37,75 @@ type WireGuard struct {
dialer *wgSingDialer dialer *wgSingDialer
startOnce sync.Once startOnce sync.Once
startErr error startErr error
resolver *dns.Resolver
refP *refProxyAdapter
} }
type WireGuardOption struct { type WireGuardOption struct {
BasicOption BasicOption
WireGuardPeerOption Name string `proxy:"name"`
Name string `proxy:"name"` Server string `proxy:"server"`
PrivateKey string `proxy:"private-key"` Port int `proxy:"port"`
Workers int `proxy:"workers,omitempty"` Ip string `proxy:"ip,omitempty"`
MTU int `proxy:"mtu,omitempty"` Ipv6 string `proxy:"ipv6,omitempty"`
UDP bool `proxy:"udp,omitempty"` PrivateKey string `proxy:"private-key"`
PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"` PublicKey string `proxy:"public-key"`
PreSharedKey string `proxy:"pre-shared-key,omitempty"`
Peers []WireGuardPeerOption `proxy:"peers,omitempty"` Reserved []uint8 `proxy:"reserved,omitempty"`
Workers int `proxy:"workers,omitempty"`
RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"` MTU int `proxy:"mtu,omitempty"`
Dns []string `proxy:"dns,omitempty"` UDP bool `proxy:"udp,omitempty"`
} PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
type WireGuardPeerOption struct {
Server string `proxy:"server"`
Port int `proxy:"port"`
Ip string `proxy:"ip,omitempty"`
Ipv6 string `proxy:"ipv6,omitempty"`
PublicKey string `proxy:"public-key,omitempty"`
PreSharedKey string `proxy:"pre-shared-key,omitempty"`
Reserved []uint8 `proxy:"reserved,omitempty"`
AllowedIPs []string `proxy:"allowed_ips,omitempty"`
} }
type wgSingDialer struct { type wgSingDialer struct {
dialer dialer.Dialer dialer dialer.Dialer
proxyName string
} }
var _ N.Dialer = (*wgSingDialer)(nil) var _ N.Dialer = &wgSingDialer{}
func (d *wgSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { func (d *wgSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
var cDialer C.Dialer = d.dialer return d.dialer.DialContext(ctx, network, destination.String())
if len(d.proxyName) > 0 {
pd, err := proxydialer.NewByName(d.proxyName, d.dialer)
if err != nil {
return nil, err
}
cDialer = pd
}
return cDialer.DialContext(ctx, network, destination.String())
} }
func (d *wgSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (d *wgSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
var cDialer C.Dialer = d.dialer return d.dialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
if len(d.proxyName) > 0 {
pd, err := proxydialer.NewByName(d.proxyName, d.dialer)
if err != nil {
return nil, err
}
cDialer = pd
}
return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
}
type wgSingErrorHandler struct {
name string
}
var _ E.Handler = (*wgSingErrorHandler)(nil)
func (w wgSingErrorHandler) NewError(ctx context.Context, err error) {
if E.IsClosedOrCanceled(err) {
log.SingLogger.Debug(fmt.Sprintf("[WG](%s) connection closed: %s", w.name, err))
return
}
log.SingLogger.Error(fmt.Sprintf("[WG](%s) %s", w.name, err))
} }
type wgNetDialer struct { type wgNetDialer struct {
tunDevice wireguard.Device tunDevice wireguard.Device
} }
var _ dialer.NetDialer = (*wgNetDialer)(nil) var _ dialer.NetDialer = &wgNetDialer{}
func (d wgNetDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { func (d wgNetDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return d.tunDevice.DialContext(ctx, network, M.ParseSocksaddr(address).Unwrap()) return d.tunDevice.DialContext(ctx, network, M.ParseSocksaddr(address).Unwrap())
} }
func (option WireGuardPeerOption) Addr() M.Socksaddr { func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
return M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)) 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: &wgSingDialer{dialer: dialer.NewDialer()},
}
runtime.SetFinalizer(outbound, closeWireGuard)
func (option WireGuardPeerOption) Prefixes() ([]netip.Prefix, error) { 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.ParseSocksaddrHostPort(option.Server, uint16(option.Port))
outbound.bind = wireguard.NewClientBind(context.Background(), outbound.dialer, peerAddr, reserved)
localPrefixes := make([]netip.Prefix, 0, 2) localPrefixes := make([]netip.Prefix, 0, 2)
if len(option.Ip) > 0 { if len(option.Ip) > 0 {
if !strings.Contains(option.Ip, "/") { if !strings.Contains(option.Ip, "/") {
@ -154,46 +130,7 @@ func (option WireGuardPeerOption) Prefixes() ([]netip.Prefix, error) {
if len(localPrefixes) == 0 { if len(localPrefixes) == 0 {
return nil, E.New("missing local address") return nil, E.New("missing local address")
} }
return localPrefixes, nil var privateKey, peerPublicKey, preSharedKey string
}
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: &wgSingDialer{dialer: dialer.NewDialer(), proxyName: option.DialerProxy},
}
runtime.SetFinalizer(outbound, closeWireGuard)
var reserved [3]uint8
if len(option.Reserved) > 0 {
if len(option.Reserved) != 3 {
return nil, E.New("invalid reserved value, required 3 bytes, got ", len(option.Reserved))
}
copy(reserved[:], option.Reserved)
}
var isConnect bool
var connectAddr M.Socksaddr
if len(option.Peers) < 2 {
isConnect = true
if len(option.Peers) == 1 {
connectAddr = option.Peers[0].Addr()
} else {
connectAddr = option.Addr()
}
}
outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, connectAddr, reserved)
var localPrefixes []netip.Prefix
var privateKey string
{ {
bytes, err := base64.StdEncoding.DecodeString(option.PrivateKey) bytes, err := base64.StdEncoding.DecodeString(option.PrivateKey)
if err != nil { if err != nil {
@ -201,92 +138,40 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
} }
privateKey = hex.EncodeToString(bytes) privateKey = hex.EncodeToString(bytes)
} }
ipcConf := "private_key=" + privateKey {
if peersLen := len(option.Peers); peersLen > 0 { bytes, err := base64.StdEncoding.DecodeString(option.PublicKey)
localPrefixes = make([]netip.Prefix, 0, peersLen*2)
for i, peer := range option.Peers {
var peerPublicKey, preSharedKey string
{
bytes, err := base64.StdEncoding.DecodeString(peer.PublicKey)
if err != nil {
return nil, E.Cause(err, "decode public key for peer ", i)
}
peerPublicKey = hex.EncodeToString(bytes)
}
if peer.PreSharedKey != "" {
bytes, err := base64.StdEncoding.DecodeString(peer.PreSharedKey)
if err != nil {
return nil, E.Cause(err, "decode pre shared key for peer ", i)
}
preSharedKey = hex.EncodeToString(bytes)
}
destination := peer.Addr()
ipcConf += "\npublic_key=" + peerPublicKey
ipcConf += "\nendpoint=" + destination.String()
if preSharedKey != "" {
ipcConf += "\npreshared_key=" + preSharedKey
}
if len(peer.AllowedIPs) == 0 {
return nil, E.New("missing allowed_ips for peer ", i)
}
for _, allowedIP := range peer.AllowedIPs {
ipcConf += "\nallowed_ip=" + allowedIP
}
if len(peer.Reserved) > 0 {
if len(peer.Reserved) != 3 {
return nil, E.New("invalid reserved value for peer ", i, ", required 3 bytes, got ", len(peer.Reserved))
}
copy(reserved[:], option.Reserved)
outbound.bind.SetReservedForEndpoint(destination, reserved)
}
prefixes, err := peer.Prefixes()
if err != nil {
return nil, err
}
localPrefixes = append(localPrefixes, prefixes...)
}
} else {
var peerPublicKey, preSharedKey string
{
bytes, err := base64.StdEncoding.DecodeString(option.PublicKey)
if err != nil {
return nil, E.Cause(err, "decode peer public key")
}
peerPublicKey = hex.EncodeToString(bytes)
}
if option.PreSharedKey != "" {
bytes, err := base64.StdEncoding.DecodeString(option.PreSharedKey)
if err != nil {
return nil, E.Cause(err, "decode pre shared key")
}
preSharedKey = hex.EncodeToString(bytes)
}
ipcConf += "\npublic_key=" + peerPublicKey
ipcConf += "\nendpoint=" + connectAddr.String()
if preSharedKey != "" {
ipcConf += "\npreshared_key=" + preSharedKey
}
var err error
localPrefixes, err = option.Prefixes()
if err != nil { if err != nil {
return nil, err return nil, E.Cause(err, "decode peer public key")
} }
var has4, has6 bool peerPublicKey = hex.EncodeToString(bytes)
for _, address := range localPrefixes { }
if address.Addr().Is4() { if option.PreSharedKey != "" {
has4 = true bytes, err := base64.StdEncoding.DecodeString(option.PreSharedKey)
} else { if err != nil {
has6 = true return nil, E.Cause(err, "decode pre shared key")
}
} }
if has4 { preSharedKey = hex.EncodeToString(bytes)
ipcConf += "\nallowed_ip=0.0.0.0/0" }
} ipcConf := "private_key=" + privateKey
if has6 { ipcConf += "\npublic_key=" + peerPublicKey
ipcConf += "\nallowed_ip=::/0" 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"
}
if option.PersistentKeepalive != 0 { if option.PersistentKeepalive != 0 {
ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", option.PersistentKeepalive) ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", option.PersistentKeepalive)
} }
@ -294,9 +179,6 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
if mtu == 0 { if mtu == 0 {
mtu = 1408 mtu = 1408
} }
if len(localPrefixes) == 0 {
return nil, E.New("missing local address")
}
var err error var err error
outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu)) outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu))
if err != nil { if err != nil {
@ -304,45 +186,20 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
} }
outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, &device.Logger{ outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, &device.Logger{
Verbosef: func(format string, args ...interface{}) { Verbosef: func(format string, args ...interface{}) {
log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...))) log.SingLogger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
}, },
Errorf: func(format string, args ...interface{}) { Errorf: func(format string, args ...interface{}) {
log.SingLogger.Error(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...))) log.SingLogger.Error(fmt.Sprintf(strings.ToLower(format), args...))
}, },
}, option.Workers) }, option.Workers)
if debug.Enabled { if debug.Enabled {
log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", option.Name, ipcConf)) log.SingLogger.Trace("created wireguard ipc conf: \n", ipcConf)
} }
err = outbound.device.IpcSet(ipcConf) err = outbound.device.IpcSet(ipcConf)
if err != nil { if err != nil {
return nil, E.Cause(err, "setup wireguard") return nil, E.Cause(err, "setup wireguard")
} }
//err = outbound.tunDevice.Start() //err = outbound.tunDevice.Start()
var has6 bool
for _, address := range localPrefixes {
if !address.Addr().Unmap().Is4() {
has6 = true
break
}
}
refP := &refProxyAdapter{}
outbound.refP = refP
if option.RemoteDnsResolve && len(option.Dns) > 0 {
nss, err := dns.ParseNameServer(option.Dns)
if err != nil {
return nil, err
}
for i := range nss {
nss[i].ProxyAdapter = refP
}
outbound.resolver = dns.NewResolver(dns.Config{
Main: nss,
IPv6: has6,
})
}
return outbound, nil return outbound, nil
} }
@ -363,14 +220,8 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
if w.startErr != nil { if w.startErr != nil {
return nil, w.startErr return nil, w.startErr
} }
if !metadata.Resolved() || w.resolver != nil { if !metadata.Resolved() {
r := resolver.DefaultResolver options = append(options, dialer.WithResolver(resolver.DefaultResolver))
if w.resolver != nil {
w.refP.SetProxyAdapter(w)
defer w.refP.ClearProxyAdapter()
r = w.resolver
}
options = append(options, dialer.WithResolver(r))
options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice})) options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice}))
conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress()) conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress())
} else { } else {
@ -399,14 +250,8 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
if err != nil { if err != nil {
return nil, err return nil, err
} }
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" { if !metadata.Resolved() {
r := resolver.DefaultResolver ip, err := resolver.ResolveIP(ctx, metadata.Host)
if w.resolver != nil {
w.refP.SetProxyAdapter(w)
defer w.refP.ClearProxyAdapter()
r = w.resolver
}
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
if err != nil { if err != nil {
return nil, errors.New("can't resolve ip") return nil, errors.New("can't resolve ip")
} }
@ -422,144 +267,3 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
} }
return newPacketConn(CN.NewRefPacketConn(pc, w), w), nil return newPacketConn(CN.NewRefPacketConn(pc, w), w), nil
} }
// IsL3Protocol implements C.ProxyAdapter
func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool {
return true
}
type refProxyAdapter struct {
proxyAdapter C.ProxyAdapter
count int
mutex sync.Mutex
}
func (r *refProxyAdapter) SetProxyAdapter(proxyAdapter C.ProxyAdapter) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.proxyAdapter = proxyAdapter
r.count++
}
func (r *refProxyAdapter) ClearProxyAdapter() {
r.mutex.Lock()
defer r.mutex.Unlock()
r.count--
if r.count == 0 {
r.proxyAdapter = nil
}
}
func (r *refProxyAdapter) Name() string {
if r.proxyAdapter != nil {
return r.proxyAdapter.Name()
}
return ""
}
func (r *refProxyAdapter) Type() C.AdapterType {
if r.proxyAdapter != nil {
return r.proxyAdapter.Type()
}
return C.AdapterType(0)
}
func (r *refProxyAdapter) Addr() string {
if r.proxyAdapter != nil {
return r.proxyAdapter.Addr()
}
return ""
}
func (r *refProxyAdapter) SupportUDP() bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportUDP()
}
return false
}
func (r *refProxyAdapter) SupportXUDP() bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportXUDP()
}
return false
}
func (r *refProxyAdapter) SupportTFO() bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportTFO()
}
return false
}
func (r *refProxyAdapter) MarshalJSON() ([]byte, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.MarshalJSON()
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.StreamConn(c, metadata)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.DialContext(ctx, metadata, opts...)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) SupportUOT() bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportUOT()
}
return false
}
func (r *refProxyAdapter) SupportWithDialer() C.NetWork {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportWithDialer()
}
return C.InvalidNet
}
func (r *refProxyAdapter) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.Conn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.DialContextWithDialer(ctx, dialer, metadata)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.PacketConn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.ListenPacketWithDialer(ctx, dialer, metadata)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) IsL3Protocol(metadata *C.Metadata) bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.IsL3Protocol(metadata)
}
return false
}
func (r *refProxyAdapter) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
if r.proxyAdapter != nil {
return r.proxyAdapter.Unwrap(metadata, touch)
}
return nil
}
var _ C.ProxyAdapter = (*refProxyAdapter)(nil)

View File

@ -37,13 +37,16 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts .
} }
if N.NeedHandshake(c) { if N.NeedHandshake(c) {
c = callback.NewFirstWriteCallBackConn(c, func(err error) { c = &callback.FirstWriteCallBackConn{
if err == nil { Conn: c,
f.onDialSuccess() Callback: func(err error) {
} else { if err == nil {
f.onDialFailed(proxy.Type(), err) f.onDialSuccess()
} } else {
}) f.onDialFailed(proxy.Type(), err)
}
},
}
} }
return c, err return c, err
@ -70,11 +73,6 @@ func (f *Fallback) SupportUDP() bool {
return proxy.SupportUDP() return proxy.SupportUDP()
} }
// IsL3Protocol implements C.ProxyAdapter
func (f *Fallback) IsL3Protocol(metadata *C.Metadata) bool {
return f.findAliveProxy(false).IsL3Protocol(metadata)
}
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (f *Fallback) MarshalJSON() ([]byte, error) { func (f *Fallback) MarshalJSON() ([]byte, error) {
all := []string{} all := []string{}
@ -138,10 +136,6 @@ func (f *Fallback) Set(name string) error {
return nil return nil
} }
func (f *Fallback) ForceSet(name string) {
f.selected = name
}
func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback { func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
return &Fallback{ return &Fallback{
GroupBase: NewGroupBase(GroupBaseOption{ GroupBase: NewGroupBase(GroupBaseOption{

View File

@ -8,7 +8,6 @@ import (
"time" "time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/atomic"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
@ -16,6 +15,7 @@ import (
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
"github.com/dlclark/regexp2" "github.com/dlclark/regexp2"
"go.uber.org/atomic"
) )
type GroupBase struct { type GroupBase struct {

View File

@ -95,13 +95,16 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, op
} }
if N.NeedHandshake(c) { if N.NeedHandshake(c) {
c = callback.NewFirstWriteCallBackConn(c, func(err error) { c = &callback.FirstWriteCallBackConn{
if err == nil { Conn: c,
lb.onDialSuccess() Callback: func(err error) {
} else { if err == nil {
lb.onDialFailed(proxy.Type(), err) lb.onDialSuccess()
} } else {
}) lb.onDialFailed(proxy.Type(), err)
}
},
}
} }
return return
@ -124,11 +127,6 @@ func (lb *LoadBalance) SupportUDP() bool {
return !lb.disableUDP return !lb.disableUDP
} }
// IsL3Protocol implements C.ProxyAdapter
func (lb *LoadBalance) IsL3Protocol(metadata *C.Metadata) bool {
return lb.Unwrap(metadata, false).IsL3Protocol(metadata)
}
func strategyRoundRobin() strategyFn { func strategyRoundRobin() strategyFn {
idx := 0 idx := 0
idxMutex := sync.Mutex{} idxMutex := sync.Mutex{}

View File

@ -3,9 +3,13 @@ package outboundgroup
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"net"
"net/netip"
"strings"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/proxydialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
) )
@ -14,6 +18,36 @@ type Relay struct {
*GroupBase *GroupBase
} }
type proxyDialer struct {
proxy C.Proxy
dialer C.Dialer
}
func (p proxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
currentMeta, err := addrToMetadata(address)
if err != nil {
return nil, err
}
if strings.Contains(network, "udp") { // should not support this operation
currentMeta.NetWork = C.UDP
pc, err := p.proxy.ListenPacketWithDialer(ctx, p.dialer, currentMeta)
if err != nil {
return nil, err
}
return N.NewBindPacketConn(pc, currentMeta.UDPAddr()), nil
}
return p.proxy.DialContextWithDialer(ctx, p.dialer, currentMeta)
}
func (p proxyDialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
currentMeta, err := addrToMetadata(rAddrPort.String())
if err != nil {
return nil, err
}
currentMeta.NetWork = C.UDP
return p.proxy.ListenPacketWithDialer(ctx, p.dialer, currentMeta)
}
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
proxies, chainProxies := r.proxies(metadata, true) proxies, chainProxies := r.proxies(metadata, true)
@ -27,7 +61,10 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
var d C.Dialer var d C.Dialer
d = dialer.NewDialer(r.Base.DialOptions(opts...)...) d = dialer.NewDialer(r.Base.DialOptions(opts...)...)
for _, proxy := range proxies[:len(proxies)-1] { for _, proxy := range proxies[:len(proxies)-1] {
d = proxydialer.New(proxy, d, false) d = proxyDialer{
proxy: proxy,
dialer: d,
}
} }
last := proxies[len(proxies)-1] last := proxies[len(proxies)-1]
conn, err := last.DialContextWithDialer(ctx, d, metadata) conn, err := last.DialContextWithDialer(ctx, d, metadata)
@ -58,7 +95,10 @@ func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
var d C.Dialer var d C.Dialer
d = dialer.NewDialer(r.Base.DialOptions(opts...)...) d = dialer.NewDialer(r.Base.DialOptions(opts...)...)
for _, proxy := range proxies[:len(proxies)-1] { for _, proxy := range proxies[:len(proxies)-1] {
d = proxydialer.New(proxy, d, false) d = proxyDialer{
proxy: proxy,
dialer: d,
}
} }
last := proxies[len(proxies)-1] last := proxies[len(proxies)-1]
pc, err := last.ListenPacketWithDialer(ctx, d, metadata) pc, err := last.ListenPacketWithDialer(ctx, d, metadata)
@ -89,10 +129,7 @@ func (r *Relay) SupportUDP() bool {
if proxy.SupportUOT() { if proxy.SupportUOT() {
return true return true
} }
switch proxy.SupportWithDialer() { if !proxy.SupportWithDialer() {
case C.ALLNet:
case C.UDP:
default: // C.TCP and C.InvalidNet
return false return false
} }
} }

View File

@ -44,11 +44,6 @@ func (s *Selector) SupportUDP() bool {
return s.selectedProxy(false).SupportUDP() return s.selectedProxy(false).SupportUDP()
} }
// IsL3Protocol implements C.ProxyAdapter
func (s *Selector) IsL3Protocol(metadata *C.Metadata) bool {
return s.selectedProxy(false).IsL3Protocol(metadata)
}
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
func (s *Selector) MarshalJSON() ([]byte, error) { func (s *Selector) MarshalJSON() ([]byte, error) {
all := []string{} all := []string{}
@ -78,10 +73,6 @@ func (s *Selector) Set(name string) error {
return errors.New("proxy not exist") return errors.New("proxy not exist")
} }
func (s *Selector) ForceSet(name string) {
s.selected = name
}
// Unwrap implements C.ProxyAdapter // Unwrap implements C.ProxyAdapter
func (s *Selector) Unwrap(metadata *C.Metadata, touch bool) C.Proxy { func (s *Selector) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
return s.selectedProxy(touch) return s.selectedProxy(touch)

View File

@ -3,7 +3,6 @@ package outboundgroup
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"time" "time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
@ -25,8 +24,6 @@ func urlTestWithTolerance(tolerance uint16) urlTestOption {
type URLTest struct { type URLTest struct {
*GroupBase *GroupBase
selected string
testUrl string
tolerance uint16 tolerance uint16
disableUDP bool disableUDP bool
fastNode C.Proxy fastNode C.Proxy
@ -37,26 +34,6 @@ func (u *URLTest) Now() string {
return u.fast(false).Name() return u.fast(false).Name()
} }
func (u *URLTest) Set(name string) error {
var p C.Proxy
for _, proxy := range u.GetProxies(false) {
if proxy.Name() == name {
p = proxy
break
}
}
if p == nil {
return errors.New("proxy not exist")
}
u.selected = name
u.fast(false)
return nil
}
func (u *URLTest) ForceSet(name string) {
u.selected = name
}
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) { func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
proxy := u.fast(true) proxy := u.fast(true)
@ -68,13 +45,16 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
} }
if N.NeedHandshake(c) { if N.NeedHandshake(c) {
c = callback.NewFirstWriteCallBackConn(c, func(err error) { c = &callback.FirstWriteCallBackConn{
if err == nil { Conn: c,
u.onDialSuccess() Callback: func(err error) {
} else { if err == nil {
u.onDialFailed(proxy.Type(), err) u.onDialSuccess()
} } else {
}) u.onDialFailed(proxy.Type(), err)
}
},
}
} }
return c, err return c, err
@ -97,24 +77,16 @@ func (u *URLTest) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
func (u *URLTest) fast(touch bool) C.Proxy { func (u *URLTest) fast(touch bool) C.Proxy {
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) { elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
var s C.Proxy
proxies := u.GetProxies(touch) proxies := u.GetProxies(touch)
fast := proxies[0] fast := proxies[0]
if fast.Name() == u.selected {
s = fast
}
min := fast.LastDelay() min := fast.LastDelay()
fastNotExist := true fastNotExist := true
for _, proxy := range proxies[1:] { for _, proxy := range proxies[1:] {
if u.fastNode != nil && proxy.Name() == u.fastNode.Name() { if u.fastNode != nil && proxy.Name() == u.fastNode.Name() {
fastNotExist = false fastNotExist = false
} }
if proxy.Name() == u.selected {
s = proxy
}
if !proxy.Alive() { if !proxy.Alive() {
continue continue
} }
@ -125,15 +97,12 @@ func (u *URLTest) fast(touch bool) C.Proxy {
min = delay min = delay
} }
} }
// tolerance // tolerance
if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance { if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance {
u.fastNode = fast u.fastNode = fast
} }
if s != nil {
if s.Alive() && s.LastDelay() < fast.LastDelay()+u.tolerance {
u.fastNode = s
}
}
return u.fastNode, nil return u.fastNode, nil
}) })
if shared && touch { // a shared fastSingle.Do() may cause providers untouched, so we touch them again if shared && touch { // a shared fastSingle.Do() may cause providers untouched, so we touch them again
@ -148,12 +117,8 @@ func (u *URLTest) SupportUDP() bool {
if u.disableUDP { if u.disableUDP {
return false return false
} }
return u.fast(false).SupportUDP()
}
// IsL3Protocol implements C.ProxyAdapter return u.fast(false).SupportUDP()
func (u *URLTest) IsL3Protocol(metadata *C.Metadata) bool {
return u.fast(false).IsL3Protocol(metadata)
} }
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter
@ -199,7 +164,6 @@ func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, o
}), }),
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10), fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,
testUrl: option.URL,
} }
for _, option := range options { for _, option := range options {

View File

@ -1,10 +1,37 @@
package outboundgroup package outboundgroup
import ( import (
"fmt"
"net" "net"
"net/netip"
"time" "time"
C "github.com/Dreamacro/clash/constant"
) )
func addrToMetadata(rawAddress string) (addr *C.Metadata, err error) {
host, port, err := net.SplitHostPort(rawAddress)
if err != nil {
err = fmt.Errorf("addrToMetadata failed: %w", err)
return
}
if ip, err := netip.ParseAddr(host); err != nil {
addr = &C.Metadata{
Host: host,
DstPort: port,
}
} else {
addr = &C.Metadata{
Host: "",
DstIP: ip.Unmap(),
DstPort: port,
}
}
return
}
func tcpKeepAlive(c net.Conn) { func tcpKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok { if tcp, ok := c.(*net.TCPConn); ok {
_ = tcp.SetKeepAlive(true) _ = tcp.SetKeepAlive(true)
@ -14,5 +41,4 @@ func tcpKeepAlive(c net.Conn) {
type SelectAble interface { type SelectAble interface {
Set(string) error Set(string) error
ForceSet(name string)
} }

View File

@ -23,7 +23,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
) )
switch proxyType { switch proxyType {
case "ss": case "ss":
ssOption := &outbound.ShadowSocksOption{ClientFingerprint: tlsC.GetGlobalFingerprint()} ssOption := &outbound.ShadowSocksOption{}
err = decoder.Decode(mapping, ssOption) err = decoder.Decode(mapping, ssOption)
if err != nil { if err != nil {
break break
@ -56,7 +56,10 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
Method: "GET", Method: "GET",
Path: []string{"/"}, Path: []string{"/"},
}, },
ClientFingerprint: tlsC.GetGlobalFingerprint(), }
if GlobalUtlsClient := tlsC.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 {
vmessOption.ClientFingerprint = GlobalUtlsClient
} }
err = decoder.Decode(mapping, vmessOption) err = decoder.Decode(mapping, vmessOption)
@ -65,7 +68,12 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
} }
proxy, err = outbound.NewVmess(*vmessOption) proxy, err = outbound.NewVmess(*vmessOption)
case "vless": case "vless":
vlessOption := &outbound.VlessOption{ClientFingerprint: tlsC.GetGlobalFingerprint()} vlessOption := &outbound.VlessOption{}
if GlobalUtlsClient := tlsC.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 {
vlessOption.ClientFingerprint = GlobalUtlsClient
}
err = decoder.Decode(mapping, vlessOption) err = decoder.Decode(mapping, vlessOption)
if err != nil { if err != nil {
break break
@ -79,7 +87,12 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
} }
proxy, err = outbound.NewSnell(*snellOption) proxy, err = outbound.NewSnell(*snellOption)
case "trojan": case "trojan":
trojanOption := &outbound.TrojanOption{ClientFingerprint: tlsC.GetGlobalFingerprint()} trojanOption := &outbound.TrojanOption{}
if GlobalUtlsClient := tlsC.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 {
trojanOption.ClientFingerprint = GlobalUtlsClient
}
err = decoder.Decode(mapping, trojanOption) err = decoder.Decode(mapping, trojanOption)
if err != nil { if err != nil {
break break
@ -114,19 +127,5 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
return nil, err return nil, err
} }
if muxMapping, muxExist := mapping["smux"].(map[string]any); muxExist {
muxOption := &outbound.SingMuxOption{}
err = decoder.Decode(muxMapping, muxOption)
if err != nil {
return nil, err
}
if muxOption.Enabled {
proxy, err = outbound.NewSingMux(*muxOption, proxy, proxy.(outbound.ProxyBase))
if err != nil {
return nil, err
}
}
}
return NewProxy(proxy), nil return NewProxy(proxy), nil
} }

View File

@ -4,12 +4,13 @@ import (
"context" "context"
"time" "time"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/common/batch" "github.com/Dreamacro/clash/common/batch"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"go.uber.org/atomic"
) )
const ( const (
@ -33,15 +34,16 @@ type HealthCheck struct {
func (hc *HealthCheck) process() { func (hc *HealthCheck) process() {
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
go func() {
time.Sleep(30 * time.Second)
hc.lazyCheck()
}()
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
now := time.Now().Unix() hc.lazyCheck()
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
hc.check()
} else {
log.Debugln("Skip once health check because we are lazy")
}
case <-hc.done: case <-hc.done:
ticker.Stop() ticker.Stop()
return return
@ -49,6 +51,17 @@ func (hc *HealthCheck) process() {
} }
} }
func (hc *HealthCheck) lazyCheck() bool {
now := time.Now().Unix()
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
hc.check()
return true
} else {
log.Debugln("Skip once health check because we are lazy")
return false
}
}
func (hc *HealthCheck) setProxy(proxies []C.Proxy) { func (hc *HealthCheck) setProxy(proxies []C.Proxy) {
hc.proxies = proxies hc.proxies = proxies
} }
@ -63,7 +76,10 @@ func (hc *HealthCheck) touch() {
func (hc *HealthCheck) check() { func (hc *HealthCheck) check() {
_, _, _ = hc.singleDo.Do(func() (struct{}, error) { _, _, _ = hc.singleDo.Do(func() (struct{}, error) {
id := utils.NewUUIDV4().String() id := ""
if uid, err := utils.UnsafeUUIDGenerator.NewV4(); err == nil {
id = uid.String()
}
log.Debugln("Start New Health Checking {%s}", id) log.Debugln("Start New Health Checking {%s}", id)
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10)) b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
for _, proxy := range hc.proxies { for _, proxy := range hc.proxies {

View File

@ -28,7 +28,6 @@ type proxyProviderSchema struct {
Filter string `provider:"filter,omitempty"` Filter string `provider:"filter,omitempty"`
ExcludeFilter string `provider:"exclude-filter,omitempty"` ExcludeFilter string `provider:"exclude-filter,omitempty"`
ExcludeType string `provider:"exclude-type,omitempty"` ExcludeType string `provider:"exclude-type,omitempty"`
DialerProxy string `provider:"dialer-proxy,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"` HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
} }
@ -66,7 +65,6 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
filter := schema.Filter filter := schema.Filter
excludeFilter := schema.ExcludeFilter excludeFilter := schema.ExcludeFilter
excludeType := schema.ExcludeType excludeType := schema.ExcludeType
dialerProxy := schema.DialerProxy
return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, dialerProxy, vehicle, hc) return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, vehicle, hc)
} }

View File

@ -17,7 +17,6 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel/statistic"
"github.com/dlclark/regexp2" "github.com/dlclark/regexp2"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@ -81,8 +80,6 @@ func (pp *proxySetProvider) Initial() error {
return err return err
} }
pp.OnUpdate(elm) pp.OnUpdate(elm)
pp.getSubscriptionInfo()
pp.closeAllConnections()
return nil return nil
} }
@ -102,7 +99,7 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
pp.proxies = proxies pp.proxies = proxies
pp.healthCheck.setProxy(proxies) pp.healthCheck.setProxy(proxies)
if pp.healthCheck.auto() { if pp.healthCheck.auto() {
go pp.healthCheck.check() defer func() { go pp.healthCheck.lazyCheck() }()
} }
} }
@ -140,24 +137,12 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
}() }()
} }
func (pp *proxySetProvider) closeAllConnections() {
snapshot := statistic.DefaultManager.Snapshot()
for _, c := range snapshot.Connections {
for _, chain := range c.Chains() {
if chain == pp.Name() {
_ = c.Close()
break
}
}
}
}
func stopProxyProvider(pd *ProxySetProvider) { func stopProxyProvider(pd *ProxySetProvider) {
pd.healthCheck.close() pd.healthCheck.close()
_ = pd.Fetcher.Destroy() _ = pd.Fetcher.Destroy()
} }
func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
excludeFilterReg, err := regexp2.Compile(excludeFilter, 0) excludeFilterReg, err := regexp2.Compile(excludeFilter, 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err) return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
@ -185,8 +170,10 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc
healthCheck: hc, healthCheck: hc,
} }
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy), proxiesOnUpdate(pd)) fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg), proxiesOnUpdate(pd))
pd.Fetcher = fetcher pd.Fetcher = fetcher
pd.getSubscriptionInfo()
wrapper := &ProxySetProvider{pd} wrapper := &ProxySetProvider{pd}
runtime.SetFinalizer(wrapper, stopProxyProvider) runtime.SetFinalizer(wrapper, stopProxyProvider)
return wrapper, nil return wrapper, nil
@ -281,7 +268,7 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
} }
} }
func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp, dialerProxy string) resource.Parser[[]C.Proxy] { func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp) resource.Parser[[]C.Proxy] {
return func(buf []byte) ([]C.Proxy, error) { return func(buf []byte) ([]C.Proxy, error) {
schema := &ProxySchema{} schema := &ProxySchema{}
@ -344,9 +331,6 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
if _, ok := proxiesSet[name]; ok { if _, ok := proxiesSet[name]; ok {
continue continue
} }
if len(dialerProxy) > 0 {
mapping["dialer-proxy"] = dialerProxy
}
proxy, err := adapter.ParseProxy(mapping) proxy, err := adapter.ParseProxy(mapping)
if err != nil { if err != nil {
return nil, fmt.Errorf("proxy %d error: %w", idx, err) return nil, fmt.Errorf("proxy %d error: %w", idx, err)

View File

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

View File

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

View File

@ -10,11 +10,9 @@ const BufferSize = buf.BufferSize
type Buffer = buf.Buffer type Buffer = buf.Buffer
var New = buf.New var New = buf.New
var NewSize = buf.NewSize
var StackNew = buf.StackNew var StackNew = buf.StackNew
var StackNewSize = buf.StackNewSize var StackNewSize = buf.StackNewSize
var With = buf.With var With = buf.With
var As = buf.As
var KeepAlive = common.KeepAlive var KeepAlive = common.KeepAlive
@ -23,7 +21,5 @@ func Dup[T any](obj T) T {
return common.Dup(obj) return common.Dup(obj)
} }
var ( var Must = common.Must
Must = common.Must var Error = common.Error
Error = common.Error
)

View File

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

View File

@ -5,11 +5,10 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/Dreamacro/clash/log"
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
"github.com/Dreamacro/clash/log"
) )
// ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config // ConvertsV2Ray convert V2Ray subscribe proxies data to clash proxies config
@ -202,8 +201,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
vmess["servername"] = sni vmess["servername"] = sni
} }
network, _ := values["net"].(string) network := strings.ToLower(values["net"].(string))
network = strings.ToLower(network)
if values["type"] == "http" { if values["type"] == "http" {
network = "http" network = "http"
} else if network == "http" { } else if network == "http" {
@ -211,12 +209,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
} }
vmess["network"] = network vmess["network"] = network
tls, ok := values["tls"].(string) tls := strings.ToLower(values["tls"].(string))
if ok { if strings.HasSuffix(tls, "tls") {
tls = strings.ToLower(tls) vmess["tls"] = true
if strings.HasSuffix(tls, "tls") {
vmess["tls"] = true
}
} }
switch network { switch network {

View File

@ -294,7 +294,8 @@ var (
) )
func RandHost() string { func RandHost() string {
base := strings.ToLower(base64.RawURLEncoding.EncodeToString(utils.NewUUIDV4().Bytes())) id, _ := utils.UnsafeUUIDGenerator.NewV4()
base := strings.ToLower(base64.RawURLEncoding.EncodeToString(id.Bytes()))
base = strings.ReplaceAll(base, "-", "") base = strings.ReplaceAll(base, "-", "")
base = strings.ReplaceAll(base, "_", "") base = strings.ReplaceAll(base, "_", "")
buf := []byte(base) buf := []byte(base)

View File

@ -27,7 +27,7 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
proxy["skip-cert-verify"] = false proxy["skip-cert-verify"] = false
proxy["tls"] = false proxy["tls"] = false
tls := strings.ToLower(query.Get("security")) tls := strings.ToLower(query.Get("security"))
if strings.HasSuffix(tls, "tls") || tls == "reality" { if strings.HasSuffix(tls, "tls") {
proxy["tls"] = true proxy["tls"] = true
if fingerprint := query.Get("fp"); fingerprint == "" { if fingerprint := query.Get("fp"); fingerprint == "" {
proxy["client-fingerprint"] = "chrome" proxy["client-fingerprint"] = "chrome"
@ -38,12 +38,6 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
if sni := query.Get("sni"); sni != "" { if sni := query.Get("sni"); sni != "" {
proxy["servername"] = sni proxy["servername"] = sni
} }
if realityPublicKey := query.Get("pbk"); realityPublicKey != "" {
proxy["reality-opts"] = map[string]any{
"public-key": realityPublicKey,
"short-id": query.Get("sid"),
}
}
switch query.Get("packetEncoding") { switch query.Get("packetEncoding") {
case "none": case "none":

View File

@ -1,36 +0,0 @@
package net
import (
"net"
)
type CustomAddr interface {
net.Addr
RawAddr() net.Addr
}
type customAddr struct {
networkStr string
addrStr string
rawAddr net.Addr
}
func (a customAddr) Network() string {
return a.networkStr
}
func (a customAddr) String() string {
return a.addrStr
}
func (a customAddr) RawAddr() net.Addr {
return a.rawAddr
}
func NewCustomAddr(networkStr string, addrStr string, rawAddr net.Addr) CustomAddr {
return customAddr{
networkStr: networkStr,
addrStr: addrStr,
rawAddr: rawAddr,
}
}

View File

@ -69,16 +69,6 @@ func (c *BufferedConn) ReadBuffer(buffer *buf.Buffer) (err error) {
return c.ExtendedConn.ReadBuffer(buffer) return c.ExtendedConn.ReadBuffer(buffer)
} }
func (c *BufferedConn) ReadCached() *buf.Buffer { // call in sing/common/bufio.Copy
if c.r.Buffered() > 0 {
length := c.r.Buffered()
b, _ := c.r.Peek(length)
_, _ = c.r.Discard(length)
return buf.As(b)
}
return nil
}
func (c *BufferedConn) Upstream() any { func (c *BufferedConn) Upstream() any {
return c.ExtendedConn return c.ExtendedConn
} }
@ -89,7 +79,3 @@ func (c *BufferedConn) ReaderReplaceable() bool {
} }
return true return true
} }
func (c *BufferedConn) WriterReplaceable() bool {
return true
}

View File

@ -4,12 +4,10 @@ import (
"net" "net"
"runtime" "runtime"
"time" "time"
"github.com/Dreamacro/clash/common/buf"
) )
type refConn struct { type refConn struct {
conn ExtendedConn conn net.Conn
ref any ref any
} }
@ -57,28 +55,8 @@ func (c *refConn) Upstream() any {
return c.conn return c.conn
} }
func (c *refConn) ReadBuffer(buffer *buf.Buffer) error {
defer runtime.KeepAlive(c.ref)
return c.conn.ReadBuffer(buffer)
}
func (c *refConn) WriteBuffer(buffer *buf.Buffer) error {
defer runtime.KeepAlive(c.ref)
return c.conn.WriteBuffer(buffer)
}
func (c *refConn) ReaderReplaceable() bool { // Relay() will handle reference
return true
}
func (c *refConn) WriterReplaceable() bool { // Relay() will handle reference
return true
}
var _ ExtendedConn = (*refConn)(nil)
func NewRefConn(conn net.Conn, ref any) net.Conn { func NewRefConn(conn net.Conn, ref any) net.Conn {
return &refConn{conn: NewExtendedConn(conn), ref: ref} return &refConn{conn: conn, ref: ref}
} }
type refPacketConn struct { type refPacketConn struct {

View File

@ -3,11 +3,9 @@ package net
import ( import (
"context" "context"
"net" "net"
"runtime"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/bufio/deadline"
"github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/network"
) )
@ -19,14 +17,6 @@ type ExtendedConn = network.ExtendedConn
type ExtendedWriter = network.ExtendedWriter type ExtendedWriter = network.ExtendedWriter
type ExtendedReader = network.ExtendedReader type ExtendedReader = network.ExtendedReader
func NewDeadlineConn(conn net.Conn) ExtendedConn {
return deadline.NewFallbackConn(conn)
}
func NewDeadlinePacketConn(pc net.PacketConn) net.PacketConn {
return deadline.NewFallbackPacketConn(bufio.NewPacketConn(pc))
}
func NeedHandshake(conn any) bool { func NeedHandshake(conn any) bool {
if earlyConn, isEarlyConn := common.Cast[network.EarlyConn](conn); isEarlyConn && earlyConn.NeedHandshake() { if earlyConn, isEarlyConn := common.Cast[network.EarlyConn](conn); isEarlyConn && earlyConn.NeedHandshake() {
return true return true
@ -34,11 +24,7 @@ func NeedHandshake(conn any) bool {
return false return false
} }
type CountFunc = network.CountFunc
// Relay copies between left and right bidirectionally. // Relay copies between left and right bidirectionally.
func Relay(leftConn, rightConn net.Conn) { func Relay(leftConn, rightConn net.Conn) {
defer runtime.KeepAlive(leftConn)
defer runtime.KeepAlive(rightConn)
_ = bufio.CopyConn(context.TODO(), leftConn, rightConn) _ = bufio.CopyConn(context.TODO(), leftConn, rightConn)
} }

View File

@ -5,9 +5,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/Dreamacro/clash/common/atomic"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.uber.org/atomic"
) )
func iterator[T any](item []T) chan T { func iterator[T any](item []T) chan T {
@ -45,7 +44,7 @@ func TestObservable_MultiSubscribe(t *testing.T) {
wg.Add(2) wg.Add(2)
waitCh := func(ch <-chan int) { waitCh := func(ch <-chan int) {
for range ch { for range ch {
count.Add(1) count.Inc()
} }
wg.Done() wg.Done()
} }

View File

@ -1,15 +0,0 @@
//go:build with_low_memory
package pool
const (
// io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
RelayBufferSize = 16 * 1024
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
// set to 9000, so the UDP Buffer size set to 16Kib
UDPBufferSize = 8 * 1024
)

View File

@ -1,15 +0,0 @@
//go:build !with_low_memory
package pool
const (
// io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
RelayBufferSize = 20 * 1024
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
// set to 9000, so the UDP Buffer size set to 16Kib
UDPBufferSize = 16 * 1024
)

View File

@ -1,5 +1,17 @@
package pool package pool
const (
// io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
RelayBufferSize = 20 * 1024
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
// set to 9000, so the UDP Buffer size set to 16Kib
UDPBufferSize = 16 * 1024
)
func Get(size int) []byte { func Get(size int) []byte {
return defaultAllocator.Get(size) return defaultAllocator.Get(size)
} }

View File

@ -5,9 +5,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/Dreamacro/clash/common/atomic"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go.uber.org/atomic"
) )
func TestBasic(t *testing.T) { func TestBasic(t *testing.T) {
@ -27,7 +26,7 @@ func TestBasic(t *testing.T) {
go func() { go func() {
_, _, shard := single.Do(call) _, _, shard := single.Do(call)
if shard { if shard {
shardCount.Add(1) shardCount.Inc()
} }
wg.Done() wg.Done()
}() }()

View File

@ -1,9 +0,0 @@
package utils
func Reverse(s string) string {
a := []rune(s)
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
return string(a)
}

View File

@ -1,7 +1,7 @@
package utils package utils
import ( import (
"github.com/gofrs/uuid/v5" "github.com/gofrs/uuid"
"github.com/zhangyunhao116/fastrand" "github.com/zhangyunhao116/fastrand"
) )
@ -13,39 +13,11 @@ func (r fastRandReader) Read(p []byte) (int, error) {
var UnsafeUUIDGenerator = uuid.NewGenWithOptions(uuid.WithRandomReader(fastRandReader{})) var UnsafeUUIDGenerator = uuid.NewGenWithOptions(uuid.WithRandomReader(fastRandReader{}))
func NewUUIDV1() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV1() // fastrand.Read wouldn't cause error, so ignore err is safe
return u
}
func NewUUIDV3(ns uuid.UUID, name string) uuid.UUID {
return UnsafeUUIDGenerator.NewV3(ns, name)
}
func NewUUIDV4() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV4() // fastrand.Read wouldn't cause error, so ignore err is safe
return u
}
func NewUUIDV5(ns uuid.UUID, name string) uuid.UUID {
return UnsafeUUIDGenerator.NewV5(ns, name)
}
func NewUUIDV6() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV6() // fastrand.Read wouldn't cause error, so ignore err is safe
return u
}
func NewUUIDV7() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV7() // fastrand.Read wouldn't cause error, so ignore err is safe
return u
}
// UUIDMap https://github.com/XTLS/Xray-core/issues/158#issue-783294090 // UUIDMap https://github.com/XTLS/Xray-core/issues/158#issue-783294090
func UUIDMap(str string) (uuid.UUID, error) { func UUIDMap(str string) (uuid.UUID, error) {
u, err := uuid.FromString(str) u, err := uuid.FromString(str)
if err != nil { if err != nil {
return NewUUIDV5(uuid.Nil, str), nil return UnsafeUUIDGenerator.NewV5(uuid.Nil, str), nil
} }
return u, nil return u, nil
} }

View File

@ -1,7 +1,7 @@
package utils package utils
import ( import (
"github.com/gofrs/uuid/v5" "github.com/gofrs/uuid"
"reflect" "reflect"
"testing" "testing"
) )

View File

@ -3,7 +3,6 @@ package dialer
import ( import (
"context" "context"
"encoding/binary" "encoding/binary"
"fmt"
"net" "net"
"net/netip" "net/netip"
"syscall" "syscall"
@ -21,19 +20,11 @@ func bind4(handle syscall.Handle, ifaceIdx int) error {
var bytes [4]byte var bytes [4]byte
binary.BigEndian.PutUint32(bytes[:], uint32(ifaceIdx)) binary.BigEndian.PutUint32(bytes[:], uint32(ifaceIdx))
idx := *(*uint32)(unsafe.Pointer(&bytes[0])) idx := *(*uint32)(unsafe.Pointer(&bytes[0]))
err := syscall.SetsockoptInt(handle, syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx)) return syscall.SetsockoptInt(handle, syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx))
if err != nil {
err = fmt.Errorf("bind4: %w", err)
}
return err
} }
func bind6(handle syscall.Handle, ifaceIdx int) error { func bind6(handle syscall.Handle, ifaceIdx int) error {
err := syscall.SetsockoptInt(handle, syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, ifaceIdx) return syscall.SetsockoptInt(handle, syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, ifaceIdx)
if err != nil {
err = fmt.Errorf("bind6: %w", err)
}
return err
} }
func bindControl(ifaceIdx int) controlFn { func bindControl(ifaceIdx int) controlFn {
@ -58,9 +49,9 @@ func bindControl(ifaceIdx int) controlFn {
if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil { if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil {
// try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6 // try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6
if bind4err != nil { if bind4err != nil {
innerErr = fmt.Errorf("%w (%s)", bind6err, bind4err) innerErr = bind6err
} else { } else {
innerErr = nil innerErr = bind4err
} }
} else { } else {
innerErr = bind6err innerErr = bind6err

View File

@ -157,7 +157,7 @@ func concurrentDualStackDialContext(ctx context.Context, network string, ips []n
} }
func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
ipv4s, ipv6s := resolver.SortationAddr(ips) ipv4s, ipv6s := sortationAddr(ips)
preferIPVersion := opt.prefer preferIPVersion := opt.prefer
fallbackTicker := time.NewTicker(fallbackTimeout) fallbackTicker := time.NewTicker(fallbackTimeout)
@ -309,16 +309,27 @@ func parseAddr(ctx context.Context, network, address string, preferResolver reso
return ips, port, nil return ips, port, nil
} }
func sortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
for _, v := range ips {
if v.Is4() { // 4in6 parse was in parseAddr
ipv4s = append(ipv4s, v)
} else {
ipv6s = append(ipv6s, v)
}
}
return
}
type Dialer struct { type Dialer struct {
Opt option opt option
} }
func (d Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { func (d Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return DialContext(ctx, network, address, WithOption(d.Opt)) return DialContext(ctx, network, address, WithOption(d.opt))
} }
func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) { func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
opt := WithOption(d.Opt) opt := WithOption(d.opt)
if rAddrPort.Addr().Unmap().IsLoopback() { if rAddrPort.Addr().Unmap().IsLoopback() {
// avoid "The requested address is not valid in its context." // avoid "The requested address is not valid in its context."
opt = WithInterface("") opt = WithInterface("")
@ -328,5 +339,5 @@ func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddr
func NewDialer(options ...Option) Dialer { func NewDialer(options ...Option) Dialer {
opt := applyOptions(options...) opt := applyOptions(options...)
return Dialer{Opt: *opt} return Dialer{opt: *opt}
} }

View File

@ -4,13 +4,14 @@ import (
"context" "context"
"net" "net"
"github.com/Dreamacro/clash/common/atomic"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
"go.uber.org/atomic"
) )
var ( var (
DefaultOptions []Option DefaultOptions []Option
DefaultInterface = atomic.NewTypedValue[string]("") DefaultInterface = atomic.NewString("")
DefaultRoutingMark = atomic.NewInt32(0) DefaultRoutingMark = atomic.NewInt32(0)
) )

View File

@ -105,22 +105,10 @@ func (c *tfoConn) Upstream() any {
return c.Conn return c.Conn
} }
func (c *tfoConn) NeedAdditionalReadDeadline() bool {
return c.Conn == nil
}
func (c *tfoConn) NeedHandshake() bool { func (c *tfoConn) NeedHandshake() bool {
return c.Conn == nil return c.Conn == nil
} }
func (c *tfoConn) ReaderReplaceable() bool {
return c.Conn != nil
}
func (c *tfoConn) WriterReplaceable() bool {
return c.Conn != nil
}
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) { func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false} dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}

View File

@ -1,10 +1,13 @@
package geodata package geodata
import ( import (
"errors"
"fmt" "fmt"
C "github.com/Dreamacro/clash/constant"
"strings"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log"
) )
type loader struct { type loader struct {
@ -12,7 +15,47 @@ type loader struct {
} }
func (l *loader) LoadGeoSite(list string) ([]*router.Domain, error) { func (l *loader) LoadGeoSite(list string) ([]*router.Domain, error) {
return l.LoadSiteByPath(C.GeositeName, list) return l.LoadGeoSiteWithAttr(C.GeositeName, list)
}
func (l *loader) LoadGeoSiteWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {
parts := strings.Split(siteWithAttr, "@")
if len(parts) == 0 {
return nil, errors.New("empty rule")
}
list := strings.TrimSpace(parts[0])
attrVal := parts[1:]
if len(list) == 0 {
return nil, fmt.Errorf("empty listname in rule: %s", siteWithAttr)
}
domains, err := l.LoadSiteByPath(file, list)
if err != nil {
return nil, err
}
attrs := parseAttrs(attrVal)
if attrs.IsEmpty() {
if strings.Contains(siteWithAttr, "@") {
log.Warnln("empty attribute list: %s", siteWithAttr)
}
return domains, nil
}
filteredDomains := make([]*router.Domain, 0, len(domains))
hasAttrMatched := false
for _, domain := range domains {
if attrs.Match(domain) {
hasAttrMatched = true
filteredDomains = append(filteredDomains, domain)
}
}
if !hasAttrMatched {
log.Warnln("attribute match no rule: geosite: %s", siteWithAttr)
}
return filteredDomains, nil
} }
func (l *loader) LoadGeoIP(country string) ([]*router.CIDR, error) { func (l *loader) LoadGeoIP(country string) ([]*router.CIDR, error) {

View File

@ -14,5 +14,6 @@ type LoaderImplementation interface {
type Loader interface { type Loader interface {
LoaderImplementation LoaderImplementation
LoadGeoSite(list string) ([]*router.Domain, error) LoadGeoSite(list string) ([]*router.Domain, error)
LoadGeoSiteWithAttr(file string, siteWithAttr string) ([]*router.Domain, error)
LoadGeoIP(country string) ([]*router.CIDR, error) LoadGeoIP(country string) ([]*router.CIDR, error)
} }

View File

@ -1,17 +1,13 @@
package geodata package geodata
import ( import (
"context"
"fmt" "fmt"
"io"
"net/http"
"os"
"time"
clashHttp "github.com/Dreamacro/clash/component/http"
"github.com/Dreamacro/clash/component/mmdb" "github.com/Dreamacro/clash/component/mmdb"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"io"
"net/http"
"os"
) )
var initGeoSite bool var initGeoSite bool
@ -42,9 +38,7 @@ func InitGeoSite() error {
} }
func downloadGeoSite(path string) (err error) { func downloadGeoSite(path string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) resp, err := http.Get(C.GeoSiteUrl)
defer cancel()
resp, err := clashHttp.HttpRequest(ctx, C.GeoSiteUrl, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
if err != nil { if err != nil {
return return
} }
@ -61,9 +55,7 @@ func downloadGeoSite(path string) (err error) {
} }
func downloadGeoIP(path string) (err error) { func downloadGeoIP(path string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) resp, err := http.Get(C.GeoIpUrl)
defer cancel()
resp, err := clashHttp.HttpRequest(ctx, C.GeoIpUrl, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
if err != nil { if err != nil {
return return
} }

View File

@ -118,7 +118,7 @@ func (g GeoSiteCache) Unmarshal(filename, code string) (*router.GeoSite, error)
case errFailedToReadBytes, errFailedToReadExpectedLenBytes, case errFailedToReadBytes, errFailedToReadExpectedLenBytes,
errInvalidGeodataFile, errInvalidGeodataVarintLength: errInvalidGeodataFile, errInvalidGeodataVarintLength:
log.Warnln("failed to decode geosite file: %s%s", filename, ", fallback to the original ReadFile method") log.Warnln("failed to decode geoip file: %s%s", filename, ", fallback to the original ReadFile method")
geositeBytes, err = os.ReadFile(asset) geositeBytes, err = os.ReadFile(asset)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -1,14 +1,9 @@
package geodata package geodata
import ( import (
"errors"
"fmt" "fmt"
"golang.org/x/sync/singleflight"
"strings"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
) )
var geoLoaderName = "memconservative" var geoLoaderName = "memconservative"
@ -39,8 +34,6 @@ func Verify(name string) error {
} }
} }
var loadGeoSiteMatcherSF = singleflight.Group{}
func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) { func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) {
if len(countryCode) == 0 { if len(countryCode) == 0 {
return nil, 0, fmt.Errorf("country code could not be empty") return nil, 0, fmt.Errorf("country code could not be empty")
@ -51,52 +44,15 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error)
not = true not = true
countryCode = countryCode[1:] countryCode = countryCode[1:]
} }
countryCode = strings.ToLower(countryCode)
parts := strings.Split(countryCode, "@") geoLoader, err := GetGeoDataLoader(geoLoaderName)
if len(parts) == 0 {
return nil, 0, errors.New("empty rule")
}
listName := strings.TrimSpace(parts[0])
attrVal := parts[1:]
if len(listName) == 0 {
return nil, 0, fmt.Errorf("empty listname in rule: %s", countryCode)
}
v, err, shared := loadGeoSiteMatcherSF.Do(listName, func() (interface{}, error) {
geoLoader, err := GetGeoDataLoader(geoLoaderName)
if err != nil {
return nil, err
}
return geoLoader.LoadGeoSite(listName)
})
if err != nil { if err != nil {
if !shared {
loadGeoSiteMatcherSF.Forget(listName) // don't store the error result
}
return nil, 0, err return nil, 0, err
} }
domains := v.([]*router.Domain)
attrs := parseAttrs(attrVal) domains, err := geoLoader.LoadGeoSite(countryCode)
if attrs.IsEmpty() { if err != nil {
if strings.Contains(countryCode, "@") { return nil, 0, err
log.Warnln("empty attribute list: %s", countryCode)
}
} else {
filteredDomains := make([]*router.Domain, 0, len(domains))
hasAttrMatched := false
for _, domain := range domains {
if attrs.Match(domain) {
hasAttrMatched = true
filteredDomains = append(filteredDomains, domain)
}
}
if !hasAttrMatched {
log.Warnln("attribute match no rule: geosite: %s", countryCode)
}
domains = filteredDomains
} }
/** /**
@ -112,34 +68,25 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error)
return matcher, len(domains), nil return matcher, len(domains), nil
} }
var loadGeoIPMatcherSF = singleflight.Group{}
func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) { func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
if len(country) == 0 { if len(country) == 0 {
return nil, 0, fmt.Errorf("country code could not be empty") return nil, 0, fmt.Errorf("country code could not be empty")
} }
geoLoader, err := GetGeoDataLoader(geoLoaderName)
if err != nil {
return nil, 0, err
}
not := false not := false
if country[0] == '!' { if country[0] == '!' {
not = true not = true
country = country[1:] country = country[1:]
} }
country = strings.ToLower(country)
v, err, shared := loadGeoIPMatcherSF.Do(country, func() (interface{}, error) { records, err := geoLoader.LoadGeoIP(country)
geoLoader, err := GetGeoDataLoader(geoLoaderName)
if err != nil {
return nil, err
}
return geoLoader.LoadGeoIP(country)
})
if err != nil { if err != nil {
if !shared {
loadGeoIPMatcherSF.Forget(country) // don't store the error result
}
return nil, 0, err return nil, 0, err
} }
records := v.([]*router.CIDR)
geoIP := &router.GeoIP{ geoIP := &router.GeoIP{
CountryCode: country, CountryCode: country,
@ -151,10 +98,6 @@ func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
return matcher, len(records), nil return matcher, len(records), nil
} }
func ClearCache() {
loadGeoSiteMatcherSF = singleflight.Group{}
loadGeoIPMatcherSF = singleflight.Group{}
}

View File

@ -2,15 +2,14 @@ package http
import ( import (
"context" "context"
"github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/listener/inner"
"io" "io"
"net" "net"
"net/http" "net/http"
URL "net/url" URL "net/url"
"strings" "strings"
"time" "time"
"github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/listener/inner"
) )
const ( const (
@ -53,7 +52,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
conn := inner.HandleTcp(address, "") conn := inner.HandleTcp(address, urlRes.Hostname())
return conn, nil return conn, nil
}, },
TLSClientConfig: tls.GetDefaultTLSConfig(), TLSClientConfig: tls.GetDefaultTLSConfig(),

View File

@ -1,18 +1,14 @@
package mmdb package mmdb
import ( import (
"context" "github.com/oschwald/geoip2-golang"
"io" "io"
"net/http" "net/http"
"os" "os"
"sync" "sync"
"time"
clashHttp "github.com/Dreamacro/clash/component/http"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/oschwald/geoip2-golang"
) )
var ( var (
@ -51,9 +47,7 @@ func Instance() *geoip2.Reader {
} }
func DownloadMMDB(path string) (err error) { func DownloadMMDB(path string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) resp, err := http.Get(C.MmdbUrl)
defer cancel()
resp, err := clashHttp.HttpRequest(ctx, C.MmdbUrl, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
if err != nil { if err != nil {
return return
} }

View File

@ -1,7 +1,7 @@
package profile package profile
import ( import (
"github.com/Dreamacro/clash/common/atomic" "go.uber.org/atomic"
) )
// StoreSelected is a global switch for storing selected proxy to cache // StoreSelected is a global switch for storing selected proxy to cache

View File

@ -1,96 +0,0 @@
package proxydialer
import (
"context"
"errors"
"fmt"
"net"
"net/netip"
"strings"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/tunnel"
"github.com/Dreamacro/clash/tunnel/statistic"
)
type proxyDialer struct {
proxy C.ProxyAdapter
dialer C.Dialer
statistic bool
}
func New(proxy C.ProxyAdapter, dialer C.Dialer, statistic bool) C.Dialer {
return proxyDialer{proxy: proxy, dialer: dialer, statistic: statistic}
}
func NewByName(proxyName string, dialer C.Dialer) (C.Dialer, error) {
proxies := tunnel.Proxies()
if proxy, ok := proxies[proxyName]; ok {
return New(proxy, dialer, true), nil
}
return nil, fmt.Errorf("proxyName[%s] not found", proxyName)
}
func (p proxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
currentMeta := &C.Metadata{Type: C.INNER}
if err := currentMeta.SetRemoteAddress(address); err != nil {
return nil, err
}
if strings.Contains(network, "udp") { // using in wireguard outbound
if !currentMeta.Resolved() {
ip, err := resolver.ResolveIP(ctx, currentMeta.Host)
if err != nil {
return nil, errors.New("can't resolve ip")
}
currentMeta.DstIP = ip
}
pc, err := p.listenPacket(ctx, currentMeta)
if err != nil {
return nil, err
}
return N.NewBindPacketConn(pc, currentMeta.UDPAddr()), nil
}
var conn C.Conn
var err error
if d, ok := p.dialer.(dialer.Dialer); ok { // first using old function to let mux work
conn, err = p.proxy.DialContext(ctx, currentMeta, dialer.WithOption(d.Opt))
} else {
conn, err = p.proxy.DialContextWithDialer(ctx, p.dialer, currentMeta)
}
if err != nil {
return nil, err
}
if p.statistic {
conn = statistic.NewTCPTracker(conn, statistic.DefaultManager, currentMeta, nil, 0, 0, false)
}
return conn, err
}
func (p proxyDialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
currentMeta := &C.Metadata{Type: C.INNER}
if err := currentMeta.SetRemoteAddress(address); err != nil {
return nil, err
}
return p.listenPacket(ctx, currentMeta)
}
func (p proxyDialer) listenPacket(ctx context.Context, currentMeta *C.Metadata) (C.PacketConn, error) {
var pc C.PacketConn
var err error
currentMeta.NetWork = C.UDP
if d, ok := p.dialer.(dialer.Dialer); ok { // first using old function to let mux work
pc, err = p.proxy.ListenPacketContext(ctx, currentMeta, dialer.WithOption(d.Opt))
} else {
pc, err = p.proxy.ListenPacketWithDialer(ctx, p.dialer, currentMeta)
}
if err != nil {
return nil, err
}
if p.statistic {
pc = statistic.NewUDPTracker(pc, statistic.DefaultManager, currentMeta, nil, 0, 0, false)
}
return pc, nil
}

View File

@ -2,12 +2,12 @@ package resolver
import ( import (
"errors" "errors"
"math/rand"
"net/netip" "net/netip"
"strings" "strings"
"github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
"github.com/zhangyunhao116/fastrand"
) )
type Hosts struct { type Hosts struct {
@ -20,7 +20,6 @@ func NewHosts(hosts *trie.DomainTrie[HostValue]) Hosts {
} }
} }
// Return the search result and whether to match the parameter `isDomain`
func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) { func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) {
value := h.DomainTrie.Search(domain) value := h.DomainTrie.Search(domain)
if value == nil { if value == nil {
@ -109,5 +108,5 @@ func (hv HostValue) RandIP() (netip.Addr, error) {
if hv.IsDomain { if hv.IsDomain {
return netip.Addr{}, errors.New("value type is error") return netip.Addr{}, errors.New("value type is error")
} }
return hv.IPs[fastrand.Intn(len(hv.IPs))], nil return hv.IPs[rand.Intn(len(hv.IPs)-1)], nil
} }

View File

@ -44,8 +44,10 @@ type Resolver interface {
LookupIP(ctx context.Context, host string) (ips []netip.Addr, err error) LookupIP(ctx context.Context, host string) (ips []netip.Addr, err error)
LookupIPv4(ctx context.Context, host string) (ips []netip.Addr, err error) LookupIPv4(ctx context.Context, host string) (ips []netip.Addr, err error)
LookupIPv6(ctx context.Context, host string) (ips []netip.Addr, err error) LookupIPv6(ctx context.Context, host string) (ips []netip.Addr, err error)
ResolveIP(ctx context.Context, host string) (ip netip.Addr, err error)
ResolveIPv4(ctx context.Context, host string) (ip netip.Addr, err error)
ResolveIPv6(ctx context.Context, host string) (ip netip.Addr, err error)
ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error) ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error)
Invalid() bool
} }
// LookupIPv4WithResolver same as LookupIPv4, but with a resolver // LookupIPv4WithResolver same as LookupIPv4, but with a resolver
@ -66,7 +68,7 @@ func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]net
return []netip.Addr{}, ErrIPVersion return []netip.Addr{}, ErrIPVersion
} }
if r != nil && r.Invalid() { if r != nil {
return r.LookupIPv4(ctx, host) return r.LookupIPv4(ctx, host)
} }
@ -122,7 +124,7 @@ func LookupIPv6WithResolver(ctx context.Context, host string, r Resolver) ([]net
return nil, ErrIPVersion return nil, ErrIPVersion
} }
if r != nil && r.Invalid() { if r != nil {
return r.LookupIPv6(ctx, host) return r.LookupIPv6(ctx, host)
} }
@ -162,7 +164,7 @@ func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip
return node.IPs, nil return node.IPs, nil
} }
if r != nil && r.Invalid() { if r != nil {
if DisableIPv6 { if DisableIPv6 {
return r.LookupIPv4(ctx, host) return r.LookupIPv4(ctx, host)
} }
@ -198,14 +200,10 @@ func ResolveIPWithResolver(ctx context.Context, host string, r Resolver) (netip.
} else if len(ips) == 0 { } else if len(ips) == 0 {
return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host) return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host)
} }
ipv4s, ipv6s := SortationAddr(ips) return ips[fastrand.Intn(len(ips))], nil
if len(ipv4s) > 0 {
return ipv4s[fastrand.Intn(len(ipv4s))], nil
}
return ipv6s[fastrand.Intn(len(ipv6s))], nil
} }
// ResolveIP with a host, return ip and priority return TypeA // ResolveIP with a host, return ip
func ResolveIP(ctx context.Context, host string) (netip.Addr, error) { func ResolveIP(ctx context.Context, host string) (netip.Addr, error) {
return ResolveIPWithResolver(ctx, host, DefaultResolver) return ResolveIPWithResolver(ctx, host, DefaultResolver)
} }
@ -266,14 +264,3 @@ func LookupIPProxyServerHost(ctx context.Context, host string) ([]netip.Addr, er
} }
return LookupIP(ctx, host) return LookupIP(ctx, host)
} }
func SortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
for _, v := range ips {
if v.Unmap().Is4() {
ipv4s = append(ipv4s, v)
} else {
ipv6s = append(ipv6s, v)
}
}
return
}

View File

@ -26,18 +26,18 @@ var (
var Dispatcher *SnifferDispatcher var Dispatcher *SnifferDispatcher
type SnifferDispatcher struct { type SnifferDispatcher struct {
enable bool enable bool
sniffers map[sniffer.Sniffer]SnifferConfig sniffers map[sniffer.Sniffer]SnifferConfig
forceDomain *trie.DomainSet forceDomain *trie.DomainTrie[struct{}]
skipSNI *trie.DomainSet skipSNI *trie.DomainTrie[struct{}]
skipList *cache.LruCache[string, uint8] skipList *cache.LruCache[string, uint8]
rwMux sync.RWMutex rwMux sync.RWMutex
forceDnsMapping bool forceDnsMapping bool
parsePureIp bool parsePureIp bool
} }
func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) { func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) {
if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Has(metadata.Host) || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) { if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Search(metadata.Host) != nil || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) {
port, err := strconv.ParseUint(metadata.DstPort, 10, 16) port, err := strconv.ParseUint(metadata.DstPort, 10, 16)
if err != nil { if err != nil {
log.Debugln("[Sniffer] Dst port is error") log.Debugln("[Sniffer] Dst port is error")
@ -74,7 +74,7 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort) log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
return return
} else { } else {
if sd.skipSNI.Has(host) { if sd.skipSNI.Search(host) != nil {
log.Debugln("[Sniffer] Skip sni[%s]", host) log.Debugln("[Sniffer] Skip sni[%s]", host)
return return
} }
@ -166,8 +166,8 @@ func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
return &dispatcher, nil return &dispatcher, nil
} }
func NewSnifferDispatcher(snifferConfig map[sniffer.Type]SnifferConfig, func NewSnifferDispatcher(snifferConfig map[sniffer.Type]SnifferConfig, forceDomain *trie.DomainTrie[struct{}],
forceDomain *trie.DomainSet, skipSNI *trie.DomainSet, skipSNI *trie.DomainTrie[struct{}],
forceDnsMapping bool, parsePureIp bool) (*SnifferDispatcher, error) { forceDnsMapping bool, parsePureIp bool) (*SnifferDispatcher, error) {
dispatcher := SnifferDispatcher{ dispatcher := SnifferDispatcher{
enable: true, enable: true,

View File

@ -14,8 +14,8 @@ import (
xtls "github.com/xtls/go" xtls "github.com/xtls/go"
) )
var trustCerts []*x509.Certificate var trustCert, _ = x509.SystemCertPool()
var certPool *x509.CertPool
var mutex sync.RWMutex var mutex sync.RWMutex
var errNotMacth error = errors.New("certificate fingerprints do not match") var errNotMacth error = errors.New("certificate fingerprints do not match")
@ -25,38 +25,16 @@ func AddCertificate(certificate string) error {
if certificate == "" { if certificate == "" {
return fmt.Errorf("certificate is empty") return fmt.Errorf("certificate is empty")
} }
if cert, err := x509.ParseCertificate([]byte(certificate)); err == nil { if ok := trustCert.AppendCertsFromPEM([]byte(certificate)); !ok {
trustCerts = append(trustCerts, cert)
return nil
} else {
return fmt.Errorf("add certificate failed") return fmt.Errorf("add certificate failed")
} }
return nil
} }
func ResetCertificate() { func ResetCertificate() {
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
trustCerts = nil trustCert, _ = x509.SystemCertPool()
}
func getCertPool() *x509.CertPool {
if len(trustCerts) == 0 {
return nil
}
if certPool == nil {
mutex.Lock()
defer mutex.Unlock()
if certPool != nil {
return certPool
}
certPool, err := x509.SystemCertPool()
if err == nil {
for _, cert := range trustCerts {
certPool.AddCert(cert)
}
}
}
return certPool
} }
func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
@ -106,13 +84,12 @@ func GetSpecifiedFingerprintTLSConfig(tlsConfig *tls.Config, fingerprint string)
} }
func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config { func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config {
certPool := getCertPool()
if tlsConfig == nil { if tlsConfig == nil {
return &tls.Config{ return &tls.Config{
RootCAs: certPool, RootCAs: trustCert,
} }
} }
tlsConfig.RootCAs = certPool tlsConfig.RootCAs = trustCert
return tlsConfig return tlsConfig
} }
@ -129,13 +106,12 @@ func GetSpecifiedFingerprintXTLSConfig(tlsConfig *xtls.Config, fingerprint strin
} }
func GetGlobalXTLSConfig(tlsConfig *xtls.Config) *xtls.Config { func GetGlobalXTLSConfig(tlsConfig *xtls.Config) *xtls.Config {
certPool := getCertPool()
if tlsConfig == nil { if tlsConfig == nil {
return &xtls.Config{ return &xtls.Config{
RootCAs: certPool, RootCAs: trustCert,
} }
} }
tlsConfig.RootCAs = certPool tlsConfig.RootCAs = trustCert
return tlsConfig return tlsConfig
} }

View File

@ -44,6 +44,7 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
} }
uConfig := &utls.Config{ uConfig := &utls.Config{
ServerName: tlsConfig.ServerName, ServerName: tlsConfig.ServerName,
NextProtos: tlsConfig.NextProtos,
InsecureSkipVerify: true, InsecureSkipVerify: true,
SessionTicketsDisabled: true, SessionTicketsDisabled: true,
VerifyPeerCertificate: verifier.VerifyPeerCertificate, VerifyPeerCertificate: verifier.VerifyPeerCertificate,
@ -61,16 +62,20 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
} }
hello := uConn.HandshakeState.Hello hello := uConn.HandshakeState.Hello
for i := range hello.SessionId { // https://github.com/golang/go/issues/5373 hello.SessionId = make([]byte, 32)
hello.SessionId[i] = 0
}
copy(hello.Raw[39:], hello.SessionId) copy(hello.Raw[39:], hello.SessionId)
binary.BigEndian.PutUint64(hello.SessionId, uint64(time.Now().Unix())) var nowTime time.Time
if uConfig.Time != nil {
nowTime = uConfig.Time()
} else {
nowTime = time.Now()
}
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
hello.SessionId[0] = 1 hello.SessionId[0] = 1
hello.SessionId[1] = 8 hello.SessionId[1] = 7
hello.SessionId[2] = 0 hello.SessionId[2] = 5
copy(hello.SessionId[8:], realityConfig.ShortID[:]) copy(hello.SessionId[8:], realityConfig.ShortID[:])
//log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16]) //log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16])
@ -125,7 +130,7 @@ func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.C
return return
} }
//_, _ = io.Copy(io.Discard, response.Body) //_, _ = io.Copy(io.Discard, response.Body)
time.Sleep(time.Duration(5+fastrand.Int63n(10)) * time.Second) time.Sleep(time.Duration(5 + fastrand.Int63n(10)))
response.Body.Close() response.Body.Close()
client.CloseIdleConnections() client.CloseIdleConnections()
} }

View File

@ -45,13 +45,8 @@ func GetFingerprint(ClientFingerprint string) (UClientHelloID, bool) {
} }
fingerprint, ok := Fingerprints[ClientFingerprint] fingerprint, ok := Fingerprints[ClientFingerprint]
if ok { log.Debugln("use specified fingerprint:%s", fingerprint.Client)
log.Debugln("use specified fingerprint:%s", fingerprint.Client) return fingerprint, ok
return fingerprint, ok
} else {
log.Warnln("wrong ClientFingerprint:%s", ClientFingerprint)
return UClientHelloID{}, false
}
} }
func RollFingerprint() (UClientHelloID, bool) { func RollFingerprint() (UClientHelloID, bool) {
@ -94,6 +89,7 @@ func copyConfig(c *tls.Config) *utls.Config {
return &utls.Config{ return &utls.Config{
RootCAs: c.RootCAs, RootCAs: c.RootCAs,
ServerName: c.ServerName, ServerName: c.ServerName,
NextProtos: c.NextProtos,
InsecureSkipVerify: c.InsecureSkipVerify, InsecureSkipVerify: c.InsecureSkipVerify,
VerifyPeerCertificate: c.VerifyPeerCertificate, VerifyPeerCertificate: c.VerifyPeerCertificate,
} }
@ -132,7 +128,10 @@ func SetGlobalUtlsClient(Client string) {
} }
func HaveGlobalFingerprint() bool { func HaveGlobalFingerprint() bool {
return len(initUtlsClient) != 0 && initUtlsClient != "none" if len(initUtlsClient) != 0 && initUtlsClient != "none" {
return true
}
return false
} }
func GetGlobalFingerprint() string { func GetGlobalFingerprint() string {

View File

@ -25,7 +25,7 @@ func ValidAndSplitDomain(domain string) ([]string, bool) {
if domain != "" && domain[len(domain)-1] == '.' { if domain != "" && domain[len(domain)-1] == '.' {
return nil, false return nil, false
} }
domain = strings.ToLower(domain)
parts := strings.Split(domain, domainStep) parts := strings.Split(domain, domainStep)
if len(parts) == 1 { if len(parts) == 1 {
if parts[0] == "" { if parts[0] == "" {
@ -123,33 +123,6 @@ func (t *DomainTrie[T]) Optimize() {
t.root.optimize() t.root.optimize()
} }
func (t *DomainTrie[T]) Foreach(print func(domain string, data T)) {
for key, data := range t.root.getChildren() {
recursion([]string{key}, data, print)
if data != nil && data.inited {
print(joinDomain([]string{key}), data.data)
}
}
}
func recursion[T any](items []string, node *Node[T], fn func(domain string, data T)) {
for key, data := range node.getChildren() {
newItems := append([]string{key}, items...)
if data != nil && data.inited {
domain := joinDomain(newItems)
if domain[0] == domainStepByte {
domain = complexWildcard + domain
}
fn(domain, data.Data())
}
recursion(newItems, data, fn)
}
}
func joinDomain(items []string) string {
return strings.Join(items, domainStep)
}
// New returns a new, empty Trie. // New returns a new, empty Trie.
func New[T any]() *DomainTrie[T] { func New[T any]() *DomainTrie[T] {
return &DomainTrie[T]{root: newNode[T]()} return &DomainTrie[T]{root: newNode[T]()}

View File

@ -1,174 +0,0 @@
package trie
// Package succinct provides several succinct data types.
// Modify from https://github.com/openacid/succinct/blob/d4684c35d123f7528b14e03c24327231723db704/sskv.go
import (
"sort"
"strings"
"github.com/Dreamacro/clash/common/utils"
"github.com/openacid/low/bitmap"
)
const (
complexWildcardByte = byte('+')
wildcardByte = byte('*')
domainStepByte = byte('.')
)
type DomainSet struct {
leaves, labelBitmap []uint64
labels []byte
ranks, selects []int32
}
// NewDomainSet creates a new *DomainSet struct, from a DomainTrie.
func (t *DomainTrie[T]) NewDomainSet() *DomainSet {
reserveDomains := make([]string, 0)
t.Foreach(func(domain string, data T) {
reserveDomains = append(reserveDomains, utils.Reverse(domain))
})
// ensure that the same prefix is continuous
// and according to the ascending sequence of length
sort.Strings(reserveDomains)
keys := reserveDomains
if len(keys) == 0 {
return nil
}
ss := &DomainSet{}
lIdx := 0
type qElt struct{ s, e, col int }
queue := []qElt{{0, len(keys), 0}}
for i := 0; i < len(queue); i++ {
elt := queue[i]
if elt.col == len(keys[elt.s]) {
elt.s++
// a leaf node
setBit(&ss.leaves, i, 1)
}
for j := elt.s; j < elt.e; {
frm := j
for ; j < elt.e && keys[j][elt.col] == keys[frm][elt.col]; j++ {
}
queue = append(queue, qElt{frm, j, elt.col + 1})
ss.labels = append(ss.labels, keys[frm][elt.col])
setBit(&ss.labelBitmap, lIdx, 0)
lIdx++
}
setBit(&ss.labelBitmap, lIdx, 1)
lIdx++
}
ss.init()
return ss
}
// Has query for a key and return whether it presents in the DomainSet.
func (ss *DomainSet) Has(key string) bool {
if ss == nil {
return false
}
key = utils.Reverse(key)
key = strings.ToLower(key)
// no more labels in this node
// skip character matching
// go to next level
nodeId, bmIdx := 0, 0
type wildcardCursor struct {
bmIdx, index int
}
stack := make([]wildcardCursor, 0)
for i := 0; i < len(key); i++ {
RESTART:
c := key[i]
for ; ; bmIdx++ {
if getBit(ss.labelBitmap, bmIdx) != 0 {
if len(stack) > 0 {
cursor := stack[len(stack)-1]
stack = stack[0 : len(stack)-1]
// back wildcard and find next node
nextNodeId := countZeros(ss.labelBitmap, ss.ranks, cursor.bmIdx+1)
nextBmIdx := selectIthOne(ss.labelBitmap, ss.ranks, ss.selects, nextNodeId-1) + 1
j := cursor.index
for ; j < len(key) && key[j] != domainStepByte; j++ {
}
if j == len(key) {
if getBit(ss.leaves, nextNodeId) != 0 {
return true
} else {
goto RESTART
}
}
for ; nextBmIdx-nextNodeId < len(ss.labels); nextBmIdx++ {
if ss.labels[nextBmIdx-nextNodeId] == domainStepByte {
bmIdx = nextBmIdx
nodeId = nextNodeId
i = j
goto RESTART
}
}
}
return false
}
// handle wildcard for domain
if ss.labels[bmIdx-nodeId] == complexWildcardByte {
return true
} else if ss.labels[bmIdx-nodeId] == wildcardByte {
cursor := wildcardCursor{}
cursor.bmIdx = bmIdx
cursor.index = i
stack = append(stack, cursor)
} else if ss.labels[bmIdx-nodeId] == c {
break
}
}
nodeId = countZeros(ss.labelBitmap, ss.ranks, bmIdx+1)
bmIdx = selectIthOne(ss.labelBitmap, ss.ranks, ss.selects, nodeId-1) + 1
}
return getBit(ss.leaves, nodeId) != 0
}
func setBit(bm *[]uint64, i int, v int) {
for i>>6 >= len(*bm) {
*bm = append(*bm, 0)
}
(*bm)[i>>6] |= uint64(v) << uint(i&63)
}
func getBit(bm []uint64, i int) uint64 {
return bm[i>>6] & (1 << uint(i&63))
}
// init builds pre-calculated cache to speed up rank() and select()
func (ss *DomainSet) init() {
ss.selects, ss.ranks = bitmap.IndexSelect32R64(ss.labelBitmap)
}
// countZeros counts the number of "0" in a bitmap before the i-th bit(excluding
// the i-th bit) on behalf of rank index.
// E.g.:
//
// countZeros("010010", 4) == 3
// // 012345
func countZeros(bm []uint64, ranks []int32, i int) int {
a, _ := bitmap.Rank64(bm, ranks, int32(i))
return i - int(a)
}
// selectIthOne returns the index of the i-th "1" in a bitmap, on behalf of rank
// and select indexes.
// E.g.:
//
// selectIthOne("010010", 1) == 4
// // 012345
func selectIthOne(bm []uint64, ranks, selects []int32, i int) int {
a, _ := bitmap.Select32R64(bm, selects, ranks, int32(i))
return int(a)
}

View File

@ -1,85 +0,0 @@
package trie_test
import (
"testing"
"github.com/Dreamacro/clash/component/trie"
"github.com/stretchr/testify/assert"
)
func TestDomainSet(t *testing.T) {
tree := trie.New[struct{}]()
domainSet := []string{
"baidu.com",
"google.com",
"www.google.com",
"test.a.net",
"test.a.oc",
"Mijia Cloud",
".qq.com",
"+.cn",
}
for _, domain := range domainSet {
assert.NoError(t, tree.Insert(domain, struct{}{}))
}
set := tree.NewDomainSet()
assert.NotNil(t, set)
assert.True(t, set.Has("test.cn"))
assert.True(t, set.Has("cn"))
assert.True(t, set.Has("Mijia Cloud"))
assert.True(t, set.Has("test.a.net"))
assert.True(t, set.Has("www.qq.com"))
assert.True(t, set.Has("google.com"))
assert.False(t, set.Has("qq.com"))
assert.False(t, set.Has("www.baidu.com"))
}
func TestDomainSetComplexWildcard(t *testing.T) {
tree := trie.New[struct{}]()
domainSet := []string{
"+.baidu.com",
"+.a.baidu.com",
"www.baidu.com",
"+.bb.baidu.com",
"test.a.net",
"test.a.oc",
"www.qq.com",
}
for _, domain := range domainSet {
assert.NoError(t, tree.Insert(domain, struct{}{}))
}
set := tree.NewDomainSet()
assert.NotNil(t, set)
assert.False(t, set.Has("google.com"))
assert.True(t, set.Has("www.baidu.com"))
assert.True(t, set.Has("test.test.baidu.com"))
}
func TestDomainSetWildcard(t *testing.T) {
tree := trie.New[struct{}]()
domainSet := []string{
"*.*.*.baidu.com",
"www.baidu.*",
"stun.*.*",
"*.*.qq.com",
"test.*.baidu.com",
"*.apple.com",
}
for _, domain := range domainSet {
assert.NoError(t, tree.Insert(domain, struct{}{}))
}
set := tree.NewDomainSet()
assert.NotNil(t, set)
assert.True(t, set.Has("www.baidu.com"))
assert.True(t, set.Has("test.test.baidu.com"))
assert.True(t, set.Has("test.test.qq.com"))
assert.True(t, set.Has("stun.ab.cd"))
assert.False(t, set.Has("test.baidu.com"))
assert.False(t, set.Has("www.google.com"))
assert.False(t, set.Has("a.www.google.com"))
assert.False(t, set.Has("test.qq.com"))
assert.False(t, set.Has("test.test.test.qq.com"))
}

View File

@ -1,17 +1,16 @@
package trie_test package trie
import ( import (
"net/netip" "net/netip"
"testing" "testing"
"github.com/Dreamacro/clash/component/trie"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var localIP = netip.AddrFrom4([4]byte{127, 0, 0, 1}) var localIP = netip.AddrFrom4([4]byte{127, 0, 0, 1})
func TestTrie_Basic(t *testing.T) { func TestTrie_Basic(t *testing.T) {
tree := trie.New[netip.Addr]() tree := New[netip.Addr]()
domains := []string{ domains := []string{
"example.com", "example.com",
"google.com", "google.com",
@ -19,7 +18,7 @@ func TestTrie_Basic(t *testing.T) {
} }
for _, domain := range domains { for _, domain := range domains {
assert.NoError(t, tree.Insert(domain, localIP)) tree.Insert(domain, localIP)
} }
node := tree.Search("example.com") node := tree.Search("example.com")
@ -32,7 +31,7 @@ func TestTrie_Basic(t *testing.T) {
} }
func TestTrie_Wildcard(t *testing.T) { func TestTrie_Wildcard(t *testing.T) {
tree := trie.New[netip.Addr]() tree := New[netip.Addr]()
domains := []string{ domains := []string{
"*.example.com", "*.example.com",
"sub.*.example.com", "sub.*.example.com",
@ -48,7 +47,7 @@ func TestTrie_Wildcard(t *testing.T) {
} }
for _, domain := range domains { for _, domain := range domains {
assert.NoError(t, tree.Insert(domain, localIP)) tree.Insert(domain, localIP)
} }
assert.NotNil(t, tree.Search("sub.example.com")) assert.NotNil(t, tree.Search("sub.example.com"))
@ -65,7 +64,7 @@ func TestTrie_Wildcard(t *testing.T) {
} }
func TestTrie_Priority(t *testing.T) { func TestTrie_Priority(t *testing.T) {
tree := trie.New[int]() tree := New[int]()
domains := []string{ domains := []string{
".dev", ".dev",
"example.dev", "example.dev",
@ -80,7 +79,7 @@ func TestTrie_Priority(t *testing.T) {
} }
for idx, domain := range domains { for idx, domain := range domains {
assert.NoError(t, tree.Insert(domain, idx+1)) tree.Insert(domain, idx+1)
} }
assertFn("test.dev", 1) assertFn("test.dev", 1)
@ -91,8 +90,8 @@ func TestTrie_Priority(t *testing.T) {
} }
func TestTrie_Boundary(t *testing.T) { func TestTrie_Boundary(t *testing.T) {
tree := trie.New[netip.Addr]() tree := New[netip.Addr]()
assert.NoError(t, tree.Insert("*.dev", localIP)) tree.Insert("*.dev", localIP)
assert.NotNil(t, tree.Insert(".", localIP)) assert.NotNil(t, tree.Insert(".", localIP))
assert.NotNil(t, tree.Insert("..dev", localIP)) assert.NotNil(t, tree.Insert("..dev", localIP))
@ -100,29 +99,9 @@ func TestTrie_Boundary(t *testing.T) {
} }
func TestTrie_WildcardBoundary(t *testing.T) { func TestTrie_WildcardBoundary(t *testing.T) {
tree := trie.New[netip.Addr]() tree := New[netip.Addr]()
assert.NoError(t, tree.Insert("+.*", localIP)) tree.Insert("+.*", localIP)
assert.NoError(t, tree.Insert("stun.*.*.*", localIP)) tree.Insert("stun.*.*.*", localIP)
assert.NotNil(t, tree.Search("example.com")) assert.NotNil(t, tree.Search("example.com"))
} }
func TestTrie_Foreach(t *testing.T) {
tree := trie.New[netip.Addr]()
domainList := []string{
"google.com",
"stun.*.*.*",
"test.*.google.com",
"+.baidu.com",
"*.baidu.com",
"*.*.baidu.com",
}
for _, domain := range domainList {
assert.NoError(t, tree.Insert(domain, localIP))
}
count := 0
tree.Foreach(func(domain string, data netip.Addr) {
count++
})
assert.Equal(t, 7, count)
}

View File

@ -116,18 +116,6 @@ func (n *Node[T]) setData(data T) {
n.inited = true n.inited = true
} }
func (n *Node[T]) getChildren() map[string]*Node[T] {
if n.childMap == nil {
if n.childNode != nil {
m := make(map[string]*Node[T])
m[n.childStr] = n.childNode
return m
}
} else {
return n.childMap
}
return nil
}
func (n *Node[T]) Data() T { func (n *Node[T]) Data() T {
return n.data return n.data
} }

View File

@ -4,11 +4,12 @@ import (
"container/list" "container/list"
"errors" "errors"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"net/url" "net/url"
"os" "os"
"regexp" "runtime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -135,8 +136,9 @@ type IPTables struct {
type Sniffer struct { type Sniffer struct {
Enable bool Enable bool
Sniffers map[snifferTypes.Type]SNIFF.SnifferConfig Sniffers map[snifferTypes.Type]SNIFF.SnifferConfig
ForceDomain *trie.DomainSet Reverses *trie.DomainTrie[struct{}]
SkipDomain *trie.DomainSet ForceDomain *trie.DomainTrie[struct{}]
SkipDomain *trie.DomainTrie[struct{}]
ForceDnsMapping bool ForceDnsMapping bool
ParsePureIp bool ParsePureIp bool
} }
@ -216,7 +218,6 @@ type RawTun struct {
ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"` ExcludePackage []string `yaml:"exclude-package" json:"exclude_package,omitempty"`
EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"` EndpointIndependentNat bool `yaml:"endpoint-independent-nat" json:"endpoint_independent_nat,omitempty"`
UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"` UDPTimeout int64 `yaml:"udp-timeout" json:"udp_timeout,omitempty"`
FileDescriptor int `yaml:"file-descriptor" json:"file-descriptor"`
} }
type RawTuicServer struct { type RawTuicServer struct {
@ -445,11 +446,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
} }
config.General = general config.General = general
if len(config.General.GlobalClientFingerprint) != 0 { dialer.DefaultInterface.Store(config.General.Interface)
log.Debugln("GlobalClientFingerprint:%s", config.General.GlobalClientFingerprint)
tlsC.SetGlobalUtlsClient(config.General.GlobalClientFingerprint)
}
proxies, providers, err := parseProxies(rawCfg) proxies, providers, err := parseProxies(rawCfg)
if err != nil { if err != nil {
return nil, err return nil, err
@ -488,7 +485,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
} }
config.Hosts = hosts config.Hosts = hosts
dnsCfg, err := parseDNS(rawCfg, hosts, rules, ruleProviders) dnsCfg, err := parseDNS(rawCfg, hosts, rules)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -524,6 +521,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms
log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm
if len(config.General.GlobalClientFingerprint) != 0 {
log.Debugln("GlobalClientFingerprint:%s", config.General.GlobalClientFingerprint)
tlsC.SetGlobalUtlsClient(config.General.GlobalClientFingerprint)
}
return config, nil return config, nil
} }
@ -820,6 +822,8 @@ func parseRules(rulesConfig []string, proxies map[string]C.Proxy, subRules map[s
rules = append(rules, parsed) rules = append(rules, parsed)
} }
runtime.GC()
return rules, nil return rules, nil
} }
@ -841,7 +845,7 @@ func parseHosts(cfg *RawConfig) (*trie.DomainTrie[resolver.HostValue], error) {
} else { } else {
ips := make([]netip.Addr, 0) ips := make([]netip.Addr, 0)
for _, addr := range addrs { for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback()&&!ipnet.IP.IsLinkLocalUnicast() { if ipnet, ok := addr.(*net.IPNet); ok {
if ip, err := netip.ParseAddr(ipnet.IP.String()); err == nil { if ip, err := netip.ParseAddr(ipnet.IP.String()); err == nil {
ips = append(ips, ip) ips = append(ips, ip)
} }
@ -896,7 +900,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
} }
proxyName := u.Fragment proxyAdapter := u.Fragment
var addr, dnsNetType string var addr, dnsNetType string
params := map[string]string{} params := map[string]string{}
@ -913,7 +917,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
case "https": case "https":
addr, err = hostWithDefaultPort(u.Host, "443") addr, err = hostWithDefaultPort(u.Host, "443")
if err == nil { if err == nil {
proxyName = "" proxyAdapter = ""
clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path} clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path}
addr = clearURL.String() addr = clearURL.String()
dnsNetType = "https" // DNS over HTTPS dnsNetType = "https" // DNS over HTTPS
@ -923,7 +927,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
if len(arr) == 0 { if len(arr) == 0 {
continue continue
} else if len(arr) == 1 { } else if len(arr) == 1 {
proxyName = arr[0] proxyAdapter = arr[0]
} else if len(arr) == 2 { } else if len(arr) == 2 {
params[arr[0]] = arr[1] params[arr[0]] = arr[1]
} else { } else {
@ -949,24 +953,18 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
nameservers = append( nameservers = append(
nameservers, nameservers,
dns.NameServer{ dns.NameServer{
Net: dnsNetType, Net: dnsNetType,
Addr: addr, Addr: addr,
ProxyName: proxyName, ProxyAdapter: proxyAdapter,
Interface: dialer.DefaultInterface, Interface: dialer.DefaultInterface,
Params: params, Params: params,
PreferH3: preferH3, PreferH3: preferH3,
}, },
) )
} }
return nameservers, nil return nameservers, nil
} }
func init() {
dns.ParseNameServer = func(servers []string) ([]dns.NameServer, error) { // using by wireguard
return parseNameServer(servers, false)
}
}
func parsePureDNSServer(server string) string { func parsePureDNSServer(server string) string {
addPre := func(server string) string { addPre := func(server string) string {
return "udp://" + server return "udp://" + server
@ -985,41 +983,11 @@ func parsePureDNSServer(server string) string {
} }
} }
} }
func parseNameServerPolicy(nsPolicy map[string]any, ruleProviders map[string]providerTypes.RuleProvider, preferH3 bool) (map[string][]dns.NameServer, error) { func parseNameServerPolicy(nsPolicy map[string]any, preferH3 bool) (map[string][]dns.NameServer, error) {
policy := map[string][]dns.NameServer{} policy := map[string][]dns.NameServer{}
updatedPolicy := make(map[string]interface{})
re := regexp.MustCompile(`[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?`)
for k, v := range nsPolicy { for domain, server := range nsPolicy {
if strings.Contains(k, ",") {
if strings.Contains(k, "geosite:") {
subkeys := strings.Split(k, ":")
subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",")
for _, subkey := range subkeys {
newKey := "geosite:" + subkey
updatedPolicy[newKey] = v
}
} else if strings.Contains(k, "rule-set:") {
subkeys := strings.Split(k, ":")
subkeys = subkeys[1:]
subkeys = strings.Split(subkeys[0], ",")
for _, subkey := range subkeys {
newKey := "rule-set:" + subkey
updatedPolicy[newKey] = v
}
} else if re.MatchString(k) {
subkeys := strings.Split(k, ",")
for _, subkey := range subkeys {
updatedPolicy[subkey] = v
}
}
} else {
updatedPolicy[k] = v
}
}
for domain, server := range updatedPolicy {
servers, err := utils.ToStringSlice(server) servers, err := utils.ToStringSlice(server)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1031,19 +999,6 @@ func parseNameServerPolicy(nsPolicy map[string]any, ruleProviders map[string]pro
if _, valid := trie.ValidAndSplitDomain(domain); !valid { if _, valid := trie.ValidAndSplitDomain(domain); !valid {
return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain) return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain)
} }
if strings.HasPrefix(domain, "rule-set:") {
domainSetName := domain[9:]
if provider, ok := ruleProviders[domainSetName]; !ok {
return nil, fmt.Errorf("not found rule-set: %s", domainSetName)
} else {
switch provider.Behavior() {
case providerTypes.IPCIDR:
return nil, fmt.Errorf("rule provider type error, except domain,actual %s", provider.Behavior())
case providerTypes.Classical:
log.Warnln("%s provider is %s, only matching it contain domain rule", provider.Name(), provider.Behavior())
}
}
}
policy[domain] = nameservers policy[domain] = nameservers
} }
@ -1096,10 +1051,11 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
log.Infoln("Start initial GeoSite dns fallback filter `%s`, records: %d", country, recordsCount) log.Infoln("Start initial GeoSite dns fallback filter `%s`, records: %d", country, recordsCount)
} }
} }
runtime.GC()
return sites, nil return sites, nil
} }
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) { func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule) (*DNS, error) {
cfg := rawCfg.DNS cfg := rawCfg.DNS
if cfg.Enable && len(cfg.NameServer) == 0 { if cfg.Enable && len(cfg.NameServer) == 0 {
return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty") return nil, fmt.Errorf("if DNS configuration is turned on, NameServer cannot be empty")
@ -1126,7 +1082,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
return nil, err return nil, err
} }
if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, ruleProviders, cfg.PreferH3); err != nil { if dnsCfg.NameServerPolicy, err = parseNameServerPolicy(cfg.NameServerPolicy, cfg.PreferH3); err != nil {
return nil, err return nil, err
} }
@ -1262,7 +1218,6 @@ func parseTun(rawTun RawTun, general *General) error {
ExcludePackage: rawTun.ExcludePackage, ExcludePackage: rawTun.ExcludePackage,
EndpointIndependentNat: rawTun.EndpointIndependentNat, EndpointIndependentNat: rawTun.EndpointIndependentNat,
UDPTimeout: rawTun.UDPTimeout, UDPTimeout: rawTun.UDPTimeout,
FileDescriptor: rawTun.FileDescriptor,
} }
return nil return nil
@ -1346,24 +1301,23 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
} }
sniffer.Sniffers = loadSniffer sniffer.Sniffers = loadSniffer
sniffer.ForceDomain = trie.New[struct{}]()
forceDomainTrie := trie.New[struct{}]()
for _, domain := range snifferRaw.ForceDomain { for _, domain := range snifferRaw.ForceDomain {
err := forceDomainTrie.Insert(domain, struct{}{}) err := sniffer.ForceDomain.Insert(domain, struct{}{})
if err != nil { if err != nil {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err) return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
} }
} }
sniffer.ForceDomain = forceDomainTrie.NewDomainSet() sniffer.ForceDomain.Optimize()
skipDomainTrie := trie.New[struct{}]() sniffer.SkipDomain = trie.New[struct{}]()
for _, domain := range snifferRaw.SkipDomain { for _, domain := range snifferRaw.SkipDomain {
err := skipDomainTrie.Insert(domain, struct{}{}) err := sniffer.SkipDomain.Insert(domain, struct{}{})
if err != nil { if err != nil {
return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err) return nil, fmt.Errorf("error domian[%s] in force-domain, error:%v", domain, err)
} }
} }
sniffer.SkipDomain = skipDomainTrie.NewDomainSet() sniffer.SkipDomain.Optimize()
return sniffer, nil return sniffer, nil
} }

View File

@ -1,20 +1,15 @@
package config package config
import ( import (
"context"
"fmt" "fmt"
"github.com/Dreamacro/clash/component/geodata"
_ "github.com/Dreamacro/clash/component/geodata/standard"
C "github.com/Dreamacro/clash/constant"
"github.com/oschwald/geoip2-golang"
"io" "io"
"net/http" "net/http"
"os" "os"
"runtime" "runtime"
"time"
"github.com/Dreamacro/clash/component/geodata"
_ "github.com/Dreamacro/clash/component/geodata/standard"
clashHttp "github.com/Dreamacro/clash/component/http"
C "github.com/Dreamacro/clash/constant"
"github.com/oschwald/geoip2-golang"
) )
func UpdateGeoDatabases() error { func UpdateGeoDatabases() error {
@ -68,15 +63,11 @@ func UpdateGeoDatabases() error {
return fmt.Errorf("can't save GeoSite database file: %w", err) return fmt.Errorf("can't save GeoSite database file: %w", err)
} }
geodata.ClearCache()
return nil return nil
} }
func downloadForBytes(url string) ([]byte, error) { func downloadForBytes(url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) resp, err := http.Get(url)
defer cancel()
resp, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -3,7 +3,6 @@ package config
import ( import (
"fmt" "fmt"
"net" "net"
"net/netip"
"strings" "strings"
"github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/adapter/outboundgroup"
@ -150,11 +149,20 @@ func proxyGroupsDagSort(groupsConfig []map[string]any) error {
} }
func verifyIP6() bool { func verifyIP6() bool {
if iAddrs, err := net.InterfaceAddrs(); err == nil { addrs, err := net.InterfaceAddrs()
for _, addr := range iAddrs { if err != nil {
if prefix, err := netip.ParsePrefix(addr.String()); err == nil { return false
if addr := prefix.Addr().Unmap(); addr.Is6() && addr.IsGlobalUnicast() { }
return true for _, addr := range addrs {
ipNet, isIpNet := addr.(*net.IPNet)
if isIpNet && !ipNet.IP.IsLoopback() {
if ipNet.IP.To16() != nil {
s := ipNet.IP.String()
for i := 0; i < len(s); i++ {
switch s[i] {
case ':':
return true
}
} }
} }
} }

View File

@ -2,14 +2,12 @@ package constant
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"sync" "sync"
"time" "time"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
) )
@ -45,8 +43,6 @@ const (
DefaultTLSTimeout = DefaultTCPTimeout DefaultTLSTimeout = DefaultTCPTimeout
) )
var ErrNotSupport = errors.New("no support")
type Connection interface { type Connection interface {
Chains() Chain Chains() Chain
AppendToChains(adapter ProxyAdapter) AppendToChains(adapter ProxyAdapter)
@ -76,7 +72,7 @@ func (c Chain) Last() string {
} }
type Conn interface { type Conn interface {
N.ExtendedConn net.Conn
Connection Connection
} }
@ -120,13 +116,10 @@ type ProxyAdapter interface {
// SupportUOT return UDP over TCP support // SupportUOT return UDP over TCP support
SupportUOT() bool SupportUOT() bool
SupportWithDialer() NetWork SupportWithDialer() bool
DialContextWithDialer(ctx context.Context, dialer Dialer, metadata *Metadata) (Conn, error) DialContextWithDialer(ctx context.Context, dialer Dialer, metadata *Metadata) (Conn, error)
ListenPacketWithDialer(ctx context.Context, dialer Dialer, metadata *Metadata) (PacketConn, error) ListenPacketWithDialer(ctx context.Context, dialer Dialer, metadata *Metadata) (PacketConn, error)
// IsL3Protocol return ProxyAdapter working in L3 (tell dns module not pass the domain to avoid loopback)
IsL3Protocol(metadata *Metadata) bool
// Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract. // Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract.
Unwrap(metadata *Metadata, touch bool) Proxy Unwrap(metadata *Metadata, touch bool) Proxy
} }

View File

@ -5,7 +5,7 @@ import (
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
"github.com/gofrs/uuid/v5" "github.com/gofrs/uuid"
) )
type PlainContext interface { type PlainContext interface {

View File

@ -1,6 +0,0 @@
//go:build with_low_memory
package features
func init() {
TAGS = append(TAGS, "with_low_memory")
}

View File

@ -0,0 +1,7 @@
//go:build no_doq
package features
func init() {
TAGS = append(TAGS, "no_doq")
}

View File

@ -1,7 +0,0 @@
//go:build no_fake_tcp
package features
func init() {
TAGS = append(TAGS, "no_fake_tcp")
}

View File

@ -0,0 +1,7 @@
//go:build no_gvisor
package features
func init() {
TAGS = append(TAGS, "no_gvisor")
}

View File

@ -1,7 +0,0 @@
//go:build with_gvisor
package features
func init() {
TAGS = append(TAGS, "with_gvisor")
}

View File

@ -15,10 +15,7 @@ const (
TCP NetWork = iota TCP NetWork = iota
UDP UDP
ALLNet ALLNet
InvalidNet = 0xff
)
const (
HTTP Type = iota HTTP Type = iota
HTTPS HTTPS
SOCKS4 SOCKS4
@ -36,16 +33,12 @@ const (
type NetWork int type NetWork int
func (n NetWork) String() string { func (n NetWork) String() string {
switch n { if n == TCP {
case TCP:
return "tcp" return "tcp"
case UDP: } else if n == UDP {
return "udp" return "udp"
case ALLNet:
return "all"
default:
return "invalid"
} }
return "all"
} }
func (n NetWork) MarshalJSON() ([]byte, error) { func (n NetWork) MarshalJSON() ([]byte, error) {
@ -229,21 +222,3 @@ func (m *Metadata) String() string {
func (m *Metadata) Valid() bool { func (m *Metadata) Valid() bool {
return m.Host != "" || m.DstIP.IsValid() return m.Host != "" || m.DstIP.IsValid()
} }
func (m *Metadata) SetRemoteAddress(rawAddress string) error {
host, port, err := net.SplitHostPort(rawAddress)
if err != nil {
return err
}
if ip, err := netip.ParseAddr(host); err != nil {
m.Host = host
m.DstIP = netip.Addr{}
} else {
m.Host = ""
m.DstIP = ip.Unmap()
}
m.DstPort = port
return nil
}

View File

@ -73,27 +73,17 @@ type ProxyProvider interface {
Version() uint32 Version() uint32
} }
// RuleProvider interface // Rule Type
type RuleProvider interface {
Provider
Behavior() RuleBehavior
Match(*constant.Metadata) bool
ShouldResolveIP() bool
ShouldFindProcess() bool
AsRule(adaptor string) constant.Rule
}
// Rule Behavior
const ( const (
Domain RuleBehavior = iota Domain RuleType = iota
IPCIDR IPCIDR
Classical Classical
) )
// RuleBehavior defined // RuleType defined
type RuleBehavior int type RuleType int
func (rt RuleBehavior) String() string { func (rt RuleType) String() string {
switch rt { switch rt {
case Domain: case Domain:
return "Domain" return "Domain"
@ -106,20 +96,12 @@ func (rt RuleBehavior) String() string {
} }
} }
const ( // RuleProvider interface
YamlRule RuleFormat = iota type RuleProvider interface {
TextRule Provider
) Behavior() RuleType
Match(*constant.Metadata) bool
type RuleFormat int ShouldResolveIP() bool
ShouldFindProcess() bool
func (rf RuleFormat) String() string { AsRule(adaptor string) constant.Rule
switch rf {
case YamlRule:
return "YamlRule"
case TextRule:
return "TextRule"
default:
return "Unknown"
}
} }

View File

@ -7,7 +7,7 @@ import (
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/gofrs/uuid/v5" "github.com/gofrs/uuid"
) )
type ConnContext struct { type ConnContext struct {
@ -17,8 +17,10 @@ type ConnContext struct {
} }
func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext { func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext {
id, _ := utils.UnsafeUUIDGenerator.NewV4()
return &ConnContext{ return &ConnContext{
id: utils.NewUUIDV4(), id: id,
metadata: metadata, metadata: metadata,
conn: N.NewBufferedConn(conn), conn: N.NewBufferedConn(conn),
} }

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/common/utils"
"github.com/gofrs/uuid/v5" "github.com/gofrs/uuid"
"github.com/miekg/dns" "github.com/miekg/dns"
) )
@ -23,10 +23,11 @@ type DNSContext struct {
} }
func NewDNSContext(ctx context.Context, msg *dns.Msg) *DNSContext { func NewDNSContext(ctx context.Context, msg *dns.Msg) *DNSContext {
id, _ := utils.UnsafeUUIDGenerator.NewV4()
return &DNSContext{ return &DNSContext{
Context: ctx, Context: ctx,
id: utils.NewUUIDV4(), id: id,
msg: msg, msg: msg,
} }
} }

View File

@ -6,7 +6,7 @@ import (
"github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/gofrs/uuid/v5" "github.com/gofrs/uuid"
) )
type PacketConnContext struct { type PacketConnContext struct {
@ -16,8 +16,9 @@ type PacketConnContext struct {
} }
func NewPacketConnContext(metadata *C.Metadata) *PacketConnContext { func NewPacketConnContext(metadata *C.Metadata) *PacketConnContext {
id, _ := utils.UnsafeUUIDGenerator.NewV4()
return &PacketConnContext{ return &PacketConnContext{
id: utils.NewUUIDV4(), id: id,
metadata: metadata, metadata: metadata,
} }
} }

View File

@ -8,11 +8,11 @@ import (
"net/netip" "net/netip"
"strings" "strings"
"github.com/Dreamacro/clash/common/atomic" tlsC "github.com/Dreamacro/clash/component/tls"
"go.uber.org/atomic"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
D "github.com/miekg/dns" D "github.com/miekg/dns"
"github.com/zhangyunhao116/fastrand" "github.com/zhangyunhao116/fastrand"
@ -23,9 +23,8 @@ type client struct {
r *Resolver r *Resolver
port string port string
host string host string
iface *atomic.TypedValue[string] iface *atomic.String
proxyAdapter C.ProxyAdapter proxyAdapter string
proxyName string
addr string addr string
} }
@ -82,7 +81,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
options = append(options, dialer.WithInterface(c.iface.Load())) options = append(options, dialer.WithInterface(c.iface.Load()))
} }
conn, err := getDialHandler(c.r, c.proxyAdapter, c.proxyName, options...)(ctx, network, net.JoinHostPort(ip.String(), c.port)) conn, err := getDialHandler(c.r, c.proxyAdapter, options...)(ctx, network, net.JoinHostPort(ip.String(), c.port))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -8,7 +8,8 @@ import (
"sync" "sync"
"time" "time"
"github.com/Dreamacro/clash/common/atomic" "go.uber.org/atomic"
"github.com/Dreamacro/clash/component/dhcp" "github.com/Dreamacro/clash/component/dhcp"
"github.com/Dreamacro/clash/component/iface" "github.com/Dreamacro/clash/component/iface"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
@ -85,7 +86,7 @@ func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) {
for _, item := range dns { for _, item := range dns {
nameserver = append(nameserver, NameServer{ nameserver = append(nameserver, NameServer{
Addr: net.JoinHostPort(item.String(), "53"), Addr: net.JoinHostPort(item.String(), "53"),
Interface: atomic.NewTypedValue(d.ifaceName), Interface: atomic.NewString(d.ifaceName),
}) })
} }

View File

@ -21,7 +21,6 @@ import (
"github.com/metacubex/quic-go" "github.com/metacubex/quic-go"
"github.com/metacubex/quic-go/http3" "github.com/metacubex/quic-go/http3"
D "github.com/miekg/dns" D "github.com/miekg/dns"
"golang.org/x/exp/slices"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
@ -64,8 +63,7 @@ type dnsOverHTTPS struct {
url *url.URL url *url.URL
r *Resolver r *Resolver
httpVersions []C.HTTPVersion httpVersions []C.HTTPVersion
proxyAdapter C.ProxyAdapter proxyAdapter string
proxyName string
addr string addr string
} }
@ -73,7 +71,7 @@ type dnsOverHTTPS struct {
var _ dnsClient = (*dnsOverHTTPS)(nil) var _ dnsClient = (*dnsOverHTTPS)(nil)
// newDoH returns the DNS-over-HTTPS Upstream. // newDoH returns the DNS-over-HTTPS Upstream.
func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[string]string, proxyAdapter C.ProxyAdapter, proxyName string) dnsClient { func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[string]string, proxyAdapter string) dnsClient {
u, _ := url.Parse(urlString) u, _ := url.Parse(urlString)
httpVersions := DefaultHTTPVersions httpVersions := DefaultHTTPVersions
if preferH3 { if preferH3 {
@ -89,7 +87,6 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
addr: u.String(), addr: u.String(),
r: r, r: r,
proxyAdapter: proxyAdapter, proxyAdapter: proxyAdapter,
proxyName: proxyName,
quicConfig: &quic.Config{ quicConfig: &quic.Config{
KeepAlivePeriod: QUICKeepAlivePeriod, KeepAlivePeriod: QUICKeepAlivePeriod,
TokenStore: newQUICTokenStore(), TokenStore: newQUICTokenStore(),
@ -393,17 +390,14 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp
nextProtos = append(nextProtos, string(v)) nextProtos = append(nextProtos, string(v))
} }
tlsConfig.NextProtos = nextProtos tlsConfig.NextProtos = nextProtos
dialContext := getDialHandler(doh.r, doh.proxyAdapter, doh.proxyName) dialContext := getDialHandler(doh.r, doh.proxyAdapter)
// First, we attempt to create an HTTP3 transport. If the probe QUIC
if slices.Contains(doh.httpVersions, C.HTTPVersion3) { // connection is established successfully, we'll be using HTTP3 for this
// First, we attempt to create an HTTP3 transport. If the probe QUIC // upstream.
// connection is established successfully, we'll be using HTTP3 for this transportH3, err := doh.createTransportH3(ctx, tlsConfig, dialContext)
// upstream. if err == nil {
transportH3, err := doh.createTransportH3(ctx, tlsConfig, dialContext) log.Debugln("[%s] using HTTP/3 for this upstream: QUIC was faster", doh.url.String())
if err == nil { return transportH3, nil
log.Debugln("[%s] using HTTP/3 for this upstream: QUIC was faster", doh.url.String())
return transportH3, nil
}
} }
log.Debugln("[%s] using HTTP/2 for this upstream: %v", doh.url.String(), err) log.Debugln("[%s] using HTTP/2 for this upstream: %v", doh.url.String(), err)
@ -539,7 +533,7 @@ func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.
IP: net.ParseIP(ip), IP: net.ParseIP(ip),
Port: portInt, Port: portInt,
} }
conn, err := listenPacket(ctx, doh.proxyAdapter, doh.proxyName, "udp", addr, doh.r) conn, err := listenPacket(ctx, doh.proxyAdapter, "udp", addr, doh.r)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -13,10 +13,9 @@ import (
"time" "time"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/metacubex/quic-go" "github.com/metacubex/quic-go"
"github.com/Dreamacro/clash/log"
D "github.com/miekg/dns" D "github.com/miekg/dns"
) )
@ -61,8 +60,7 @@ type dnsOverQUIC struct {
bytesPoolGuard sync.Mutex bytesPoolGuard sync.Mutex
addr string addr string
proxyAdapter C.ProxyAdapter proxyAdapter string
proxyName string
r *Resolver r *Resolver
} }
@ -70,11 +68,10 @@ type dnsOverQUIC struct {
var _ dnsClient = (*dnsOverQUIC)(nil) var _ dnsClient = (*dnsOverQUIC)(nil)
// newDoQ returns the DNS-over-QUIC Upstream. // newDoQ returns the DNS-over-QUIC Upstream.
func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyName string) (dnsClient, error) { func newDoQ(resolver *Resolver, addr string, adapter string) (dnsClient, error) {
doq := &dnsOverQUIC{ doq := &dnsOverQUIC{
addr: addr, addr: addr,
proxyAdapter: proxyAdapter, proxyAdapter: adapter,
proxyName: proxyName,
r: resolver, r: resolver,
quicConfig: &quic.Config{ quicConfig: &quic.Config{
KeepAlivePeriod: QUICKeepAlivePeriod, KeepAlivePeriod: QUICKeepAlivePeriod,
@ -313,7 +310,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
// we're using bootstrapped address instead of what's passed to the function // 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 // it does not create an actual connection, but it helps us determine
// what IP is actually reachable (when there're v4/v6 addresses). // what IP is actually reachable (when there're v4/v6 addresses).
rawConn, err := getDialHandler(doq.r, doq.proxyAdapter, doq.proxyName)(ctx, "udp", doq.addr) rawConn, err := getDialHandler(doq.r, doq.proxyAdapter)(ctx, "udp", doq.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to open a QUIC connection: %w", err) return nil, fmt.Errorf("failed to open a QUIC connection: %w", err)
} }
@ -328,7 +325,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
p, err := strconv.Atoi(port) p, err := strconv.Atoi(port)
udpAddr := net.UDPAddr{IP: net.ParseIP(ip), Port: p} udpAddr := net.UDPAddr{IP: net.ParseIP(ip), Port: p}
udp, err := listenPacket(ctx, doq.proxyAdapter, doq.proxyName, "udp", addr, doq.r) udp, err := listenPacket(ctx, doq.proxyAdapter, "udp", addr, doq.r)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -29,10 +29,29 @@ func (gf *geoipFilter) Match(ip netip.Addr) bool {
} }
if geoIPMatcher == nil { if geoIPMatcher == nil {
var err error countryCode := "cn"
geoIPMatcher, _, err = geodata.LoadGeoIPMatcher("CN") geoLoader, err := geodata.GetGeoDataLoader(geodata.LoaderName())
if err != nil { if err != nil {
log.Errorln("[GeoIPFilter] LoadGeoIPMatcher error: %s", err.Error()) log.Errorln("[GeoIPFilter] GetGeoDataLoader error: %s", err.Error())
return false
}
records, err := geoLoader.LoadGeoIP(countryCode)
if err != nil {
log.Errorln("[GeoIPFilter] LoadGeoIP error: %s", err.Error())
return false
}
geoIP := &router.GeoIP{
CountryCode: countryCode,
Cidr: records,
ReverseMatch: false,
}
geoIPMatcher, err = router.NewGeoIPMatcher(geoIP)
if err != nil {
log.Errorln("[GeoIPFilter] NewGeoIPMatcher error: %s", err.Error())
return false return false
} }
} }
@ -73,10 +92,6 @@ type geoSiteFilter struct {
} }
func NewGeoSite(group string) (fallbackDomainFilter, error) { func NewGeoSite(group string) (fallbackDomainFilter, error) {
if err := geodata.InitGeoSite(); err != nil {
log.Errorln("can't initial GeoSite: %s", err)
return nil, err
}
matcher, _, err := geodata.LoadGeoSiteMatcher(group) matcher, _, err := geodata.LoadGeoSiteMatcher(group)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -3,21 +3,23 @@ package dns
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"net/netip" "net/netip"
"strings" "strings"
"time" "time"
"github.com/Dreamacro/clash/common/atomic" "go.uber.org/atomic"
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
D "github.com/miekg/dns" D "github.com/miekg/dns"
"github.com/zhangyunhao116/fastrand"
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
) )
@ -38,11 +40,6 @@ type geositePolicyRecord struct {
inversedMatching bool inversedMatching bool
} }
type domainSetPolicyRecord struct {
domainSetProvider provider.RuleProvider
policy *Policy
}
type Resolver struct { type Resolver struct {
ipv6 bool ipv6 bool
ipv6Timeout time.Duration ipv6Timeout time.Duration
@ -54,7 +51,6 @@ type Resolver struct {
group singleflight.Group group singleflight.Group
lruCache *cache.LruCache[string, *D.Msg] lruCache *cache.LruCache[string, *D.Msg]
policy *trie.DomainTrie[*Policy] policy *trie.DomainTrie[*Policy]
domainSetPolicy []domainSetPolicyRecord
geositePolicy []geositePolicyRecord geositePolicy []geositePolicyRecord
proxyServer []dnsClient proxyServer []dnsClient
} }
@ -97,7 +93,7 @@ func (r *Resolver) LookupIP(ctx context.Context, host string) (ips []netip.Addr,
ips, err = r.lookupIP(ctx, host, D.TypeA) ips, err = r.lookupIP(ctx, host, D.TypeA)
var waitIPv6 *time.Timer var waitIPv6 *time.Timer
if r != nil && r.ipv6Timeout > 0 { if r != nil {
waitIPv6 = time.NewTimer(r.ipv6Timeout) waitIPv6 = time.NewTimer(r.ipv6Timeout)
} else { } else {
waitIPv6 = time.NewTimer(100 * time.Millisecond) waitIPv6 = time.NewTimer(100 * time.Millisecond)
@ -116,16 +112,49 @@ func (r *Resolver) LookupIP(ctx context.Context, host string) (ips []netip.Addr,
return ips, nil return ips, nil
} }
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
func (r *Resolver) ResolveIP(ctx context.Context, host string) (ip netip.Addr, err error) {
ips, err := r.LookupIPPrimaryIPv4(ctx, host)
if err != nil {
return netip.Addr{}, err
} else if len(ips) == 0 {
return netip.Addr{}, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, host)
}
return ips[fastrand.Intn(len(ips))], nil
}
// LookupIPv4 request with TypeA // LookupIPv4 request with TypeA
func (r *Resolver) LookupIPv4(ctx context.Context, host string) ([]netip.Addr, error) { func (r *Resolver) LookupIPv4(ctx context.Context, host string) ([]netip.Addr, error) {
return r.lookupIP(ctx, host, D.TypeA) return r.lookupIP(ctx, host, D.TypeA)
} }
// ResolveIPv4 request with TypeA
func (r *Resolver) ResolveIPv4(ctx context.Context, host string) (ip netip.Addr, err error) {
ips, err := r.lookupIP(ctx, host, D.TypeA)
if err != nil {
return netip.Addr{}, err
} else if len(ips) == 0 {
return netip.Addr{}, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, host)
}
return ips[fastrand.Intn(len(ips))], nil
}
// LookupIPv6 request with TypeAAAA // LookupIPv6 request with TypeAAAA
func (r *Resolver) LookupIPv6(ctx context.Context, host string) ([]netip.Addr, error) { func (r *Resolver) LookupIPv6(ctx context.Context, host string) ([]netip.Addr, error) {
return r.lookupIP(ctx, host, D.TypeAAAA) return r.lookupIP(ctx, host, D.TypeAAAA)
} }
// ResolveIPv6 request with TypeAAAA
func (r *Resolver) ResolveIPv6(ctx context.Context, host string) (ip netip.Addr, err error) {
ips, err := r.lookupIP(ctx, host, D.TypeAAAA)
if err != nil {
return netip.Addr{}, err
} else if len(ips) == 0 {
return netip.Addr{}, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, host)
}
return ips[fastrand.Intn(len(ips))], nil
}
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool { func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
for _, filter := range r.fallbackIPFilters { for _, filter := range r.fallbackIPFilters {
if filter.Match(ip) { if filter.Match(ip) {
@ -272,12 +301,6 @@ func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
return geositeRecord.policy.GetData() return geositeRecord.policy.GetData()
} }
} }
metadata := &C.Metadata{Host: domain}
for _, domainSetRecord := range r.domainSetPolicy {
if ok := domainSetRecord.domainSetProvider.Match(metadata); ok {
return domainSetRecord.policy.GetData()
}
}
return nil return nil
} }
@ -376,20 +399,16 @@ func (r *Resolver) asyncExchange(ctx context.Context, client []dnsClient, msg *D
return ch return ch
} }
// Invalid return this resolver can or can't be used // HasProxyServer has proxy server dns client
func (r *Resolver) Invalid() bool { func (r *Resolver) HasProxyServer() bool {
if r == nil {
return false
}
return len(r.main) > 0 return len(r.main) > 0
} }
type NameServer struct { type NameServer struct {
Net string Net string
Addr string Addr string
Interface *atomic.TypedValue[string] Interface *atomic.String
ProxyAdapter C.ProxyAdapter ProxyAdapter string
ProxyName string
Params map[string]string Params map[string]string
PreferH3 bool PreferH3 bool
} }
@ -403,18 +422,16 @@ type FallbackFilter struct {
} }
type Config struct { type Config struct {
Main, Fallback []NameServer Main, Fallback []NameServer
Default []NameServer Default []NameServer
ProxyServer []NameServer ProxyServer []NameServer
IPv6 bool IPv6 bool
IPv6Timeout uint IPv6Timeout uint
EnhancedMode C.DNSMode EnhancedMode C.DNSMode
FallbackFilter FallbackFilter FallbackFilter FallbackFilter
Pool *fakeip.Pool Pool *fakeip.Pool
Hosts *trie.DomainTrie[resolver.HostValue] Hosts *trie.DomainTrie[resolver.HostValue]
Policy map[string][]NameServer Policy map[string][]NameServer
DomainSetPolicy map[provider.RuleProvider][]NameServer
GeositePolicy map[router.DomainMatcher][]NameServer
} }
func NewResolver(config Config) *Resolver { func NewResolver(config Config) *Resolver {
@ -466,14 +483,6 @@ func NewResolver(config Config) *Resolver {
} }
r.policy.Optimize() r.policy.Optimize()
} }
if len(config.DomainSetPolicy) > 0 {
for p, n := range config.DomainSetPolicy {
r.domainSetPolicy = append(r.domainSetPolicy, domainSetPolicyRecord{
domainSetProvider: p,
policy: NewPolicy(transform(n, defaultResolver)),
})
}
}
fallbackIPFilters := []fallbackIPFilter{} fallbackIPFilters := []fallbackIPFilter{}
if config.FallbackFilter.GeoIP { if config.FallbackFilter.GeoIP {
@ -507,10 +516,8 @@ func NewProxyServerHostResolver(old *Resolver) *Resolver {
main: old.proxyServer, main: old.proxyServer,
lruCache: old.lruCache, lruCache: old.lruCache,
hosts: old.hosts, hosts: old.hosts,
policy: trie.New[*Policy](), policy: old.policy,
ipv6Timeout: old.ipv6Timeout, ipv6Timeout: old.ipv6Timeout,
} }
return r return r
} }
var ParseNameServer func(servers []string) ([]NameServer, error) // define in config/config.go

View File

@ -74,13 +74,13 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
for _, s := range servers { for _, s := range servers {
switch s.Net { switch s.Net {
case "https": case "https":
ret = append(ret, newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName)) ret = append(ret, newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter))
continue continue
case "dhcp": case "dhcp":
ret = append(ret, newDHCPClient(s.Addr)) ret = append(ret, newDHCPClient(s.Addr))
continue continue
case "quic": case "quic":
if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter, s.ProxyName); err == nil { if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter); err == nil {
ret = append(ret, doq) ret = append(ret, doq)
} else { } else {
log.Fatalln("DoQ format error: %v", err) log.Fatalln("DoQ format error: %v", err)
@ -103,7 +103,6 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient {
iface: s.Interface, iface: s.Interface,
r: resolver, r: resolver,
proxyAdapter: s.ProxyAdapter, proxyAdapter: s.ProxyAdapter,
proxyName: s.ProxyName,
}) })
} }
return ret return ret
@ -145,9 +144,9 @@ func msgToDomain(msg *D.Msg) string {
type dialHandler func(ctx context.Context, network, addr string) (net.Conn, error) type dialHandler func(ctx context.Context, network, addr string) (net.Conn, error)
func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string, opts ...dialer.Option) dialHandler { func getDialHandler(r *Resolver, proxyAdapter string, opts ...dialer.Option) dialHandler {
return func(ctx context.Context, network, addr string) (net.Conn, error) { return func(ctx context.Context, network, addr string) (net.Conn, error) {
if len(proxyName) == 0 && proxyAdapter == nil { if len(proxyAdapter) == 0 {
opts = append(opts, dialer.WithResolver(r)) opts = append(opts, dialer.WithResolver(r))
return dialer.DialContext(ctx, network, addr, opts...) return dialer.DialContext(ctx, network, addr, opts...)
} else { } else {
@ -155,14 +154,10 @@ func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string,
if err != nil { if err != nil {
return nil, err return nil, err
} }
if proxyAdapter == nil { adapter, ok := tunnel.Proxies()[proxyAdapter]
var ok bool if !ok {
proxyAdapter, ok = tunnel.Proxies()[proxyName] opts = append(opts, dialer.WithInterface(proxyAdapter))
if !ok {
opts = append(opts, dialer.WithInterface(proxyName))
}
} }
if strings.Contains(network, "tcp") { if strings.Contains(network, "tcp") {
// tcp can resolve host by remote // tcp can resolve host by remote
metadata := &C.Metadata{ metadata := &C.Metadata{
@ -170,16 +165,8 @@ func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string,
Host: host, Host: host,
DstPort: port, DstPort: port,
} }
if proxyAdapter != nil { if ok {
if proxyAdapter.IsL3Protocol(metadata) { // L3 proxy should resolve domain before to avoid loopback return adapter.DialContext(ctx, metadata, opts...)
dstIP, err := resolver.ResolveIPWithResolver(ctx, host, r)
if err != nil {
return nil, err
}
metadata.Host = ""
metadata.DstIP = dstIP
}
return proxyAdapter.DialContext(ctx, metadata, opts...)
} }
opts = append(opts, dialer.WithResolver(r)) opts = append(opts, dialer.WithResolver(r))
return dialer.DialContext(ctx, network, addr, opts...) return dialer.DialContext(ctx, network, addr, opts...)
@ -195,15 +182,15 @@ func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string,
DstIP: dstIP, DstIP: dstIP,
DstPort: port, DstPort: port,
} }
if proxyAdapter == nil { if !ok {
return dialer.DialContext(ctx, network, addr, opts...) return dialer.DialContext(ctx, network, addr, opts...)
} }
if !proxyAdapter.SupportUDP() { if !adapter.SupportUDP() {
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter) return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter)
} }
packetConn, err := proxyAdapter.ListenPacketContext(ctx, metadata, opts...) packetConn, err := adapter.ListenPacketContext(ctx, metadata, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -214,17 +201,14 @@ func getDialHandler(r *Resolver, proxyAdapter C.ProxyAdapter, proxyName string,
} }
} }
func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName string, network string, addr string, r *Resolver, opts ...dialer.Option) (net.PacketConn, error) { func listenPacket(ctx context.Context, proxyAdapter string, network string, addr string, r *Resolver, opts ...dialer.Option) (net.PacketConn, error) {
host, port, err := net.SplitHostPort(addr) host, port, err := net.SplitHostPort(addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if proxyAdapter == nil { adapter, ok := tunnel.Proxies()[proxyAdapter]
var ok bool if !ok && len(proxyAdapter) != 0 {
proxyAdapter, ok = tunnel.Proxies()[proxyName] opts = append(opts, dialer.WithInterface(proxyAdapter))
if !ok {
opts = append(opts, dialer.WithInterface(proxyName))
}
} }
// udp must resolve host first // udp must resolve host first
@ -238,15 +222,15 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st
DstIP: dstIP, DstIP: dstIP,
DstPort: port, DstPort: port,
} }
if proxyAdapter == nil { if !ok {
return dialer.ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", opts...) return dialer.ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", opts...)
} }
if !proxyAdapter.SupportUDP() { if !adapter.SupportUDP() {
return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter) return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter)
} }
return proxyAdapter.ListenPacketContext(ctx, metadata, opts...) return adapter.ListenPacketContext(ctx, metadata, opts...)
} }
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) { func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {

2387
docs/allocs.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 127 KiB

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