Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
8ee452f544 | |||
d0b95e91f5 | |||
6cee7213c4 | |||
c555cec490 | |||
a510909228 | |||
0273a6bb9d | |||
4b1e00d3aa | |||
17fea4b459 | |||
5ecdefcfb5 | |||
2f69b64d82 |
82
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
82
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -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
|
|
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
36
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -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
|
|
||||||
"
|
|
1
.github/genReleaseNote.sh
vendored
1
.github/genReleaseNote.sh
vendored
@ -1 +0,0 @@
|
|||||||
git log --pretty=format:"* %s by @%an" v1.14.x..v1.14.y | sort -f | uniq > release.md
|
|
9
.github/rename-cgo.sh
vendored
9
.github/rename-cgo.sh
vendored
@ -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
|
||||||
|
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@ -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 }}
|
||||||
|
15
.github/workflows/delete.yml
vendored
15
.github/workflows/delete.yml
vendored
@ -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
|
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,7 +34,6 @@ 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 {
|
||||||
@ -52,8 +47,6 @@ type ShadowSocksOption struct {
|
|||||||
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 {
|
||||||
@ -76,27 +69,28 @@ 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"`
|
||||||
|
ClientFingerprint string `obfs:"client-fingerprint,omitempty"`
|
||||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||||
Version int `obfs:"version,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) {
|
||||||
|
switch ss.obfsMode {
|
||||||
|
case shadowtls.Mode:
|
||||||
// fix tls handshake not timeout
|
// fix tls handshake not timeout
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
return ss.StreamConnContext(ctx, c, metadata)
|
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
|
return nil, errors.New("no support")
|
||||||
}
|
|
||||||
|
|
||||||
destination := M.SocksaddrFromNet(metadata.UDPAddr())
|
|
||||||
if ss.option.UDPOverTCPVersion == uot.LegacyVersion {
|
|
||||||
return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), ss), nil
|
|
||||||
} else {
|
|
||||||
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), ss), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, C.ErrNotSupport
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportUOT implements C.ProxyAdapter
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +51,6 @@ type TuicOption struct {
|
|||||||
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
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)
|
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())
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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"`
|
||||||
PrivateKey string `proxy:"private-key"`
|
|
||||||
Workers int `proxy:"workers,omitempty"`
|
|
||||||
MTU int `proxy:"mtu,omitempty"`
|
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
|
||||||
PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
|
|
||||||
|
|
||||||
Peers []WireGuardPeerOption `proxy:"peers,omitempty"`
|
|
||||||
|
|
||||||
RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"`
|
|
||||||
Dns []string `proxy:"dns,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WireGuardPeerOption struct {
|
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
Ip string `proxy:"ip,omitempty"`
|
Ip string `proxy:"ip,omitempty"`
|
||||||
Ipv6 string `proxy:"ipv6,omitempty"`
|
Ipv6 string `proxy:"ipv6,omitempty"`
|
||||||
PublicKey string `proxy:"public-key,omitempty"`
|
PrivateKey string `proxy:"private-key"`
|
||||||
|
PublicKey string `proxy:"public-key"`
|
||||||
PreSharedKey string `proxy:"pre-shared-key,omitempty"`
|
PreSharedKey string `proxy:"pre-shared-key,omitempty"`
|
||||||
Reserved []uint8 `proxy:"reserved,omitempty"`
|
Reserved []uint8 `proxy:"reserved,omitempty"`
|
||||||
AllowedIPs []string `proxy:"allowed_ips,omitempty"`
|
Workers int `proxy:"workers,omitempty"`
|
||||||
|
MTU int `proxy:"mtu,omitempty"`
|
||||||
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
|
PersistentKeepalive int `proxy:"persistent-keepalive,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,52 +138,6 @@ 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 {
|
|
||||||
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)
|
bytes, err := base64.StdEncoding.DecodeString(option.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -261,16 +152,12 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
|||||||
}
|
}
|
||||||
preSharedKey = hex.EncodeToString(bytes)
|
preSharedKey = hex.EncodeToString(bytes)
|
||||||
}
|
}
|
||||||
|
ipcConf := "private_key=" + privateKey
|
||||||
ipcConf += "\npublic_key=" + peerPublicKey
|
ipcConf += "\npublic_key=" + peerPublicKey
|
||||||
ipcConf += "\nendpoint=" + connectAddr.String()
|
ipcConf += "\nendpoint=" + peerAddr.String()
|
||||||
if preSharedKey != "" {
|
if preSharedKey != "" {
|
||||||
ipcConf += "\npreshared_key=" + preSharedKey
|
ipcConf += "\npreshared_key=" + preSharedKey
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
localPrefixes, err = option.Prefixes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var has4, has6 bool
|
var has4, has6 bool
|
||||||
for _, address := range localPrefixes {
|
for _, address := range localPrefixes {
|
||||||
if address.Addr().Is4() {
|
if address.Addr().Is4() {
|
||||||
@ -285,8 +172,6 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
|||||||
if has6 {
|
if has6 {
|
||||||
ipcConf += "\nallowed_ip=::/0"
|
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)
|
|
||||||
|
@ -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{
|
||||||
|
Conn: c,
|
||||||
|
Callback: func(err error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
f.onDialSuccess()
|
f.onDialSuccess()
|
||||||
} else {
|
} else {
|
||||||
f.onDialFailed(proxy.Type(), err)
|
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{
|
||||||
|
@ -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 {
|
||||||
|
@ -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{
|
||||||
|
Conn: c,
|
||||||
|
Callback: func(err error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
lb.onDialSuccess()
|
lb.onDialSuccess()
|
||||||
} else {
|
} else {
|
||||||
lb.onDialFailed(proxy.Type(), err)
|
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{}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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{
|
||||||
|
Conn: c,
|
||||||
|
Callback: func(err error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
u.onDialSuccess()
|
u.onDialSuccess()
|
||||||
} else {
|
} else {
|
||||||
u.onDialFailed(proxy.Type(), err)
|
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 {
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
@ -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
|
|
||||||
)
|
|
||||||
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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,13 +209,10 @@ 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 {
|
|
||||||
tls = strings.ToLower(tls)
|
|
||||||
if strings.HasSuffix(tls, "tls") {
|
if strings.HasSuffix(tls, "tls") {
|
||||||
vmess["tls"] = true
|
vmess["tls"] = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
switch network {
|
switch network {
|
||||||
case "http":
|
case "http":
|
||||||
|
@ -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)
|
||||||
|
@ -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":
|
||||||
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
)
|
|
@ -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
|
|
||||||
)
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}()
|
}()
|
||||||
|
@ -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)
|
|
||||||
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
@ -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}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -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}
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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, "@")
|
|
||||||
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)
|
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return geoLoader.LoadGeoSite(listName)
|
|
||||||
})
|
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return geoLoader.LoadGeoIP(country)
|
|
||||||
})
|
|
||||||
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{}
|
|
||||||
}
|
|
||||||
|
@ -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(),
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -28,8 +28,8 @@ 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
|
||||||
@ -37,7 +37,7 @@ type SnifferDispatcher struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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]()}
|
||||||
|
@ -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)
|
|
||||||
}
|
|
@ -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"))
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
106
config/config.go
106
config/config.go
@ -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 {
|
||||||
@ -951,7 +955,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
|
|||||||
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,
|
||||||
@ -961,12 +965,6 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
|
|||||||
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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,14 +149,23 @@ 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() {
|
}
|
||||||
|
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
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
//go:build with_low_memory
|
|
||||||
package features
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
TAGS = append(TAGS, "with_low_memory")
|
|
||||||
}
|
|
7
constant/features/no_doq.go
Normal file
7
constant/features/no_doq.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
//go:build no_doq
|
||||||
|
|
||||||
|
package features
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
TAGS = append(TAGS, "no_doq")
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
//go:build no_fake_tcp
|
|
||||||
|
|
||||||
package features
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
TAGS = append(TAGS, "no_fake_tcp")
|
|
||||||
}
|
|
7
constant/features/no_gvisor.go
Normal file
7
constant/features/no_gvisor.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
//go:build no_gvisor
|
||||||
|
|
||||||
|
package features
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
TAGS = append(TAGS, "no_gvisor")
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
//go:build with_gvisor
|
|
||||||
|
|
||||||
package features
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
TAGS = append(TAGS, "with_gvisor")
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
dns/doh.go
14
dns/doh.go
@ -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,9 +390,7 @@ 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)
|
||||||
|
|
||||||
if slices.Contains(doh.httpVersions, C.HTTPVersion3) {
|
|
||||||
// First, we attempt to create an HTTP3 transport. If the probe QUIC
|
// First, we attempt to create an HTTP3 transport. If the probe QUIC
|
||||||
// connection is established successfully, we'll be using HTTP3 for this
|
// connection is established successfully, we'll be using HTTP3 for this
|
||||||
// upstream.
|
// upstream.
|
||||||
@ -404,7 +399,6 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp
|
|||||||
log.Debugln("[%s] using HTTP/3 for this upstream: QUIC was faster", doh.url.String())
|
log.Debugln("[%s] using HTTP/3 for this upstream: QUIC was faster", doh.url.String())
|
||||||
return transportH3, nil
|
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
|
||||||
}
|
}
|
||||||
|
15
dns/doq.go
15
dns/doq.go
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
@ -413,8 +432,6 @@ type Config struct {
|
|||||||
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
|
|
||||||
|
52
dns/util.go
52
dns/util.go
@ -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
|
|
||||||
proxyAdapter, ok = tunnel.Proxies()[proxyName]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
opts = append(opts, dialer.WithInterface(proxyName))
|
opts = append(opts, dialer.WithInterface(proxyAdapter))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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
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
Reference in New Issue
Block a user