Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
8293b7fdae | |||
0ba415866e | |||
53b41ca166 | |||
8a75f78e63 | |||
d9692c6366 | |||
f4b0062dfc | |||
b9ffc82e53 | |||
78aaea6a45 | |||
3645fbf161 | |||
a1d0f22132 | |||
fa73b0f4bf | |||
3b76a8b839 | |||
667f42dcdc | |||
dfbe09860f | |||
9e20f9c26a | |||
f968d0cb82 | |||
2ad84f4379 | |||
c7aa16426f | |||
5987f8e3b5 | |||
3a8eb72de2 | |||
33abbdfd24 | |||
0703d6cbff | |||
10d2d14938 | |||
691cf1d8d6 | |||
d1decb8e58 | |||
7d04904109 | |||
a5acd3aa97 | |||
eea9a12560 | |||
0a4570b55c |
16
.github/workflows/Delete.yml
vendored
Normal file
16
.github/workflows/Delete.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
name: Delete old workflow runs
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 1 * *'
|
||||||
|
# Run monthly, at 00:00 on the 1st day of month.
|
||||||
|
|
||||||
|
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
|
50
.github/workflows/build.yml
vendored
50
.github/workflows/build.yml
vendored
@ -16,11 +16,6 @@ on:
|
|||||||
- Alpha
|
- Alpha
|
||||||
- Beta
|
- Beta
|
||||||
- Meta
|
- Meta
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.ref }}-${{ github.workflow }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: docker.io
|
REGISTRY: docker.io
|
||||||
jobs:
|
jobs:
|
||||||
@ -51,26 +46,25 @@ jobs:
|
|||||||
target: "linux-mips-softfloat linux-mips-hardfloat linux-mipsle-softfloat linux-mipsle-hardfloat",
|
target: "linux-mips-softfloat linux-mips-hardfloat linux-mipsle-softfloat linux-mipsle-hardfloat",
|
||||||
id: "4",
|
id: "4",
|
||||||
}
|
}
|
||||||
- { type: "WithoutCGO", target: "linux-386 linux-riscv64", id: "5" }
|
|
||||||
- {
|
- {
|
||||||
type: "WithoutCGO",
|
type: "WithoutCGO",
|
||||||
target: "freebsd-386 freebsd-amd64 freebsd-arm64",
|
target: "freebsd-386 freebsd-amd64 freebsd-arm64",
|
||||||
id: "6",
|
id: "5",
|
||||||
}
|
}
|
||||||
- {
|
- {
|
||||||
type: "WithoutCGO",
|
type: "WithoutCGO",
|
||||||
target: "windows-amd64-compatible windows-amd64 windows-386",
|
target: "windows-amd64-compatible windows-amd64 windows-386",
|
||||||
id: "7",
|
id: "6",
|
||||||
}
|
}
|
||||||
- {
|
- {
|
||||||
type: "WithoutCGO",
|
type: "WithoutCGO",
|
||||||
target: "windows-arm64 windows-arm32v7",
|
target: "windows-arm64 windows-arm32v7",
|
||||||
id: "8",
|
id: "7",
|
||||||
}
|
}
|
||||||
- {
|
- {
|
||||||
type: "WithoutCGO",
|
type: "WithoutCGO",
|
||||||
target: "darwin-amd64 darwin-arm64 android-arm64",
|
target: "darwin-amd64 darwin-arm64 android-arm64",
|
||||||
id: "9",
|
id: "8",
|
||||||
}
|
}
|
||||||
- { type: "WithCGO", target: "windows/*", id: "1" }
|
- { type: "WithCGO", target: "windows/*", id: "1" }
|
||||||
- { type: "WithCGO", target: "linux/386", id: "2" }
|
- { type: "WithCGO", target: "linux/386", id: "2" }
|
||||||
@ -114,11 +108,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Set ENV
|
- name: Set ENV
|
||||||
run: |
|
run: |
|
||||||
sudo timedatectl set-timezone "Asia/Shanghai"
|
|
||||||
echo "NAME=clash.meta" >> $GITHUB_ENV
|
echo "NAME=clash.meta" >> $GITHUB_ENV
|
||||||
echo "REPO=${{ github.repository }}" >> $GITHUB_ENV
|
echo "REPO=${{ github.repository }}" >> $GITHUB_ENV
|
||||||
echo "ShortSHA=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV
|
echo "ShortSHA=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV
|
||||||
echo "BUILDTIME=$(date)" >> $GITHUB_ENV
|
echo "BUILDTIME=$(date -u)" >> $GITHUB_ENV
|
||||||
echo "BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV
|
echo "BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
@ -200,10 +193,6 @@ jobs:
|
|||||||
ls -la
|
ls -la
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
- name: Save version
|
|
||||||
run: echo ${VERSION} > bin/version.txt
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
with:
|
with:
|
||||||
@ -213,7 +202,7 @@ jobs:
|
|||||||
Upload-Prerelease:
|
Upload-Prerelease:
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
if: ${{ github.ref_type=='branch' }}
|
if: ${{ github.ref_type=='branch' }}
|
||||||
needs: [Build]
|
needs: [ Build ]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
@ -232,11 +221,6 @@ jobs:
|
|||||||
tag: Prerelease-${{ github.ref_name }}
|
tag: Prerelease-${{ github.ref_name }}
|
||||||
deleteOnlyFromDrafts: false
|
deleteOnlyFromDrafts: false
|
||||||
|
|
||||||
- name: Set Env
|
|
||||||
run: |
|
|
||||||
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Tag Repo
|
- name: Tag Repo
|
||||||
uses: richardsimko/update-tag@v1.0.6
|
uses: richardsimko/update-tag@v1.0.6
|
||||||
with:
|
with:
|
||||||
@ -244,34 +228,20 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- run: |
|
|
||||||
cat > release.txt << 'EOF'
|
|
||||||
Release created at ${{ env.BUILDTIME }}
|
|
||||||
Synchronize ${{ github.ref_name }} branch code updates, keeping only the latest version
|
|
||||||
<br>
|
|
||||||
### release version
|
|
||||||
`default(not specified in file name)`: compiled with GOAMD64=v3
|
|
||||||
`cgo`: support lwip tun stack, compiled with GOAMD64=v1
|
|
||||||
`compatible`: compiled with GOAMD64=v1
|
|
||||||
Check details between different architectural levels [here](https://github.com/golang/go/wiki/MinimumRequirements#amd64).
|
|
||||||
EOF
|
|
||||||
|
|
||||||
- name: Upload Prerelease
|
- name: Upload Prerelease
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@v1
|
||||||
if: ${{ success() }}
|
if: ${{ success() }}
|
||||||
with:
|
with:
|
||||||
tag: ${{ github.ref_name }}
|
tag: ${{ github.ref_name }}
|
||||||
tag_name: Prerelease-${{ github.ref_name }}
|
tag_name: Prerelease-${{ github.ref_name }}
|
||||||
files: |
|
files: bin/*
|
||||||
bin/*
|
|
||||||
prerelease: true
|
prerelease: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
body_path: release.txt
|
|
||||||
|
|
||||||
Upload-Release:
|
Upload-Release:
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
if: ${{ github.ref_type=='tag' }}
|
if: ${{ github.ref_type=='tag' }}
|
||||||
needs: [Build]
|
needs: [ Build ]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v3
|
- uses: actions/download-artifact@v3
|
||||||
@ -294,7 +264,7 @@ jobs:
|
|||||||
|
|
||||||
Docker:
|
Docker:
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
needs: [Build]
|
needs: [ Build ]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@ -351,7 +321,5 @@ jobs:
|
|||||||
linux/386
|
linux/386
|
||||||
linux/amd64
|
linux/amd64
|
||||||
linux/arm64/v8
|
linux/arm64/v8
|
||||||
linux/arm/v7
|
|
||||||
# linux/riscv64
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
FROM alpine:latest as builder
|
FROM alpine:latest as builder
|
||||||
ARG TARGETPLATFORM
|
|
||||||
RUN echo "I'm building for $TARGETPLATFORM"
|
|
||||||
|
|
||||||
RUN apk add --no-cache gzip && \
|
RUN apk add --no-cache gzip && \
|
||||||
mkdir /clash-config && \
|
mkdir /clash-config && \
|
||||||
@ -12,7 +10,7 @@ COPY docker/file-name.sh /clash/file-name.sh
|
|||||||
WORKDIR /clash
|
WORKDIR /clash
|
||||||
COPY bin/ bin/
|
COPY bin/ bin/
|
||||||
RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \
|
RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \
|
||||||
FILE_NAME=`ls bin/ | egrep "$FILE_NAME.*"|awk NR==1` && echo $FILE_NAME && \
|
FILE_NAME=`ls bin/ | egrep "$FILE_NAME.*"|awk NR==1` && \
|
||||||
mv bin/$FILE_NAME clash.gz && gzip -d clash.gz && echo "$FILE_NAME" > /clash-config/test
|
mv bin/$FILE_NAME clash.gz && gzip -d clash.gz && echo "$FILE_NAME" > /clash-config/test
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/Clash.Meta"
|
LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/Clash.Meta"
|
||||||
|
3
Makefile
3
Makefile
@ -101,9 +101,6 @@ linux-mips64:
|
|||||||
linux-mips64le:
|
linux-mips64le:
|
||||||
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
linux-riscv64:
|
|
||||||
GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
|
||||||
|
|
||||||
android-arm64:
|
android-arm64:
|
||||||
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ func NewInner(conn net.Conn, dst string, host string) *context.ConnContext {
|
|||||||
metadata := &C.Metadata{}
|
metadata := &C.Metadata{}
|
||||||
metadata.NetWork = C.TCP
|
metadata.NetWork = C.TCP
|
||||||
metadata.Type = C.INNER
|
metadata.Type = C.INNER
|
||||||
metadata.DNSMode = C.DNSNormal
|
metadata.DNSMode = C.DNSMapping
|
||||||
metadata.Host = host
|
metadata.Host = host
|
||||||
metadata.Process = C.ClashName
|
metadata.Process = C.ClashName
|
||||||
if h, port, err := net.SplitHostPort(dst); err == nil {
|
if h, port, err := net.SplitHostPort(dst); err == nil {
|
||||||
|
@ -8,9 +8,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Base struct {
|
type Base struct {
|
||||||
@ -34,7 +35,7 @@ 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 == "" {
|
||||||
id, err := utils.UnsafeUUIDGenerator.NewV6()
|
id, err := uuid.NewV6()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.id = b.name
|
b.id = b.name
|
||||||
} else {
|
} else {
|
||||||
@ -139,15 +140,10 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.tfo {
|
|
||||||
opts = append(opts, dialer.WithTFO(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
type BasicOption struct {
|
type BasicOption struct {
|
||||||
TFO bool `proxy:"tfo,omitempty" group:"tfo,omitempty"`
|
|
||||||
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"`
|
||||||
|
@ -170,7 +170,6 @@ func NewHttp(option HttpOption) (*Http, error) {
|
|||||||
name: option.Name,
|
name: option.Name,
|
||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Http,
|
tp: C.Http,
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
package outbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/curve25519"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RealityOptions struct {
|
|
||||||
PublicKey string `proxy:"public-key"`
|
|
||||||
ShortID string `proxy:"short-id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) {
|
|
||||||
if o.PublicKey != "" {
|
|
||||||
config := new(tlsC.RealityConfig)
|
|
||||||
|
|
||||||
n, err := base64.RawURLEncoding.Decode(config.PublicKey[:], []byte(o.PublicKey))
|
|
||||||
if err != nil || n != curve25519.ScalarSize {
|
|
||||||
return nil, errors.New("invalid REALITY public key")
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = hex.Decode(config.ShortID[:], []byte(o.ShortID))
|
|
||||||
if err != nil || n > tlsC.RealityMaxShortIDLen {
|
|
||||||
return nil, errors.New("invalid REALITY short ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
@ -6,7 +6,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/buf"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
)
|
)
|
||||||
@ -17,12 +16,12 @@ type Reject struct {
|
|||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
return NewConn(nopConn{}, r), nil
|
return NewConn(&nopConn{}, r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenPacketContext implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
return newPacketConn(nopPacketConn{}, r), nil
|
return newPacketConn(&nopPacketConn{}, r), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReject() *Reject {
|
func NewReject() *Reject {
|
||||||
@ -49,37 +48,27 @@ func NewPass() *Reject {
|
|||||||
|
|
||||||
type nopConn struct{}
|
type nopConn struct{}
|
||||||
|
|
||||||
func (rw nopConn) Read(b []byte) (int, error) {
|
func (rw *nopConn) Read(b []byte) (int, error) {
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw nopConn) ReadBuffer(buffer *buf.Buffer) error {
|
func (rw *nopConn) Write(b []byte) (int, error) {
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rw nopConn) Write(b []byte) (int, error) {
|
|
||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rw nopConn) WriteBuffer(buffer *buf.Buffer) error {
|
func (rw *nopConn) Close() error { return nil }
|
||||||
return io.EOF
|
func (rw *nopConn) LocalAddr() net.Addr { return nil }
|
||||||
}
|
func (rw *nopConn) RemoteAddr() net.Addr { return nil }
|
||||||
|
func (rw *nopConn) SetDeadline(time.Time) error { return nil }
|
||||||
func (rw nopConn) Close() error { return nil }
|
func (rw *nopConn) SetReadDeadline(time.Time) error { return nil }
|
||||||
func (rw nopConn) LocalAddr() net.Addr { return nil }
|
func (rw *nopConn) SetWriteDeadline(time.Time) error { return nil }
|
||||||
func (rw nopConn) RemoteAddr() net.Addr { return nil }
|
|
||||||
func (rw nopConn) SetDeadline(time.Time) error { return nil }
|
|
||||||
func (rw nopConn) SetReadDeadline(time.Time) error { return nil }
|
|
||||||
func (rw nopConn) SetWriteDeadline(time.Time) error { return nil }
|
|
||||||
|
|
||||||
var udpAddrIPv4Unspecified = &net.UDPAddr{IP: net.IPv4zero, Port: 0}
|
|
||||||
|
|
||||||
type nopPacketConn struct{}
|
type nopPacketConn struct{}
|
||||||
|
|
||||||
func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil }
|
func (npc *nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil }
|
||||||
func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF }
|
func (npc *nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF }
|
||||||
func (npc nopPacketConn) Close() error { return nil }
|
func (npc *nopPacketConn) Close() error { return nil }
|
||||||
func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified }
|
func (npc *nopPacketConn) LocalAddr() net.Addr { return &net.UDPAddr{IP: net.IPv4zero, Port: 0} }
|
||||||
func (npc nopPacketConn) SetDeadline(time.Time) error { return nil }
|
func (npc *nopPacketConn) SetDeadline(time.Time) error { return nil }
|
||||||
func (npc nopPacketConn) SetReadDeadline(time.Time) error { return nil }
|
func (npc *nopPacketConn) SetReadDeadline(time.Time) error { return nil }
|
||||||
func (npc nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
|
func (npc *nopPacketConn) SetWriteDeadline(time.Time) error { return nil }
|
||||||
|
@ -2,18 +2,18 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
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"
|
||||||
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/shadowtls"
|
||||||
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
obfs "github.com/Dreamacro/clash/transport/simple-obfs"
|
||||||
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"
|
||||||
|
|
||||||
@ -33,7 +33,8 @@ type ShadowSocks struct {
|
|||||||
obfsMode string
|
obfsMode string
|
||||||
obfsOption *simpleObfsOption
|
obfsOption *simpleObfsOption
|
||||||
v2rayOption *v2rayObfs.Option
|
v2rayOption *v2rayObfs.Option
|
||||||
shadowTLSOption *shadowtls.ShadowTLSOption
|
shadowTLSOption *shadowTLSOption
|
||||||
|
tlsConfig *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShadowSocksOption struct {
|
type ShadowSocksOption struct {
|
||||||
@ -66,31 +67,14 @@ type v2rayObfsOption struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type shadowTLSOption struct {
|
type shadowTLSOption struct {
|
||||||
Password string `obfs:"password"`
|
Password string `obfs:"password"`
|
||||||
Host string `obfs:"host"`
|
Host string `obfs:"host"`
|
||||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
|
||||||
defer cancel()
|
|
||||||
var err error
|
|
||||||
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ss.streamConn(c, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ss *ShadowSocks) streamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|
||||||
switch ss.obfsMode {
|
switch ss.obfsMode {
|
||||||
case "tls":
|
case "tls":
|
||||||
c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
|
c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
|
||||||
@ -103,19 +87,13 @@ func (ss *ShadowSocks) streamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
|
|||||||
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:
|
||||||
|
c = shadowtls.NewShadowTLS(c, ss.shadowTLSOption.Password, ss.tlsConfig)
|
||||||
}
|
}
|
||||||
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
|
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
|
||||||
if N.NeedHandshake(c) {
|
return ss.method.DialConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443"))
|
||||||
return ss.method.DialEarlyConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443")), nil
|
|
||||||
} else {
|
|
||||||
return ss.method.DialConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if N.NeedHandshake(c) {
|
|
||||||
return ss.method.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
|
|
||||||
} else {
|
|
||||||
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
|
||||||
}
|
}
|
||||||
|
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
@ -135,15 +113,7 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
|
|||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
|
|
||||||
switch ss.obfsMode {
|
c, err = ss.StreamConn(c, metadata)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,14 +164,15 @@ func (ss *ShadowSocks) SupportUOT() bool {
|
|||||||
|
|
||||||
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
method, err := shadowimpl.FetchMethod(option.Cipher, option.Password, time.Now)
|
method, err := shadowimpl.FetchMethod(option.Cipher, option.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ss %s initialize error: %w", addr, err)
|
return nil, fmt.Errorf("ss %s initialize error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var v2rayOption *v2rayObfs.Option
|
var v2rayOption *v2rayObfs.Option
|
||||||
var obfsOption *simpleObfsOption
|
var obfsOption *simpleObfsOption
|
||||||
var shadowTLSOpt *shadowtls.ShadowTLSOption
|
var shadowTLSOpt *shadowTLSOption
|
||||||
|
var tlsConfig *tls.Config
|
||||||
obfsMode := ""
|
obfsMode := ""
|
||||||
|
|
||||||
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
|
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
|
||||||
@ -239,20 +210,24 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
}
|
}
|
||||||
} else if option.Plugin == shadowtls.Mode {
|
} else if option.Plugin == shadowtls.Mode {
|
||||||
obfsMode = shadowtls.Mode
|
obfsMode = shadowtls.Mode
|
||||||
opt := &shadowTLSOption{
|
shadowTLSOpt = &shadowTLSOption{}
|
||||||
Version: 2,
|
if err := decoder.Decode(option.PluginOpts, shadowTLSOpt); err != nil {
|
||||||
}
|
|
||||||
if err := decoder.Decode(option.PluginOpts, opt); err != nil {
|
|
||||||
return nil, fmt.Errorf("ss %s initialize shadow-tls-plugin error: %w", addr, err)
|
return nil, fmt.Errorf("ss %s initialize shadow-tls-plugin error: %w", addr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowTLSOpt = &shadowtls.ShadowTLSOption{
|
tlsConfig = &tls.Config{
|
||||||
Password: opt.Password,
|
NextProtos: shadowtls.DefaultALPN,
|
||||||
Host: opt.Host,
|
MinVersion: tls.VersionTLS12,
|
||||||
Fingerprint: opt.Fingerprint,
|
InsecureSkipVerify: shadowTLSOpt.SkipCertVerify,
|
||||||
ClientFingerprint: opt.ClientFingerprint,
|
ServerName: shadowTLSOpt.Host,
|
||||||
SkipCertVerify: opt.SkipCertVerify,
|
}
|
||||||
Version: opt.Version,
|
|
||||||
|
if len(shadowTLSOpt.Fingerprint) == 0 {
|
||||||
|
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
||||||
|
} else {
|
||||||
|
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, shadowTLSOpt.Fingerprint); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,7 +237,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
addr: addr,
|
addr: addr,
|
||||||
tp: C.Shadowsocks,
|
tp: C.Shadowsocks,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||||
@ -274,6 +248,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
v2rayOption: v2rayOption,
|
v2rayOption: v2rayOption,
|
||||||
obfsOption: obfsOption,
|
obfsOption: obfsOption,
|
||||||
shadowTLSOption: shadowTLSOpt,
|
shadowTLSOption: shadowTLSOpt,
|
||||||
|
tlsConfig: tlsConfig,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +163,6 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
|||||||
addr: addr,
|
addr: addr,
|
||||||
tp: C.ShadowsocksR,
|
tp: C.ShadowsocksR,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||||
|
@ -167,7 +167,6 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
|||||||
addr: addr,
|
addr: addr,
|
||||||
tp: C.Snell,
|
tp: C.Snell,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||||
|
@ -182,7 +182,6 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
|
|||||||
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
|
||||||
tp: C.Socks5,
|
tp: C.Socks5,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||||
|
@ -26,28 +26,25 @@ type Trojan struct {
|
|||||||
gunTLSConfig *tls.Config
|
gunTLSConfig *tls.Config
|
||||||
gunConfig *gun.Config
|
gunConfig *gun.Config
|
||||||
transport *gun.TransportWrap
|
transport *gun.TransportWrap
|
||||||
|
|
||||||
realityConfig *tlsC.RealityConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TrojanOption struct {
|
type TrojanOption struct {
|
||||||
BasicOption
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
Password string `proxy:"password"`
|
Password string `proxy:"password"`
|
||||||
ALPN []string `proxy:"alpn,omitempty"`
|
ALPN []string `proxy:"alpn,omitempty"`
|
||||||
SNI string `proxy:"sni,omitempty"`
|
SNI string `proxy:"sni,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
Flow string `proxy:"flow,omitempty"`
|
||||||
Flow string `proxy:"flow,omitempty"`
|
FlowShow bool `proxy:"flow-show,omitempty"`
|
||||||
FlowShow bool `proxy:"flow-show,omitempty"`
|
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
|
func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
|
||||||
@ -86,7 +83,7 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if t.transport != nil {
|
if t.transport != nil {
|
||||||
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.realityConfig)
|
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
|
||||||
} else {
|
} else {
|
||||||
c, err = t.plainStream(c)
|
c, err = t.plainStream(c)
|
||||||
}
|
}
|
||||||
@ -253,7 +250,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
addr: addr,
|
addr: addr,
|
||||||
tp: C.Trojan,
|
tp: C.Trojan,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||||
@ -262,13 +258,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
option: &option,
|
option: &option,
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
t.realityConfig, err = option.RealityOpts.Parse()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tOption.Reality = t.realityConfig
|
|
||||||
|
|
||||||
if option.Network == "grpc" {
|
if option.Network == "grpc" {
|
||||||
dialFn := func(network, addr string) (net.Conn, error) {
|
dialFn := func(network, addr string) (net.Conn, error) {
|
||||||
c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...)
|
c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...)
|
||||||
@ -295,7 +284,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint, t.realityConfig)
|
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint)
|
||||||
|
|
||||||
t.gunTLSConfig = tlsConfig
|
t.gunTLSConfig = tlsConfig
|
||||||
t.gunConfig = &gun.Config{
|
t.gunConfig = &gun.Config{
|
||||||
|
@ -51,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"`
|
||||||
SNI string `proxy:"sni,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
@ -107,14 +106,12 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (pc net.Pack
|
|||||||
func NewTuic(option TuicOption) (*Tuic, error) {
|
func NewTuic(option TuicOption) (*Tuic, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
serverName := option.Server
|
serverName := option.Server
|
||||||
|
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
ServerName: serverName,
|
ServerName: serverName,
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
InsecureSkipVerify: option.SkipCertVerify,
|
||||||
MinVersion: tls.VersionTLS13,
|
MinVersion: tls.VersionTLS13,
|
||||||
}
|
}
|
||||||
if option.SNI != "" {
|
|
||||||
tlsConfig.ServerName = option.SNI
|
|
||||||
}
|
|
||||||
|
|
||||||
var bs []byte
|
var bs []byte
|
||||||
var err error
|
var err error
|
||||||
@ -216,7 +213,6 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
|||||||
udp: true,
|
udp: true,
|
||||||
tfo: option.FastOpen,
|
tfo: option.FastOpen,
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ import (
|
|||||||
"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"
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"github.com/Dreamacro/clash/transport/gun"
|
"github.com/Dreamacro/clash/transport/gun"
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
"github.com/Dreamacro/clash/transport/vless"
|
"github.com/Dreamacro/clash/transport/vless"
|
||||||
@ -42,8 +41,6 @@ type Vless struct {
|
|||||||
gunTLSConfig *tls.Config
|
gunTLSConfig *tls.Config
|
||||||
gunConfig *gun.Config
|
gunConfig *gun.Config
|
||||||
transport *gun.TransportWrap
|
transport *gun.TransportWrap
|
||||||
|
|
||||||
realityConfig *tlsC.RealityConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type VlessOption struct {
|
type VlessOption struct {
|
||||||
@ -60,7 +57,6 @@ type VlessOption struct {
|
|||||||
XUDP bool `proxy:"xudp,omitempty"`
|
XUDP bool `proxy:"xudp,omitempty"`
|
||||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
@ -82,6 +78,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
|
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
|
|
||||||
host, port, _ := net.SplitHostPort(v.addr)
|
host, port, _ := net.SplitHostPort(v.addr)
|
||||||
wsOpts := &vmess.WebsocketConfig{
|
wsOpts := &vmess.WebsocketConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
@ -157,7 +154,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
|
|
||||||
c, err = vmess.StreamH2Conn(c, h2Opts)
|
c, err = vmess.StreamH2Conn(c, h2Opts)
|
||||||
case "grpc":
|
case "grpc":
|
||||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
||||||
default:
|
default:
|
||||||
// default tcp network
|
// default tcp network
|
||||||
// handle TLS And XTLS
|
// handle TLS And XTLS
|
||||||
@ -174,7 +171,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) {
|
func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) {
|
||||||
host, _, _ := net.SplitHostPort(v.addr)
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
|
|
||||||
if v.isLegacyXTLSEnabled() && !isH2 {
|
if v.isXTLSEnabled() && !isH2 {
|
||||||
xtlsOpts := vless.XTLSConfig{
|
xtlsOpts := vless.XTLSConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
@ -193,7 +190,6 @@ func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error)
|
|||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
FingerPrint: v.option.Fingerprint,
|
FingerPrint: v.option.Fingerprint,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
Reality: v.realityConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if isH2 {
|
if isH2 {
|
||||||
@ -210,8 +206,8 @@ func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error)
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vless) isLegacyXTLSEnabled() bool {
|
func (v *Vless) isXTLSEnabled() bool {
|
||||||
return v.client.Addons != nil && v.client.Addons.Flow != vless.XRV
|
return v.client.Addons != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
@ -483,9 +479,6 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
if option.Network != "ws" && len(option.Flow) >= 16 {
|
if option.Network != "ws" && len(option.Flow) >= 16 {
|
||||||
option.Flow = option.Flow[:16]
|
option.Flow = option.Flow[:16]
|
||||||
switch option.Flow {
|
switch option.Flow {
|
||||||
case vless.XRV:
|
|
||||||
log.Warnln("To use %s, ensure your server is upgrade to Xray-core v1.8.0+", vless.XRV)
|
|
||||||
fallthrough
|
|
||||||
case vless.XRO, vless.XRD, vless.XRS:
|
case vless.XRO, vless.XRD, vless.XRS:
|
||||||
addons = &vless.Addons{
|
addons = &vless.Addons{
|
||||||
Flow: option.Flow,
|
Flow: option.Flow,
|
||||||
@ -517,7 +510,6 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
tp: C.Vless,
|
tp: C.Vless,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
xudp: option.XUDP,
|
xudp: option.XUDP,
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||||
@ -526,11 +518,6 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
option: &option,
|
option: &option,
|
||||||
}
|
}
|
||||||
|
|
||||||
v.realityConfig, err = v.option.RealityOpts.Parse()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch option.Network {
|
switch option.Network {
|
||||||
case "h2":
|
case "h2":
|
||||||
if len(option.HTTP2Opts.Host) == 0 {
|
if len(option.HTTP2Opts.Host) == 0 {
|
||||||
@ -565,7 +552,8 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
v.gunTLSConfig = tlsConfig
|
v.gunTLSConfig = tlsConfig
|
||||||
v.gunConfig = gunConfig
|
v.gunConfig = gunConfig
|
||||||
|
|
||||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
|
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"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"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
@ -35,35 +34,32 @@ type Vmess struct {
|
|||||||
gunTLSConfig *tls.Config
|
gunTLSConfig *tls.Config
|
||||||
gunConfig *gun.Config
|
gunConfig *gun.Config
|
||||||
transport *gun.TransportWrap
|
transport *gun.TransportWrap
|
||||||
|
|
||||||
realityConfig *tlsC.RealityConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type VmessOption struct {
|
type VmessOption struct {
|
||||||
BasicOption
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
UUID string `proxy:"uuid"`
|
UUID string `proxy:"uuid"`
|
||||||
AlterID int `proxy:"alterId"`
|
AlterID int `proxy:"alterId"`
|
||||||
Cipher string `proxy:"cipher"`
|
Cipher string `proxy:"cipher"`
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||||
ServerName string `proxy:"servername,omitempty"`
|
ServerName string `proxy:"servername,omitempty"`
|
||||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
||||||
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
XUDP bool `proxy:"xudp,omitempty"`
|
||||||
XUDP bool `proxy:"xudp,omitempty"`
|
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
GlobalPadding bool `proxy:"global-padding,omitempty"`
|
||||||
GlobalPadding bool `proxy:"global-padding,omitempty"`
|
AuthenticatedLength bool `proxy:"authenticated-length,omitempty"`
|
||||||
AuthenticatedLength bool `proxy:"authenticated-length,omitempty"`
|
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPOptions struct {
|
type HTTPOptions struct {
|
||||||
@ -98,6 +94,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
|
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
|
|
||||||
host, port, _ := net.SplitHostPort(v.addr)
|
host, port, _ := net.SplitHostPort(v.addr)
|
||||||
wsOpts := &clashVMess.WebsocketConfig{
|
wsOpts := &clashVMess.WebsocketConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
@ -146,12 +143,12 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
Reality: v.realityConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
tlsOpts.Host = v.option.ServerName
|
tlsOpts.Host = v.option.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err = clashVMess.StreamTLSConn(c, tlsOpts)
|
c, err = clashVMess.StreamTLSConn(c, tlsOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -174,7 +171,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
NextProtos: []string{"h2"},
|
NextProtos: []string{"h2"},
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
Reality: v.realityConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
@ -193,7 +189,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
|
|
||||||
c, err = clashVMess.StreamH2Conn(c, h2Opts)
|
c, err = clashVMess.StreamH2Conn(c, h2Opts)
|
||||||
case "grpc":
|
case "grpc":
|
||||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
|
||||||
default:
|
default:
|
||||||
// handle TLS
|
// handle TLS
|
||||||
if v.option.TLS {
|
if v.option.TLS {
|
||||||
@ -202,7 +198,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
Reality: v.realityConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
@ -218,24 +213,12 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
if metadata.NetWork == C.UDP {
|
if metadata.NetWork == C.UDP {
|
||||||
if v.option.XUDP {
|
if v.option.XUDP {
|
||||||
if N.NeedHandshake(c) {
|
return v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||||
return v.client.DialEarlyXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
|
|
||||||
} else {
|
|
||||||
return v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if N.NeedHandshake(c) {
|
return v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||||
return v.client.DialEarlyPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
|
|
||||||
} else {
|
|
||||||
return v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if N.NeedHandshake(c) {
|
return v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||||
return v.client.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
|
|
||||||
} else {
|
|
||||||
return v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,17 +289,9 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
|||||||
}(c)
|
}(c)
|
||||||
|
|
||||||
if v.option.XUDP {
|
if v.option.XUDP {
|
||||||
if N.NeedHandshake(c) {
|
c, err = v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||||
c = v.client.DialEarlyXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
|
||||||
} else {
|
|
||||||
c, err = v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if N.NeedHandshake(c) {
|
c, err = v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
|
||||||
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 {
|
||||||
@ -412,7 +387,6 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
tp: C.Vmess,
|
tp: C.Vmess,
|
||||||
udp: option.UDP,
|
udp: option.UDP,
|
||||||
xudp: option.XUDP,
|
xudp: option.XUDP,
|
||||||
tfo: option.TFO,
|
|
||||||
iface: option.Interface,
|
iface: option.Interface,
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||||
@ -455,14 +429,9 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
v.gunTLSConfig = tlsConfig
|
v.gunTLSConfig = tlsConfig
|
||||||
v.gunConfig = gunConfig
|
v.gunConfig = gunConfig
|
||||||
|
|
||||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
|
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint)
|
||||||
}
|
|
||||||
|
|
||||||
v.realityConfig, err = v.option.RealityOpts.Parse()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
"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/log"
|
"github.com/Dreamacro/clash/listener/sing"
|
||||||
|
|
||||||
wireguard "github.com/metacubex/sing-wireguard"
|
wireguard "github.com/metacubex/sing-wireguard"
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ type WireGuard struct {
|
|||||||
bind *wireguard.ClientBind
|
bind *wireguard.ClientBind
|
||||||
device *device.Device
|
device *device.Device
|
||||||
tunDevice wireguard.Device
|
tunDevice wireguard.Device
|
||||||
dialer *wgSingDialer
|
dialer *wgDialer
|
||||||
startOnce sync.Once
|
startOnce sync.Once
|
||||||
startErr error
|
startErr error
|
||||||
}
|
}
|
||||||
@ -56,28 +56,16 @@ type WireGuardOption struct {
|
|||||||
PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
|
PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type wgSingDialer struct {
|
type wgDialer struct {
|
||||||
dialer dialer.Dialer
|
options []dialer.Option
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ N.Dialer = &wgSingDialer{}
|
func (d *wgDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
return dialer.DialContext(ctx, network, destination.String(), d.options...)
|
||||||
func (d *wgSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
|
||||||
return d.dialer.DialContext(ctx, network, destination.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *wgSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *wgDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
return d.dialer.ListenPacket(ctx, "udp", "", destination.AddrPort())
|
return dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", destination.Addr), "", d.options...)
|
||||||
}
|
|
||||||
|
|
||||||
type wgNetDialer struct {
|
|
||||||
tunDevice wireguard.Device
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ dialer.NetDialer = &wgNetDialer{}
|
|
||||||
|
|
||||||
func (d wgNetDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
return d.tunDevice.DialContext(ctx, network, M.ParseSocksaddr(address).Unwrap())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||||
@ -91,7 +79,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
|||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||||
},
|
},
|
||||||
dialer: &wgSingDialer{dialer: dialer.NewDialer()},
|
dialer: &wgDialer{},
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(outbound, closeWireGuard)
|
runtime.SetFinalizer(outbound, closeWireGuard)
|
||||||
|
|
||||||
@ -186,14 +174,14 @@ 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(strings.ToLower(format), args...))
|
sing.Logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
|
||||||
},
|
},
|
||||||
Errorf: func(format string, args ...interface{}) {
|
Errorf: func(format string, args ...interface{}) {
|
||||||
log.SingLogger.Error(fmt.Sprintf(strings.ToLower(format), args...))
|
sing.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...))
|
||||||
},
|
},
|
||||||
}, option.Workers)
|
}, option.Workers)
|
||||||
if debug.Enabled {
|
if debug.Enabled {
|
||||||
log.SingLogger.Trace("created wireguard ipc conf: \n", ipcConf)
|
sing.Logger.Trace("created wireguard ipc conf: \n", ipcConf)
|
||||||
}
|
}
|
||||||
err = outbound.device.IpcSet(ipcConf)
|
err = outbound.device.IpcSet(ipcConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -211,8 +199,7 @@ func closeWireGuard(w *WireGuard) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
options := w.Base.DialOptions(opts...)
|
w.dialer.options = opts
|
||||||
w.dialer.dialer = dialer.NewDialer(options...)
|
|
||||||
var conn net.Conn
|
var conn net.Conn
|
||||||
w.startOnce.Do(func() {
|
w.startOnce.Do(func() {
|
||||||
w.startErr = w.tunDevice.Start()
|
w.startErr = w.tunDevice.Start()
|
||||||
@ -221,12 +208,15 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
|||||||
return nil, w.startErr
|
return nil, w.startErr
|
||||||
}
|
}
|
||||||
if !metadata.Resolved() {
|
if !metadata.Resolved() {
|
||||||
options = append(options, dialer.WithResolver(resolver.DefaultResolver))
|
var addrs []netip.Addr
|
||||||
options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice}))
|
addrs, err = resolver.LookupIP(ctx, metadata.Host)
|
||||||
conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress())
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn, err = N.DialSerial(ctx, w.tunDevice, "tcp", M.ParseSocksaddr(metadata.RemoteAddress()), addrs)
|
||||||
} else {
|
} else {
|
||||||
port, _ := strconv.Atoi(metadata.DstPort)
|
port, _ := strconv.Atoi(metadata.DstPort)
|
||||||
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap())
|
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, uint16(port)))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -238,8 +228,7 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||||
options := w.Base.DialOptions(opts...)
|
w.dialer.options = opts
|
||||||
w.dialer.dialer = dialer.NewDialer(options...)
|
|
||||||
var pc net.PacketConn
|
var pc net.PacketConn
|
||||||
w.startOnce.Do(func() {
|
w.startOnce.Do(func() {
|
||||||
w.startErr = w.tunDevice.Start()
|
w.startErr = w.tunDevice.Start()
|
||||||
@ -258,7 +247,7 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
|
|||||||
metadata.DstIP = ip
|
metadata.DstIP = ip
|
||||||
}
|
}
|
||||||
port, _ := strconv.Atoi(metadata.DstPort)
|
port, _ := strconv.Atoi(metadata.DstPort)
|
||||||
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap())
|
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, uint16(port)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/common/callback"
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/constant/provider"
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
@ -32,23 +30,11 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts .
|
|||||||
c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
|
c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.AppendToChains(f)
|
c.AppendToChains(f)
|
||||||
|
f.onDialSuccess()
|
||||||
} else {
|
} else {
|
||||||
f.onDialFailed(proxy.Type(), err)
|
f.onDialFailed(proxy.Type(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if N.NeedHandshake(c) {
|
|
||||||
c = &callback.FirstWriteCallBackConn{
|
|
||||||
Conn: c,
|
|
||||||
Callback: func(err error) {
|
|
||||||
if err == nil {
|
|
||||||
f.onDialSuccess()
|
|
||||||
} else {
|
|
||||||
f.onDialFailed(proxy.Type(), err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,14 +6,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/common/cache"
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
"github.com/Dreamacro/clash/common/callback"
|
|
||||||
"github.com/Dreamacro/clash/common/murmur3"
|
"github.com/Dreamacro/clash/common/murmur3"
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/constant/provider"
|
"github.com/Dreamacro/clash/constant/provider"
|
||||||
@ -21,7 +18,7 @@ import (
|
|||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
)
|
)
|
||||||
|
|
||||||
type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy
|
type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy
|
||||||
|
|
||||||
type LoadBalance struct {
|
type LoadBalance struct {
|
||||||
*GroupBase
|
*GroupBase
|
||||||
@ -86,27 +83,17 @@ func jumpHash(key uint64, buckets int32) int32 {
|
|||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
|
func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
|
||||||
proxy := lb.Unwrap(metadata, true)
|
proxy := lb.Unwrap(metadata, true)
|
||||||
c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
|
|
||||||
|
|
||||||
if err == nil {
|
defer func() {
|
||||||
c.AppendToChains(lb)
|
if err == nil {
|
||||||
} else {
|
c.AppendToChains(lb)
|
||||||
lb.onDialFailed(proxy.Type(), err)
|
lb.onDialSuccess()
|
||||||
}
|
} else {
|
||||||
|
lb.onDialFailed(proxy.Type(), err)
|
||||||
if N.NeedHandshake(c) {
|
|
||||||
c = &callback.FirstWriteCallBackConn{
|
|
||||||
Conn: c,
|
|
||||||
Callback: func(err error) {
|
|
||||||
if err == nil {
|
|
||||||
lb.onDialSuccess()
|
|
||||||
} else {
|
|
||||||
lb.onDialFailed(proxy.Type(), err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}()
|
||||||
|
|
||||||
|
c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,26 +115,22 @@ func (lb *LoadBalance) SupportUDP() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func strategyRoundRobin() strategyFn {
|
func strategyRoundRobin() strategyFn {
|
||||||
|
flag := true
|
||||||
idx := 0
|
idx := 0
|
||||||
idxMutex := sync.Mutex{}
|
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
|
||||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
|
||||||
idxMutex.Lock()
|
|
||||||
defer idxMutex.Unlock()
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
length := len(proxies)
|
length := len(proxies)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
if touch {
|
flag = !flag
|
||||||
defer func() {
|
if flag {
|
||||||
idx = (idx + i) % length
|
idx = (idx - 1) % length
|
||||||
}()
|
} else {
|
||||||
}
|
idx = (idx + 2) % length
|
||||||
|
}
|
||||||
for ; i < length; i++ {
|
if idx < 0 {
|
||||||
id := (idx + i) % length
|
idx = idx + length
|
||||||
proxy := proxies[id]
|
}
|
||||||
|
proxy := proxies[idx]
|
||||||
if proxy.Alive() {
|
if proxy.Alive() {
|
||||||
i++
|
|
||||||
return proxy
|
return proxy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,7 +141,7 @@ func strategyRoundRobin() strategyFn {
|
|||||||
|
|
||||||
func strategyConsistentHashing() strategyFn {
|
func strategyConsistentHashing() strategyFn {
|
||||||
maxRetry := 5
|
maxRetry := 5
|
||||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
|
||||||
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
|
key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
|
||||||
buckets := int32(len(proxies))
|
buckets := int32(len(proxies))
|
||||||
for i := 0; i < maxRetry; i, key = i+1, key+1 {
|
for i := 0; i < maxRetry; i, key = i+1, key+1 {
|
||||||
@ -186,7 +169,7 @@ func strategyStickySessions() strategyFn {
|
|||||||
lruCache := cache.New[uint64, int](
|
lruCache := cache.New[uint64, int](
|
||||||
cache.WithAge[uint64, int](int64(ttl.Seconds())),
|
cache.WithAge[uint64, int](int64(ttl.Seconds())),
|
||||||
cache.WithSize[uint64, int](1000))
|
cache.WithSize[uint64, int](1000))
|
||||||
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
|
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
|
||||||
key := uint64(murmur3.Sum32([]byte(getKeyWithSrcAndDst(metadata))))
|
key := uint64(murmur3.Sum32([]byte(getKeyWithSrcAndDst(metadata))))
|
||||||
length := len(proxies)
|
length := len(proxies)
|
||||||
idx, has := lruCache.Get(key)
|
idx, has := lruCache.Get(key)
|
||||||
@ -218,7 +201,7 @@ func strategyStickySessions() strategyFn {
|
|||||||
// Unwrap implements C.ProxyAdapter
|
// Unwrap implements C.ProxyAdapter
|
||||||
func (lb *LoadBalance) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
func (lb *LoadBalance) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
||||||
proxies := lb.GetProxies(touch)
|
proxies := lb.GetProxies(touch)
|
||||||
return lb.strategyFn(proxies, metadata, touch)
|
return lb.strategyFn(proxies, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements C.ProxyAdapter
|
// MarshalJSON implements C.ProxyAdapter
|
||||||
|
@ -176,7 +176,7 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) ([]C.Proxy, []C.Proxy)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Relay) Addr() string {
|
func (r *Relay) Addr() string {
|
||||||
proxies, _ := r.proxies(nil, false)
|
proxies, _ := r.proxies(nil, true)
|
||||||
return proxies[len(proxies)-1].Addr()
|
return proxies[len(proxies)-1].Addr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,8 +6,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/outbound"
|
"github.com/Dreamacro/clash/adapter/outbound"
|
||||||
"github.com/Dreamacro/clash/common/callback"
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/common/singledo"
|
"github.com/Dreamacro/clash/common/singledo"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
@ -40,23 +38,10 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
|
|||||||
c, err = proxy.DialContext(ctx, metadata, u.Base.DialOptions(opts...)...)
|
c, err = proxy.DialContext(ctx, metadata, u.Base.DialOptions(opts...)...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
c.AppendToChains(u)
|
c.AppendToChains(u)
|
||||||
|
u.onDialSuccess()
|
||||||
} else {
|
} else {
|
||||||
u.onDialFailed(proxy.Type(), err)
|
u.onDialFailed(proxy.Type(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if N.NeedHandshake(c) {
|
|
||||||
c = &callback.FirstWriteCallBackConn{
|
|
||||||
Conn: c,
|
|
||||||
Callback: func(err error) {
|
|
||||||
if err == nil {
|
|
||||||
u.onDialSuccess()
|
|
||||||
} else {
|
|
||||||
u.onDialFailed(proxy.Type(), err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,10 +6,10 @@ import (
|
|||||||
|
|
||||||
"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"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid"
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ 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 := ""
|
id := ""
|
||||||
if uid, err := utils.UnsafeUUIDGenerator.NewV4(); err == nil {
|
if uid, err := uuid.NewV4(); err == nil {
|
||||||
id = uid.String()
|
id = uid.String()
|
||||||
}
|
}
|
||||||
log.Debugln("Start New Health Checking {%s}", id)
|
log.Debugln("Start New Health Checking {%s}", id)
|
||||||
|
@ -5,15 +5,9 @@ import (
|
|||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
)
|
)
|
||||||
|
|
||||||
const BufferSize = buf.BufferSize
|
|
||||||
|
|
||||||
type Buffer = buf.Buffer
|
type Buffer = buf.Buffer
|
||||||
|
|
||||||
var New = buf.New
|
|
||||||
var StackNew = buf.StackNew
|
|
||||||
var StackNewSize = buf.StackNewSize
|
var StackNewSize = buf.StackNewSize
|
||||||
var With = buf.With
|
|
||||||
|
|
||||||
var KeepAlive = common.KeepAlive
|
var KeepAlive = common.KeepAlive
|
||||||
|
|
||||||
//go:norace
|
//go:norace
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package callback
|
|
||||||
|
|
||||||
import (
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FirstWriteCallBackConn struct {
|
|
||||||
C.Conn
|
|
||||||
Callback func(error)
|
|
||||||
written bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FirstWriteCallBackConn) Write(b []byte) (n int, err error) {
|
|
||||||
defer func() {
|
|
||||||
if !c.written {
|
|
||||||
c.written = true
|
|
||||||
c.Callback(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return c.Conn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *FirstWriteCallBackConn) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
@ -83,7 +83,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||||||
trojan["port"] = urlTrojan.Port()
|
trojan["port"] = urlTrojan.Port()
|
||||||
trojan["password"] = urlTrojan.User.Username()
|
trojan["password"] = urlTrojan.User.Username()
|
||||||
trojan["udp"] = true
|
trojan["udp"] = true
|
||||||
trojan["skip-cert-verify"], _ = strconv.ParseBool(query.Get("allowInsecure"))
|
trojan["skip-cert-verify"] = false
|
||||||
|
|
||||||
sni := query.Get("sni")
|
sni := query.Get("sni")
|
||||||
if sni != "" {
|
if sni != "" {
|
||||||
|
@ -2,14 +2,12 @@ package convert
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"github.com/metacubex/sing-shadowsocks/shadowimpl"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
"github.com/gofrs/uuid"
|
||||||
|
|
||||||
"github.com/metacubex/sing-shadowsocks/shadowimpl"
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var hostsSuffix = []string{
|
var hostsSuffix = []string{
|
||||||
@ -294,7 +292,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func RandHost() string {
|
func RandHost() string {
|
||||||
id, _ := utils.UnsafeUUIDGenerator.NewV4()
|
id, _ := uuid.NewV4()
|
||||||
base := strings.ToLower(base64.RawURLEncoding.EncodeToString(id.Bytes()))
|
base := strings.ToLower(base64.RawURLEncoding.EncodeToString(id.Bytes()))
|
||||||
base = strings.ReplaceAll(base, "-", "")
|
base = strings.ReplaceAll(base, "-", "")
|
||||||
base = strings.ReplaceAll(base, "_", "")
|
base = strings.ReplaceAll(base, "_", "")
|
||||||
@ -303,11 +301,11 @@ func RandHost() string {
|
|||||||
prefix += string(buf[6:8]) + "-"
|
prefix += string(buf[6:8]) + "-"
|
||||||
prefix += string(buf[len(buf)-8:])
|
prefix += string(buf[len(buf)-8:])
|
||||||
|
|
||||||
return prefix + hostsSuffix[fastrand.Intn(hostsLen)]
|
return prefix + hostsSuffix[rand.Intn(hostsLen)]
|
||||||
}
|
}
|
||||||
|
|
||||||
func RandUserAgent() string {
|
func RandUserAgent() string {
|
||||||
return userAgents[fastrand.Intn(uaLen)]
|
return userAgents[rand.Intn(uaLen)]
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetUserAgent(header http.Header) {
|
func SetUserAgent(header http.Header) {
|
||||||
@ -319,6 +317,6 @@ func SetUserAgent(header http.Header) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func VerifyMethod(cipher, password string) (err error) {
|
func VerifyMethod(cipher, password string) (err error) {
|
||||||
_, err = shadowimpl.FetchMethod(cipher, password, time.Now)
|
_, err = shadowimpl.FetchMethod(cipher, password)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,13 @@ var _ ExtendedConn = (*BufferedConn)(nil)
|
|||||||
type BufferedConn struct {
|
type BufferedConn struct {
|
||||||
r *bufio.Reader
|
r *bufio.Reader
|
||||||
ExtendedConn
|
ExtendedConn
|
||||||
peeked bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBufferedConn(c net.Conn) *BufferedConn {
|
func NewBufferedConn(c net.Conn) *BufferedConn {
|
||||||
if bc, ok := c.(*BufferedConn); ok {
|
if bc, ok := c.(*BufferedConn); ok {
|
||||||
return bc
|
return bc
|
||||||
}
|
}
|
||||||
return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c), false}
|
return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reader returns the internal bufio.Reader.
|
// Reader returns the internal bufio.Reader.
|
||||||
@ -27,24 +26,11 @@ func (c *BufferedConn) Reader() *bufio.Reader {
|
|||||||
return c.r
|
return c.r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BufferedConn) ResetPeeked() {
|
|
||||||
c.peeked = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *BufferedConn) Peeked() bool {
|
|
||||||
return c.peeked
|
|
||||||
}
|
|
||||||
|
|
||||||
// Peek returns the next n bytes without advancing the reader.
|
// Peek returns the next n bytes without advancing the reader.
|
||||||
func (c *BufferedConn) Peek(n int) ([]byte, error) {
|
func (c *BufferedConn) Peek(n int) ([]byte, error) {
|
||||||
c.peeked = true
|
|
||||||
return c.r.Peek(n)
|
return c.r.Peek(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BufferedConn) Discard(n int) (discarded int, err error) {
|
|
||||||
return c.r.Discard(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *BufferedConn) Read(p []byte) (int, error) {
|
func (c *BufferedConn) Read(p []byte) (int, error) {
|
||||||
return c.r.Read(p)
|
return c.r.Read(p)
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
"github.com/sagernet/sing/common/network"
|
"github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
@ -17,13 +16,6 @@ type ExtendedConn = network.ExtendedConn
|
|||||||
type ExtendedWriter = network.ExtendedWriter
|
type ExtendedWriter = network.ExtendedWriter
|
||||||
type ExtendedReader = network.ExtendedReader
|
type ExtendedReader = network.ExtendedReader
|
||||||
|
|
||||||
func NeedHandshake(conn any) bool {
|
|
||||||
if earlyConn, isEarlyConn := common.Cast[network.EarlyConn](conn); isEarlyConn && earlyConn.NeedHandshake() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
||||||
_ = bufio.CopyConn(context.TODO(), leftConn, rightConn)
|
_ = bufio.CopyConn(context.TODO(), leftConn, rightConn)
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package pool
|
package pool
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAllocGet(t *testing.T) {
|
func TestAllocGet(t *testing.T) {
|
||||||
@ -43,6 +43,6 @@ func TestAllocPutThenGet(t *testing.T) {
|
|||||||
|
|
||||||
func BenchmarkMSB(b *testing.B) {
|
func BenchmarkMSB(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
msb(fastrand.Int())
|
msb(rand.Int())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
func MustOK[T any](result T, ok bool) T {
|
|
||||||
if ok {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
panic("operation failed")
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Filter[T comparable](tSlice []T, filter func(t T) bool) []T {
|
|
||||||
result := make([]T, 0)
|
|
||||||
for _, t := range tSlice {
|
|
||||||
if filter(t) {
|
|
||||||
result = append(result, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToStringSlice(value any) ([]string, error) {
|
|
||||||
strArr := make([]string, 0)
|
|
||||||
switch reflect.TypeOf(value).Kind() {
|
|
||||||
case reflect.Slice, reflect.Array:
|
|
||||||
origin := reflect.ValueOf(value)
|
|
||||||
for i := 0; i < origin.Len(); i++ {
|
|
||||||
item := fmt.Sprintf("%v", origin.Index(i))
|
|
||||||
strArr = append(strArr, item)
|
|
||||||
}
|
|
||||||
case reflect.String:
|
|
||||||
strArr = append(strArr, fmt.Sprintf("%v", value))
|
|
||||||
default:
|
|
||||||
return nil, errors.New("value format error, must be string or array")
|
|
||||||
}
|
|
||||||
return strArr, nil
|
|
||||||
}
|
|
@ -2,22 +2,15 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type fastRandReader struct{}
|
var uuidNamespace, _ = uuid.FromString("00000000-0000-0000-0000-000000000000")
|
||||||
|
|
||||||
func (r fastRandReader) Read(p []byte) (int, error) {
|
|
||||||
return fastrand.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
var UnsafeUUIDGenerator = uuid.NewGenWithOptions(uuid.WithRandomReader(fastRandReader{}))
|
|
||||||
|
|
||||||
// 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 UnsafeUUIDGenerator.NewV5(uuid.Nil, str), nil
|
return uuid.NewV5(uuidNamespace, str), nil
|
||||||
}
|
}
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
@ -37,25 +37,14 @@ func bindControl(ifaceIdx int) controlFn {
|
|||||||
var innerErr error
|
var innerErr error
|
||||||
err = c.Control(func(fd uintptr) {
|
err = c.Control(func(fd uintptr) {
|
||||||
handle := syscall.Handle(fd)
|
handle := syscall.Handle(fd)
|
||||||
bind6err := bind6(handle, ifaceIdx)
|
|
||||||
bind4err := bind4(handle, ifaceIdx)
|
|
||||||
switch network {
|
switch network {
|
||||||
case "ip6", "tcp6":
|
case "tcp6", "udp6":
|
||||||
innerErr = bind6err
|
innerErr = bind6(handle, ifaceIdx)
|
||||||
case "ip4", "tcp4", "udp4":
|
_ = bind4(handle, ifaceIdx)
|
||||||
innerErr = bind4err
|
default:
|
||||||
case "udp6":
|
innerErr = bind4(handle, ifaceIdx)
|
||||||
// golang will set network to udp6 when listenUDP on wildcard ip (eg: ":0", "")
|
// try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6
|
||||||
if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil {
|
_ = bind6(handle, ifaceIdx)
|
||||||
// try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6
|
|
||||||
if bind4err != nil {
|
|
||||||
innerErr = bind6err
|
|
||||||
} else {
|
|
||||||
innerErr = bind4err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
innerErr = bind6err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2,25 +2,26 @@ package dialer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
|
||||||
|
"go.uber.org/atomic"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dialMux sync.Mutex
|
dialMux sync.Mutex
|
||||||
actualSingleStackDialContext = serialSingleStackDialContext
|
actualSingleDialContext = singleDialContext
|
||||||
actualDualStackDialContext = serialDualStackDialContext
|
actualDualStackDialContext = dualStackDialContext
|
||||||
tcpConcurrent = false
|
tcpConcurrent = false
|
||||||
fallbackTimeout = 300 * time.Millisecond
|
DisableIPv6 = false
|
||||||
|
ErrorInvalidedNetworkStack = errors.New("invalided network stack")
|
||||||
|
ErrorDisableIPv6 = errors.New("IPv6 is disabled, dialer cancel")
|
||||||
)
|
)
|
||||||
|
|
||||||
func applyOptions(options ...Option) *option {
|
func applyOptions(options ...Option) *option {
|
||||||
@ -53,23 +54,29 @@ func DialContext(ctx context.Context, network, address string, options ...Option
|
|||||||
network = fmt.Sprintf("%s%d", network, opt.network)
|
network = fmt.Sprintf("%s%d", network, opt.network)
|
||||||
}
|
}
|
||||||
|
|
||||||
ips, port, err := parseAddr(ctx, network, address, opt.resolver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp4", "tcp6", "udp4", "udp6":
|
case "tcp4", "tcp6", "udp4", "udp6":
|
||||||
return actualSingleStackDialContext(ctx, network, ips, port, opt)
|
return actualSingleDialContext(ctx, network, address, opt)
|
||||||
case "tcp", "udp":
|
case "tcp", "udp":
|
||||||
return actualDualStackDialContext(ctx, network, ips, port, opt)
|
return actualDualStackDialContext(ctx, network, address, opt)
|
||||||
default:
|
default:
|
||||||
return nil, ErrorInvalidedNetworkStack
|
return nil, ErrorInvalidedNetworkStack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
|
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
|
||||||
cfg := applyOptions(options...)
|
cfg := &option{
|
||||||
|
interfaceName: DefaultInterface.Load(),
|
||||||
|
routingMark: int(DefaultRoutingMark.Load()),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range DefaultOptions {
|
||||||
|
o(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range options {
|
||||||
|
o(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
lc := &net.ListenConfig{}
|
lc := &net.ListenConfig{}
|
||||||
if cfg.interfaceName != "" {
|
if cfg.interfaceName != "" {
|
||||||
@ -89,40 +96,26 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
|
|||||||
return lc.ListenPacket(ctx, network, address)
|
return lc.ListenPacket(ctx, network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetTcpConcurrent(concurrent bool) {
|
func SetDial(concurrent bool) {
|
||||||
dialMux.Lock()
|
dialMux.Lock()
|
||||||
defer dialMux.Unlock()
|
|
||||||
tcpConcurrent = concurrent
|
tcpConcurrent = concurrent
|
||||||
if concurrent {
|
if concurrent {
|
||||||
actualSingleStackDialContext = concurrentSingleStackDialContext
|
actualSingleDialContext = concurrentSingleDialContext
|
||||||
actualDualStackDialContext = concurrentDualStackDialContext
|
actualDualStackDialContext = concurrentDualStackDialContext
|
||||||
} else {
|
} else {
|
||||||
actualSingleStackDialContext = serialSingleStackDialContext
|
actualSingleDialContext = singleDialContext
|
||||||
actualDualStackDialContext = serialDualStackDialContext
|
actualDualStackDialContext = dualStackDialContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dialMux.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTcpConcurrent() bool {
|
func GetDial() bool {
|
||||||
dialMux.Lock()
|
|
||||||
defer dialMux.Unlock()
|
|
||||||
return tcpConcurrent
|
return tcpConcurrent
|
||||||
}
|
}
|
||||||
|
|
||||||
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
|
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||||
address := net.JoinHostPort(destination.String(), port)
|
dialer := &net.Dialer{}
|
||||||
|
|
||||||
netDialer := opt.netDialer
|
|
||||||
switch netDialer.(type) {
|
|
||||||
case nil:
|
|
||||||
netDialer = &net.Dialer{}
|
|
||||||
case *net.Dialer:
|
|
||||||
_netDialer := *netDialer.(*net.Dialer)
|
|
||||||
netDialer = &_netDialer // make a copy
|
|
||||||
default:
|
|
||||||
return netDialer.DialContext(ctx, network, address)
|
|
||||||
}
|
|
||||||
|
|
||||||
dialer := netDialer.(*net.Dialer)
|
|
||||||
if opt.interfaceName != "" {
|
if opt.interfaceName != "" {
|
||||||
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
|
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -131,213 +124,328 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
|||||||
if opt.routingMark != 0 {
|
if opt.routingMark != 0 {
|
||||||
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
||||||
}
|
}
|
||||||
if opt.tfo {
|
|
||||||
return dialTFO(ctx, *dialer, network, address)
|
if DisableIPv6 && destination.Is6() {
|
||||||
|
return nil, ErrorDisableIPv6
|
||||||
}
|
}
|
||||||
return dialer.DialContext(ctx, network, address)
|
|
||||||
|
return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
|
||||||
}
|
}
|
||||||
|
|
||||||
func serialSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
func dualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
|
||||||
return serialDialContext(ctx, network, ips, port, opt)
|
host, port, err := net.SplitHostPort(address)
|
||||||
}
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
func serialDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
|
||||||
return dualStackDialContext(ctx, serialDialContext, network, ips, port, opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func concurrentSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
|
||||||
return parallelDialContext(ctx, network, ips, port, opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func concurrentDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
|
||||||
if opt.prefer != 4 && opt.prefer != 6 {
|
|
||||||
return parallelDialContext(ctx, network, ips, port, opt)
|
|
||||||
}
|
}
|
||||||
return dualStackDialContext(ctx, parallelDialContext, network, ips, port, opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
|
||||||
ipv4s, ipv6s := sortationAddr(ips)
|
|
||||||
preferIPVersion := opt.prefer
|
|
||||||
|
|
||||||
fallbackTicker := time.NewTicker(fallbackTimeout)
|
|
||||||
defer fallbackTicker.Stop()
|
|
||||||
results := make(chan dialResult)
|
|
||||||
returned := make(chan struct{})
|
returned := make(chan struct{})
|
||||||
defer close(returned)
|
defer close(returned)
|
||||||
racer := func(ips []netip.Addr, isPrimary bool) {
|
|
||||||
result := dialResult{isPrimary: isPrimary}
|
type dialResult struct {
|
||||||
|
net.Conn
|
||||||
|
error
|
||||||
|
resolved bool
|
||||||
|
ipv6 bool
|
||||||
|
done bool
|
||||||
|
}
|
||||||
|
results := make(chan dialResult)
|
||||||
|
var primary, fallback dialResult
|
||||||
|
|
||||||
|
startRacer := func(ctx context.Context, network, host string, r resolver.Resolver, ipv6 bool) {
|
||||||
|
result := dialResult{ipv6: ipv6, done: true}
|
||||||
defer func() {
|
defer func() {
|
||||||
select {
|
select {
|
||||||
case results <- result:
|
case results <- result:
|
||||||
case <-returned:
|
case <-returned:
|
||||||
if result.Conn != nil && result.error == nil {
|
if result.Conn != nil {
|
||||||
_ = result.Conn.Close()
|
_ = result.Conn.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
result.Conn, result.error = dialFn(ctx, network, ips, port, opt)
|
|
||||||
}
|
var ip netip.Addr
|
||||||
go racer(ipv4s, preferIPVersion != 6)
|
if ipv6 {
|
||||||
go racer(ipv6s, preferIPVersion != 4)
|
if r == nil {
|
||||||
var fallback dialResult
|
ip, result.error = resolver.ResolveIPv6ProxyServerHost(ctx, host)
|
||||||
var errs []error
|
|
||||||
for i := 0; i < 2; {
|
|
||||||
select {
|
|
||||||
case <-fallbackTicker.C:
|
|
||||||
if fallback.error == nil && fallback.Conn != nil {
|
|
||||||
return fallback.Conn, nil
|
|
||||||
}
|
|
||||||
case res := <-results:
|
|
||||||
i++
|
|
||||||
if res.error == nil {
|
|
||||||
if res.isPrimary {
|
|
||||||
return res.Conn, nil
|
|
||||||
}
|
|
||||||
fallback = res
|
|
||||||
} else {
|
} else {
|
||||||
if res.isPrimary {
|
ip, result.error = resolver.ResolveIPv6WithResolver(ctx, host, r)
|
||||||
errs = append([]error{fmt.Errorf("connect failed: %w", res.error)}, errs...)
|
}
|
||||||
} else {
|
} else {
|
||||||
errs = append(errs, fmt.Errorf("connect failed: %w", res.error))
|
if r == nil {
|
||||||
}
|
ip, result.error = resolver.ResolveIPv4ProxyServerHost(ctx, host)
|
||||||
|
} else {
|
||||||
|
ip, result.error = resolver.ResolveIPv4WithResolver(ctx, host, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if result.error != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result.resolved = true
|
||||||
|
|
||||||
|
result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
|
||||||
}
|
}
|
||||||
if fallback.error == nil && fallback.Conn != nil {
|
|
||||||
return fallback.Conn, nil
|
go startRacer(ctx, network+"4", host, opt.resolver, false)
|
||||||
|
go startRacer(ctx, network+"6", host, opt.resolver, true)
|
||||||
|
|
||||||
|
count := 2
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
select {
|
||||||
|
case res := <-results:
|
||||||
|
if res.error == nil {
|
||||||
|
return res.Conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !res.ipv6 {
|
||||||
|
primary = res
|
||||||
|
} else {
|
||||||
|
fallback = res
|
||||||
|
}
|
||||||
|
|
||||||
|
if primary.done && fallback.done {
|
||||||
|
if primary.resolved {
|
||||||
|
return nil, primary.error
|
||||||
|
} else if fallback.resolved {
|
||||||
|
return nil, fallback.error
|
||||||
|
} else {
|
||||||
|
return nil, primary.error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
err = ctx.Err()
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil, errorsJoin(errs...)
|
|
||||||
|
if err == nil {
|
||||||
|
err = fmt.Errorf("dual stack dial failed")
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("dual stack dial failed:%w", err)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
func concurrentDualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
|
||||||
if len(ips) == 0 {
|
host, port, err := net.SplitHostPort(address)
|
||||||
return nil, ErrorNoIpAddress
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
results := make(chan dialResult)
|
|
||||||
|
var ips []netip.Addr
|
||||||
|
if opt.resolver != nil {
|
||||||
|
ips, err = resolver.LookupIPWithResolver(ctx, host, opt.resolver)
|
||||||
|
} else {
|
||||||
|
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return concurrentDialContext(ctx, network, ips, port, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||||
returned := make(chan struct{})
|
returned := make(chan struct{})
|
||||||
defer close(returned)
|
defer close(returned)
|
||||||
racer := func(ctx context.Context, ip netip.Addr) {
|
|
||||||
result := dialResult{isPrimary: true, ip: ip}
|
type dialResult struct {
|
||||||
|
ip netip.Addr
|
||||||
|
net.Conn
|
||||||
|
error
|
||||||
|
isPrimary bool
|
||||||
|
done bool
|
||||||
|
}
|
||||||
|
|
||||||
|
preferCount := atomic.NewInt32(0)
|
||||||
|
results := make(chan dialResult)
|
||||||
|
tcpRacer := func(ctx context.Context, ip netip.Addr) {
|
||||||
|
result := dialResult{ip: ip, done: true}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
select {
|
select {
|
||||||
case results <- result:
|
case results <- result:
|
||||||
case <-returned:
|
case <-returned:
|
||||||
if result.Conn != nil && result.error == nil {
|
if result.Conn != nil {
|
||||||
_ = result.Conn.Close()
|
_ = result.Conn.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
if strings.Contains(network, "tcp") {
|
||||||
|
network = "tcp"
|
||||||
|
} else {
|
||||||
|
network = "udp"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip.Is6() {
|
||||||
|
network += "6"
|
||||||
|
if opt.prefer != 4 {
|
||||||
|
result.isPrimary = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip.Is4() {
|
||||||
|
network += "4"
|
||||||
|
if opt.prefer != 6 {
|
||||||
|
result.isPrimary = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.isPrimary {
|
||||||
|
preferCount.Add(1)
|
||||||
|
}
|
||||||
|
|
||||||
result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
|
result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
go racer(ctx, ip)
|
go tcpRacer(ctx, ip)
|
||||||
}
|
|
||||||
var errs []error
|
|
||||||
for i := 0; i < len(ips); i++ {
|
|
||||||
res := <-results
|
|
||||||
if res.error == nil {
|
|
||||||
return res.Conn, nil
|
|
||||||
}
|
|
||||||
errs = append(errs, res.error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(errs) > 0 {
|
connCount := len(ips)
|
||||||
return nil, errorsJoin(errs...)
|
var fallback dialResult
|
||||||
}
|
var primaryError error
|
||||||
return nil, os.ErrDeadlineExceeded
|
var finalError error
|
||||||
}
|
for i := 0; i < connCount; i++ {
|
||||||
|
select {
|
||||||
func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
case res := <-results:
|
||||||
if len(ips) == 0 {
|
if res.error == nil {
|
||||||
return nil, ErrorNoIpAddress
|
if res.isPrimary {
|
||||||
}
|
return res.Conn, nil
|
||||||
var errs []error
|
} else {
|
||||||
for _, ip := range ips {
|
if !fallback.done || fallback.error != nil {
|
||||||
if conn, err := dialContext(ctx, network, ip, port, opt); err == nil {
|
fallback = res
|
||||||
return conn, nil
|
}
|
||||||
} else {
|
}
|
||||||
errs = append(errs, err)
|
} else {
|
||||||
|
if res.isPrimary {
|
||||||
|
primaryError = res.error
|
||||||
|
preferCount.Add(-1)
|
||||||
|
if preferCount.Load() == 0 && fallback.done && fallback.error == nil {
|
||||||
|
return fallback.Conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
if fallback.done && fallback.error == nil {
|
||||||
|
return fallback.Conn, nil
|
||||||
|
}
|
||||||
|
finalError = ctx.Err()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, errorsJoin(errs...)
|
|
||||||
|
if fallback.done && fallback.error == nil {
|
||||||
|
return fallback.Conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if primaryError != nil {
|
||||||
|
return nil, primaryError
|
||||||
|
}
|
||||||
|
|
||||||
|
if fallback.error != nil {
|
||||||
|
return nil, fallback.error
|
||||||
|
}
|
||||||
|
|
||||||
|
if finalError == nil {
|
||||||
|
finalError = fmt.Errorf("all ips %v tcp shake hands failed", ips)
|
||||||
|
} else {
|
||||||
|
finalError = fmt.Errorf("concurrent dial failed:%w", finalError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, finalError
|
||||||
}
|
}
|
||||||
|
|
||||||
type dialResult struct {
|
func singleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
|
||||||
ip netip.Addr
|
|
||||||
net.Conn
|
|
||||||
error
|
|
||||||
isPrimary bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseAddr(ctx context.Context, network, address string, preferResolver resolver.Resolver) ([]netip.Addr, string, error) {
|
|
||||||
host, port, err := net.SplitHostPort(address)
|
host, port, err := net.SplitHostPort(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "-1", err
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ip netip.Addr
|
||||||
|
switch network {
|
||||||
|
case "tcp4", "udp4":
|
||||||
|
if opt.resolver == nil {
|
||||||
|
ip, err = resolver.ResolveIPv4ProxyServerHost(ctx, host)
|
||||||
|
} else {
|
||||||
|
ip, err = resolver.ResolveIPv4WithResolver(ctx, host, opt.resolver)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if opt.resolver == nil {
|
||||||
|
ip, err = resolver.ResolveIPv6ProxyServerHost(ctx, host)
|
||||||
|
} else {
|
||||||
|
ip, err = resolver.ResolveIPv6WithResolver(ctx, host, opt.resolver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialContext(ctx, network, ip, port, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func concurrentSingleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
|
||||||
|
switch network {
|
||||||
|
case "tcp4", "udp4":
|
||||||
|
return concurrentIPv4DialContext(ctx, network, address, opt)
|
||||||
|
default:
|
||||||
|
return concurrentIPv6DialContext(ctx, network, address, opt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func concurrentIPv4DialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
|
||||||
|
host, port, err := net.SplitHostPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ips []netip.Addr
|
var ips []netip.Addr
|
||||||
switch network {
|
if opt.resolver == nil {
|
||||||
case "tcp4", "udp4":
|
ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
|
||||||
if preferResolver == nil {
|
} else {
|
||||||
ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
|
ips, err = resolver.LookupIPv4WithResolver(ctx, host, opt.resolver)
|
||||||
} else {
|
|
||||||
ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver)
|
|
||||||
}
|
|
||||||
case "tcp6", "udp6":
|
|
||||||
if preferResolver == nil {
|
|
||||||
ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
|
|
||||||
} else {
|
|
||||||
ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if preferResolver == nil {
|
|
||||||
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
|
|
||||||
} else {
|
|
||||||
ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "-1", fmt.Errorf("dns resolve failed: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
for i, ip := range ips {
|
|
||||||
if ip.Is4In6() {
|
return concurrentDialContext(ctx, network, ips, port, opt)
|
||||||
ips[i] = ip.Unmap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ips, port, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
|
func concurrentIPv6DialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
|
||||||
for _, v := range ips {
|
host, port, err := net.SplitHostPort(address)
|
||||||
if v.Is4() { // 4in6 parse was in parseAddr
|
if err != nil {
|
||||||
ipv4s = append(ipv4s, v)
|
return nil, err
|
||||||
} else {
|
|
||||||
ipv6s = append(ipv6s, v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
var ips []netip.Addr
|
||||||
|
if opt.resolver == nil {
|
||||||
|
ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
|
||||||
|
} else {
|
||||||
|
ips, err = resolver.LookupIPv6WithResolver(ctx, host, opt.resolver)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return concurrentDialContext(ctx, network, ips, port, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, WithOption(d.Opt))
|
||||||
if rAddrPort.Addr().Unmap().IsLoopback() {
|
|
||||||
// avoid "The requested address is not valid in its context."
|
|
||||||
opt = WithInterface("")
|
|
||||||
}
|
|
||||||
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, opt)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDialer(options ...Option) Dialer {
|
func NewDialer(options ...Option) Dialer {
|
||||||
opt := applyOptions(options...)
|
opt := applyOptions(options...)
|
||||||
return Dialer{opt: *opt}
|
return Dialer{Opt: *opt}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrorNoIpAddress = errors.New("no ip address")
|
|
||||||
ErrorInvalidedNetworkStack = errors.New("invalided network stack")
|
|
||||||
)
|
|
||||||
|
|
||||||
func errorsJoin(errs ...error) error {
|
|
||||||
// compatibility with golang<1.20
|
|
||||||
// maybe use errors.Join(errs...) is better after we drop the old version's support
|
|
||||||
return E.Errors(errs...)
|
|
||||||
}
|
|
@ -1,9 +1,6 @@
|
|||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
@ -15,19 +12,13 @@ var (
|
|||||||
DefaultRoutingMark = atomic.NewInt32(0)
|
DefaultRoutingMark = atomic.NewInt32(0)
|
||||||
)
|
)
|
||||||
|
|
||||||
type NetDialer interface {
|
|
||||||
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type option struct {
|
type option struct {
|
||||||
interfaceName string
|
interfaceName string
|
||||||
addrReuse bool
|
addrReuse bool
|
||||||
routingMark int
|
routingMark int
|
||||||
network int
|
network int
|
||||||
prefer int
|
prefer int
|
||||||
tfo bool
|
|
||||||
resolver resolver.Resolver
|
resolver resolver.Resolver
|
||||||
netDialer NetDialer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Option func(opt *option)
|
type Option func(opt *option)
|
||||||
@ -78,18 +69,6 @@ func WithOnlySingleStack(isIPv4 bool) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithTFO(tfo bool) Option {
|
|
||||||
return func(opt *option) {
|
|
||||||
opt.tfo = tfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithNetDialer(netDialer NetDialer) Option {
|
|
||||||
return func(opt *option) {
|
|
||||||
opt.netDialer = netDialer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithOption(o option) Option {
|
func WithOption(o option) Option {
|
||||||
return func(opt *option) {
|
return func(opt *option) {
|
||||||
*opt = o
|
*opt = o
|
||||||
|
@ -1,123 +0,0 @@
|
|||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/sagernet/tfo-go"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tfoConn struct {
|
|
||||||
net.Conn
|
|
||||||
closed bool
|
|
||||||
dialed chan bool
|
|
||||||
cancel context.CancelFunc
|
|
||||||
ctx context.Context
|
|
||||||
dialFn func(ctx context.Context, earlyData []byte) (net.Conn, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) Dial(earlyData []byte) (err error) {
|
|
||||||
c.Conn, err = c.dialFn(c.ctx, earlyData)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.dialed <- true
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) Read(b []byte) (n int, err error) {
|
|
||||||
if c.closed {
|
|
||||||
return 0, io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
if c.Conn == nil {
|
|
||||||
select {
|
|
||||||
case <-c.ctx.Done():
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
case <-c.dialed:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.Conn.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) Write(b []byte) (n int, err error) {
|
|
||||||
if c.closed {
|
|
||||||
return 0, io.ErrClosedPipe
|
|
||||||
}
|
|
||||||
if c.Conn == nil {
|
|
||||||
if err := c.Dial(b); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Conn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) Close() error {
|
|
||||||
c.closed = true
|
|
||||||
c.cancel()
|
|
||||||
if c.Conn == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.Conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) LocalAddr() net.Addr {
|
|
||||||
if c.Conn == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.Conn.LocalAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) RemoteAddr() net.Addr {
|
|
||||||
if c.Conn == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.Conn.RemoteAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) SetDeadline(t time.Time) error {
|
|
||||||
if err := c.SetReadDeadline(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return c.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) SetReadDeadline(t time.Time) error {
|
|
||||||
if c.Conn == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.Conn.SetReadDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) SetWriteDeadline(t time.Time) error {
|
|
||||||
if c.Conn == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.Conn.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) Upstream() any {
|
|
||||||
if c.Conn == nil { // ensure return a nil interface not an interface with nil value
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *tfoConn) NeedHandshake() bool {
|
|
||||||
return c.Conn == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
|
|
||||||
return &tfoConn{
|
|
||||||
dialed: make(chan bool, 1),
|
|
||||||
cancel: cancel,
|
|
||||||
ctx: ctx,
|
|
||||||
dialFn: func(ctx context.Context, earlyData []byte) (net.Conn, error) {
|
|
||||||
return dialer.DialContext(ctx, network, address, earlyData)
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
@ -1,112 +0,0 @@
|
|||||||
package resolver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"math/rand"
|
|
||||||
"net/netip"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Hosts struct {
|
|
||||||
*trie.DomainTrie[HostValue]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHosts(hosts *trie.DomainTrie[HostValue]) Hosts {
|
|
||||||
return Hosts{
|
|
||||||
hosts,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) {
|
|
||||||
value := h.DomainTrie.Search(domain)
|
|
||||||
if value == nil {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
hostValue := value.Data()
|
|
||||||
for {
|
|
||||||
if isDomain && hostValue.IsDomain {
|
|
||||||
return &hostValue, true
|
|
||||||
} else {
|
|
||||||
if node := h.DomainTrie.Search(hostValue.Domain); node != nil {
|
|
||||||
hostValue = node.Data()
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isDomain == hostValue.IsDomain {
|
|
||||||
return &hostValue, true
|
|
||||||
}
|
|
||||||
return &hostValue, false
|
|
||||||
}
|
|
||||||
|
|
||||||
type HostValue struct {
|
|
||||||
IsDomain bool
|
|
||||||
IPs []netip.Addr
|
|
||||||
Domain string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHostValue(value any) (HostValue, error) {
|
|
||||||
isDomain := true
|
|
||||||
ips := make([]netip.Addr, 0)
|
|
||||||
domain := ""
|
|
||||||
if valueArr, err := utils.ToStringSlice(value); err != nil {
|
|
||||||
return HostValue{}, err
|
|
||||||
} else {
|
|
||||||
if len(valueArr) > 1 {
|
|
||||||
isDomain = false
|
|
||||||
for _, str := range valueArr {
|
|
||||||
if ip, err := netip.ParseAddr(str); err == nil {
|
|
||||||
ips = append(ips, ip)
|
|
||||||
} else {
|
|
||||||
return HostValue{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if len(valueArr) == 1 {
|
|
||||||
host := valueArr[0]
|
|
||||||
if ip, err := netip.ParseAddr(host); err == nil {
|
|
||||||
ips = append(ips, ip)
|
|
||||||
isDomain = false
|
|
||||||
} else {
|
|
||||||
domain = host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isDomain {
|
|
||||||
return NewHostValueByDomain(domain)
|
|
||||||
} else {
|
|
||||||
return NewHostValueByIPs(ips)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHostValueByIPs(ips []netip.Addr) (HostValue, error) {
|
|
||||||
if len(ips) == 0 {
|
|
||||||
return HostValue{}, errors.New("ip list is empty")
|
|
||||||
}
|
|
||||||
return HostValue{
|
|
||||||
IsDomain: false,
|
|
||||||
IPs: ips,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHostValueByDomain(domain string) (HostValue, error) {
|
|
||||||
domain = strings.Trim(domain, ".")
|
|
||||||
item := strings.Split(domain, ".")
|
|
||||||
if len(item) < 2 {
|
|
||||||
return HostValue{}, errors.New("invaild domain")
|
|
||||||
}
|
|
||||||
return HostValue{
|
|
||||||
IsDomain: true,
|
|
||||||
Domain: domain,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hv HostValue) RandIP() (netip.Addr, error) {
|
|
||||||
if hv.IsDomain {
|
|
||||||
return netip.Addr{}, errors.New("value type is error")
|
|
||||||
}
|
|
||||||
return hv.IPs[rand.Intn(len(hv.IPs)-1)], nil
|
|
||||||
}
|
|
@ -4,16 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -28,7 +27,7 @@ var (
|
|||||||
DisableIPv6 = true
|
DisableIPv6 = true
|
||||||
|
|
||||||
// DefaultHosts aim to resolve hosts
|
// DefaultHosts aim to resolve hosts
|
||||||
DefaultHosts = NewHosts(trie.New[HostValue]())
|
DefaultHosts = trie.New[netip.Addr]()
|
||||||
|
|
||||||
// DefaultDNSTimeout defined the default dns request timeout
|
// DefaultDNSTimeout defined the default dns request timeout
|
||||||
DefaultDNSTimeout = time.Second * 5
|
DefaultDNSTimeout = time.Second * 5
|
||||||
@ -52,11 +51,9 @@ type Resolver interface {
|
|||||||
|
|
||||||
// LookupIPv4WithResolver same as LookupIPv4, but with a resolver
|
// LookupIPv4WithResolver same as LookupIPv4, but with a resolver
|
||||||
func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) {
|
func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) {
|
||||||
if node, ok := DefaultHosts.Search(host, false); ok {
|
if node := DefaultHosts.Search(host); node != nil {
|
||||||
if addrs := utils.Filter(node.IPs, func(ip netip.Addr) bool {
|
if ip := node.Data(); ip.Is4() {
|
||||||
return ip.Is4()
|
return []netip.Addr{node.Data()}, nil
|
||||||
}); len(addrs) > 0 {
|
|
||||||
return addrs, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,6 +69,10 @@ func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]net
|
|||||||
return r.LookupIPv4(ctx, host)
|
return r.LookupIPv4(ctx, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if DefaultResolver != nil {
|
||||||
|
return DefaultResolver.LookupIPv4(ctx, host)
|
||||||
|
}
|
||||||
|
|
||||||
ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip4", host)
|
ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip4", host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -95,7 +96,7 @@ func ResolveIPv4WithResolver(ctx context.Context, host string, r Resolver) (neti
|
|||||||
} 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)
|
||||||
}
|
}
|
||||||
return ips[fastrand.Intn(len(ips))], nil
|
return ips[rand.Intn(len(ips))], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIPv4 with a host, return ipv4
|
// ResolveIPv4 with a host, return ipv4
|
||||||
@ -109,11 +110,9 @@ func LookupIPv6WithResolver(ctx context.Context, host string, r Resolver) ([]net
|
|||||||
return nil, ErrIPv6Disabled
|
return nil, ErrIPv6Disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
if node, ok := DefaultHosts.Search(host, false); ok {
|
if node := DefaultHosts.Search(host); node != nil {
|
||||||
if addrs := utils.Filter(node.IPs, func(ip netip.Addr) bool {
|
if ip := node.Data(); ip.Is6() {
|
||||||
return ip.Is6()
|
return []netip.Addr{ip}, nil
|
||||||
}); len(addrs) > 0 {
|
|
||||||
return addrs, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,6 +126,9 @@ func LookupIPv6WithResolver(ctx context.Context, host string, r Resolver) ([]net
|
|||||||
if r != nil {
|
if r != nil {
|
||||||
return r.LookupIPv6(ctx, host)
|
return r.LookupIPv6(ctx, host)
|
||||||
}
|
}
|
||||||
|
if DefaultResolver != nil {
|
||||||
|
return DefaultResolver.LookupIPv6(ctx, host)
|
||||||
|
}
|
||||||
|
|
||||||
ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip6", host)
|
ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip6", host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -151,7 +153,7 @@ func ResolveIPv6WithResolver(ctx context.Context, host string, r Resolver) (neti
|
|||||||
} 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)
|
||||||
}
|
}
|
||||||
return ips[fastrand.Intn(len(ips))], nil
|
return ips[rand.Intn(len(ips))], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResolveIPv6(ctx context.Context, host string) (netip.Addr, error) {
|
func ResolveIPv6(ctx context.Context, host string) (netip.Addr, error) {
|
||||||
@ -160,8 +162,8 @@ func ResolveIPv6(ctx context.Context, host string) (netip.Addr, error) {
|
|||||||
|
|
||||||
// LookupIPWithResolver same as LookupIP, but with a resolver
|
// LookupIPWithResolver same as LookupIP, but with a resolver
|
||||||
func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) {
|
func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) {
|
||||||
if node, ok := DefaultHosts.Search(host, false); ok {
|
if node := DefaultHosts.Search(host); node != nil {
|
||||||
return node.IPs, nil
|
return []netip.Addr{node.Data()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if r != nil {
|
if r != nil {
|
||||||
@ -170,7 +172,7 @@ func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip
|
|||||||
}
|
}
|
||||||
return r.LookupIP(ctx, host)
|
return r.LookupIP(ctx, host)
|
||||||
} else if DisableIPv6 {
|
} else if DisableIPv6 {
|
||||||
return LookupIPv4WithResolver(ctx, host, r)
|
return LookupIPv4(ctx, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip, err := netip.ParseAddr(host); err == nil {
|
if ip, err := netip.ParseAddr(host); err == nil {
|
||||||
@ -200,7 +202,7 @@ 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)
|
||||||
}
|
}
|
||||||
return ips[fastrand.Intn(len(ips))], nil
|
return ips[rand.Intn(len(ips))], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIP with a host, return ip
|
// ResolveIP with a host, return ip
|
||||||
|
@ -36,7 +36,12 @@ type SnifferDispatcher struct {
|
|||||||
parsePureIp bool
|
parsePureIp bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) {
|
func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
|
||||||
|
bufConn, ok := conn.(*N.BufferedConn)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Search(metadata.Host) != nil || (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 {
|
||||||
@ -69,7 +74,7 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
|
|||||||
}
|
}
|
||||||
sd.rwMux.RUnlock()
|
sd.rwMux.RUnlock()
|
||||||
|
|
||||||
if host, err := sd.sniffDomain(conn, metadata); err != nil {
|
if host, err := sd.sniffDomain(bufConn, metadata); err != nil {
|
||||||
sd.cacheSniffFailed(metadata)
|
sd.cacheSniffFailed(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
|
||||||
|
@ -11,30 +11,31 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
CN "github.com/Dreamacro/clash/common/net"
|
||||||
|
|
||||||
xtls "github.com/xtls/go"
|
xtls "github.com/xtls/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
var trustCert, _ = x509.SystemCertPool()
|
var tlsCertificates = make([]tls.Certificate, 0)
|
||||||
|
|
||||||
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")
|
||||||
|
|
||||||
func AddCertificate(certificate string) error {
|
func AddCertificate(privateKey, certificate string) error {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
if certificate == "" {
|
if cert, err := CN.ParseCert(certificate, privateKey); err != nil {
|
||||||
return fmt.Errorf("certificate is empty")
|
return err
|
||||||
}
|
} else {
|
||||||
if ok := trustCert.AppendCertsFromPEM([]byte(certificate)); !ok {
|
tlsCertificates = append(tlsCertificates, cert)
|
||||||
return fmt.Errorf("add certificate failed")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResetCertificate() {
|
func GetCertificates() []tls.Certificate {
|
||||||
mutex.Lock()
|
mutex.RLock()
|
||||||
defer mutex.Unlock()
|
defer mutex.RUnlock()
|
||||||
trustCert, _ = x509.SystemCertPool()
|
return tlsCertificates
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||||
@ -86,10 +87,10 @@ func GetSpecifiedFingerprintTLSConfig(tlsConfig *tls.Config, fingerprint string)
|
|||||||
func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config {
|
func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config {
|
||||||
if tlsConfig == nil {
|
if tlsConfig == nil {
|
||||||
return &tls.Config{
|
return &tls.Config{
|
||||||
RootCAs: trustCert,
|
Certificates: tlsCertificates,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tlsConfig.RootCAs = trustCert
|
tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCertificates...)
|
||||||
return tlsConfig
|
return tlsConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,12 +107,29 @@ func GetSpecifiedFingerprintXTLSConfig(tlsConfig *xtls.Config, fingerprint strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetGlobalXTLSConfig(tlsConfig *xtls.Config) *xtls.Config {
|
func GetGlobalXTLSConfig(tlsConfig *xtls.Config) *xtls.Config {
|
||||||
|
xtlsCerts := make([]xtls.Certificate, len(tlsCertificates))
|
||||||
|
for _, cert := range tlsCertificates {
|
||||||
|
tlsSsaList := make([]xtls.SignatureScheme, len(cert.SupportedSignatureAlgorithms))
|
||||||
|
for _, ssa := range cert.SupportedSignatureAlgorithms {
|
||||||
|
tlsSsa := xtls.SignatureScheme(ssa)
|
||||||
|
tlsSsaList = append(tlsSsaList, tlsSsa)
|
||||||
|
}
|
||||||
|
xtlsCert := xtls.Certificate{
|
||||||
|
Certificate: cert.Certificate,
|
||||||
|
PrivateKey: cert.PrivateKey,
|
||||||
|
OCSPStaple: cert.OCSPStaple,
|
||||||
|
SignedCertificateTimestamps: cert.SignedCertificateTimestamps,
|
||||||
|
Leaf: cert.Leaf,
|
||||||
|
SupportedSignatureAlgorithms: tlsSsaList,
|
||||||
|
}
|
||||||
|
xtlsCerts = append(xtlsCerts, xtlsCert)
|
||||||
|
}
|
||||||
if tlsConfig == nil {
|
if tlsConfig == nil {
|
||||||
return &xtls.Config{
|
return &xtls.Config{
|
||||||
RootCAs: trustCert,
|
Certificates: xtlsCerts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig.RootCAs = trustCert
|
tlsConfig.Certificates = xtlsCerts
|
||||||
return tlsConfig
|
return tlsConfig
|
||||||
}
|
}
|
||||||
|
@ -1,169 +0,0 @@
|
|||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/ed25519"
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/sha512"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
utls "github.com/sagernet/utls"
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
"golang.org/x/crypto/curve25519"
|
|
||||||
"golang.org/x/crypto/hkdf"
|
|
||||||
"golang.org/x/net/http2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const RealityMaxShortIDLen = 8
|
|
||||||
|
|
||||||
type RealityConfig struct {
|
|
||||||
PublicKey [curve25519.ScalarSize]byte
|
|
||||||
ShortID [RealityMaxShortIDLen]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
|
|
||||||
if fingerprint, exists := GetFingerprint(ClientFingerprint); exists {
|
|
||||||
verifier := &realityVerifier{
|
|
||||||
serverName: tlsConfig.ServerName,
|
|
||||||
}
|
|
||||||
uConfig := &utls.Config{
|
|
||||||
ServerName: tlsConfig.ServerName,
|
|
||||||
NextProtos: tlsConfig.NextProtos,
|
|
||||||
InsecureSkipVerify: true,
|
|
||||||
SessionTicketsDisabled: true,
|
|
||||||
VerifyPeerCertificate: verifier.VerifyPeerCertificate,
|
|
||||||
}
|
|
||||||
clientID := utls.ClientHelloID{
|
|
||||||
Client: fingerprint.Client,
|
|
||||||
Version: fingerprint.Version,
|
|
||||||
Seed: fingerprint.Seed,
|
|
||||||
}
|
|
||||||
uConn := utls.UClient(conn, uConfig, clientID)
|
|
||||||
verifier.UConn = uConn
|
|
||||||
err := uConn.BuildHandshakeState()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hello := uConn.HandshakeState.Hello
|
|
||||||
hello.SessionId = make([]byte, 32)
|
|
||||||
copy(hello.Raw[39:], hello.SessionId)
|
|
||||||
|
|
||||||
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[1] = 7
|
|
||||||
hello.SessionId[2] = 5
|
|
||||||
copy(hello.SessionId[8:], realityConfig.ShortID[:])
|
|
||||||
|
|
||||||
//log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16])
|
|
||||||
|
|
||||||
authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(realityConfig.PublicKey[:])
|
|
||||||
if authKey == nil {
|
|
||||||
return nil, errors.New("nil auth_key")
|
|
||||||
}
|
|
||||||
verifier.authKey = authKey
|
|
||||||
_, err = hkdf.New(sha256.New, authKey, hello.Random[:20], []byte("REALITY")).Read(authKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
aesBlock, _ := aes.NewCipher(authKey)
|
|
||||||
aesGcmCipher, _ := cipher.NewGCM(aesBlock)
|
|
||||||
aesGcmCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
|
|
||||||
copy(hello.Raw[39:], hello.SessionId)
|
|
||||||
//log.Debugln("REALITY hello.sessionId: %v", hello.SessionId)
|
|
||||||
//log.Debugln("REALITY uConn.AuthKey: %v", authKey)
|
|
||||||
|
|
||||||
err = uConn.HandshakeContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugln("REALITY Authentication: %v", verifier.verified)
|
|
||||||
|
|
||||||
if !verifier.verified {
|
|
||||||
go realityClientFallback(uConn, uConfig.ServerName, clientID)
|
|
||||||
return nil, errors.New("REALITY authentication failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
return uConn, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("unknown uTLS fingerprint")
|
|
||||||
}
|
|
||||||
|
|
||||||
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
|
||||||
defer uConn.Close()
|
|
||||||
client := http.Client{
|
|
||||||
Transport: &http2.Transport{
|
|
||||||
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
|
|
||||||
return uConn, nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
|
||||||
request.Header.Set("User-Agent", fingerprint.Client)
|
|
||||||
request.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", fastrand.Intn(32)+30)})
|
|
||||||
response, err := client.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
//_, _ = io.Copy(io.Discard, response.Body)
|
|
||||||
time.Sleep(time.Duration(5 + fastrand.Int63n(10)))
|
|
||||||
response.Body.Close()
|
|
||||||
client.CloseIdleConnections()
|
|
||||||
}
|
|
||||||
|
|
||||||
type realityVerifier struct {
|
|
||||||
*utls.UConn
|
|
||||||
serverName string
|
|
||||||
authKey []byte
|
|
||||||
verified bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var pOffset = utils.MustOK(reflect.TypeOf((*utls.UConn)(nil)).Elem().FieldByName("peerCertificates")).Offset
|
|
||||||
|
|
||||||
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
|
||||||
//p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
|
|
||||||
certs := *(*[]*x509.Certificate)(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + pOffset))
|
|
||||||
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
|
|
||||||
h := hmac.New(sha512.New, c.authKey)
|
|
||||||
h.Write(pub)
|
|
||||||
if bytes.Equal(h.Sum(nil), certs[0].Signature) {
|
|
||||||
c.verified = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
opts := x509.VerifyOptions{
|
|
||||||
DNSName: c.serverName,
|
|
||||||
Intermediates: x509.NewCertPool(),
|
|
||||||
}
|
|
||||||
for _, cert := range certs[1:] {
|
|
||||||
opts.Intermediates.AddCert(cert)
|
|
||||||
}
|
|
||||||
if _, err := certs[0].Verify(opts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
"github.com/mroth/weightedrand/v2"
|
"github.com/mroth/weightedrand/v2"
|
||||||
utls "github.com/sagernet/utls"
|
utls "github.com/refraction-networking/utls"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UConn struct {
|
type UConn struct {
|
||||||
@ -67,29 +67,13 @@ var Fingerprints = map[string]UClientHelloID{
|
|||||||
"firefox": {&utls.HelloFirefox_Auto},
|
"firefox": {&utls.HelloFirefox_Auto},
|
||||||
"safari": {&utls.HelloSafari_Auto},
|
"safari": {&utls.HelloSafari_Auto},
|
||||||
"ios": {&utls.HelloIOS_Auto},
|
"ios": {&utls.HelloIOS_Auto},
|
||||||
"android": {&utls.HelloAndroid_11_OkHttp},
|
"randomized": {&utls.HelloRandomized},
|
||||||
"edge": {&utls.HelloEdge_Auto},
|
|
||||||
"360": {&utls.Hello360_Auto},
|
|
||||||
"qq": {&utls.HelloQQ_Auto},
|
|
||||||
"random": {nil},
|
|
||||||
"randomized": {nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
weights := utls.DefaultWeights
|
|
||||||
weights.TLSVersMax_Set_VersionTLS13 = 1
|
|
||||||
weights.FirstKeyShare_Set_CurveP256 = 0
|
|
||||||
randomized := utls.HelloRandomized
|
|
||||||
randomized.Seed, _ = utls.NewPRNGSeed()
|
|
||||||
randomized.Weights = &weights
|
|
||||||
Fingerprints["randomized"] = UClientHelloID{&randomized}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyConfig(c *tls.Config) *utls.Config {
|
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,
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -25,7 +26,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/geodata"
|
"github.com/Dreamacro/clash/component/geodata"
|
||||||
"github.com/Dreamacro/clash/component/geodata/router"
|
"github.com/Dreamacro/clash/component/geodata/router"
|
||||||
P "github.com/Dreamacro/clash/component/process"
|
P "github.com/Dreamacro/clash/component/process"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
|
||||||
SNIFF "github.com/Dreamacro/clash/component/sniffer"
|
SNIFF "github.com/Dreamacro/clash/component/sniffer"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
"github.com/Dreamacro/clash/component/trie"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
@ -92,7 +92,6 @@ type DNS struct {
|
|||||||
Enable bool `yaml:"enable"`
|
Enable bool `yaml:"enable"`
|
||||||
PreferH3 bool `yaml:"prefer-h3"`
|
PreferH3 bool `yaml:"prefer-h3"`
|
||||||
IPv6 bool `yaml:"ipv6"`
|
IPv6 bool `yaml:"ipv6"`
|
||||||
IPv6Timeout uint `yaml:"ipv6-timeout"`
|
|
||||||
NameServer []dns.NameServer `yaml:"nameserver"`
|
NameServer []dns.NameServer `yaml:"nameserver"`
|
||||||
Fallback []dns.NameServer `yaml:"fallback"`
|
Fallback []dns.NameServer `yaml:"fallback"`
|
||||||
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
|
FallbackFilter FallbackFilter `yaml:"fallback-filter"`
|
||||||
@ -100,7 +99,7 @@ type DNS struct {
|
|||||||
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
|
EnhancedMode C.DNSMode `yaml:"enhanced-mode"`
|
||||||
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
|
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
|
||||||
FakeIPRange *fakeip.Pool
|
FakeIPRange *fakeip.Pool
|
||||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
Hosts *trie.DomainTrie[netip.Addr]
|
||||||
NameServerPolicy map[string][]dns.NameServer
|
NameServerPolicy map[string][]dns.NameServer
|
||||||
ProxyServerNameserver []dns.NameServer
|
ProxyServerNameserver []dns.NameServer
|
||||||
}
|
}
|
||||||
@ -121,9 +120,13 @@ type Profile struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TLS struct {
|
type TLS struct {
|
||||||
Certificate string `yaml:"certificate"`
|
RawCert `yaml:",inline"`
|
||||||
PrivateKey string `yaml:"private-key"`
|
CustomTrustCert []RawCert `yaml:"custom-certifactes"`
|
||||||
CustomTrustCert []string `yaml:"custom-certifactes"`
|
}
|
||||||
|
|
||||||
|
type RawCert struct {
|
||||||
|
Certificate string `yaml:"certificate"`
|
||||||
|
PrivateKey string `yaml:"private-key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPTables config
|
// IPTables config
|
||||||
@ -154,7 +157,7 @@ type Config struct {
|
|||||||
IPTables *IPTables
|
IPTables *IPTables
|
||||||
DNS *DNS
|
DNS *DNS
|
||||||
Experimental *Experimental
|
Experimental *Experimental
|
||||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
Hosts *trie.DomainTrie[netip.Addr]
|
||||||
Profile *Profile
|
Profile *Profile
|
||||||
Rules []C.Rule
|
Rules []C.Rule
|
||||||
SubRules map[string][]C.Rule
|
SubRules map[string][]C.Rule
|
||||||
@ -172,7 +175,6 @@ type RawDNS struct {
|
|||||||
Enable bool `yaml:"enable"`
|
Enable bool `yaml:"enable"`
|
||||||
PreferH3 bool `yaml:"prefer-h3"`
|
PreferH3 bool `yaml:"prefer-h3"`
|
||||||
IPv6 bool `yaml:"ipv6"`
|
IPv6 bool `yaml:"ipv6"`
|
||||||
IPv6Timeout uint `yaml:"ipv6-timeout"`
|
|
||||||
UseHosts bool `yaml:"use-hosts"`
|
UseHosts bool `yaml:"use-hosts"`
|
||||||
NameServer []string `yaml:"nameserver"`
|
NameServer []string `yaml:"nameserver"`
|
||||||
Fallback []string `yaml:"fallback"`
|
Fallback []string `yaml:"fallback"`
|
||||||
@ -265,7 +267,7 @@ type RawConfig struct {
|
|||||||
Sniffer RawSniffer `yaml:"sniffer"`
|
Sniffer RawSniffer `yaml:"sniffer"`
|
||||||
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
|
||||||
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
|
RuleProvider map[string]map[string]any `yaml:"rule-providers"`
|
||||||
Hosts map[string]any `yaml:"hosts"`
|
Hosts map[string]string `yaml:"hosts"`
|
||||||
DNS RawDNS `yaml:"dns"`
|
DNS RawDNS `yaml:"dns"`
|
||||||
Tun RawTun `yaml:"tun"`
|
Tun RawTun `yaml:"tun"`
|
||||||
TuicServer RawTuicServer `yaml:"tuic-server"`
|
TuicServer RawTuicServer `yaml:"tuic-server"`
|
||||||
@ -339,7 +341,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
UnifiedDelay: false,
|
UnifiedDelay: false,
|
||||||
Authentication: []string{},
|
Authentication: []string{},
|
||||||
LogLevel: log.INFO,
|
LogLevel: log.INFO,
|
||||||
Hosts: map[string]any{},
|
Hosts: map[string]string{},
|
||||||
Rule: []string{},
|
Rule: []string{},
|
||||||
Proxy: []map[string]any{},
|
Proxy: []map[string]any{},
|
||||||
ProxyGroup: []map[string]any{},
|
ProxyGroup: []map[string]any{},
|
||||||
@ -379,7 +381,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
Enable: false,
|
Enable: false,
|
||||||
IPv6: false,
|
IPv6: false,
|
||||||
UseHosts: true,
|
UseHosts: true,
|
||||||
IPv6Timeout: 100,
|
|
||||||
EnhancedMode: C.DNSMapping,
|
EnhancedMode: C.DNSMapping,
|
||||||
FakeIPRange: "198.18.0.1/16",
|
FakeIPRange: "198.18.0.1/16",
|
||||||
FallbackFilter: RawFallbackFilter{
|
FallbackFilter: RawFallbackFilter{
|
||||||
@ -418,9 +419,9 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
|
|||||||
StoreSelected: true,
|
StoreSelected: true,
|
||||||
},
|
},
|
||||||
GeoXUrl: RawGeoXUrl{
|
GeoXUrl: RawGeoXUrl{
|
||||||
Mmdb: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/country.mmdb",
|
GeoIp: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geoip.dat",
|
||||||
GeoIp: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat",
|
Mmdb: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb",
|
||||||
GeoSite: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat",
|
GeoSite: "https://ghproxy.com/https://raw.githubusercontent.com/Loyalsoldier/v2ray-rules-dat/release/geosite.dat",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -827,47 +828,21 @@ func parseRules(rulesConfig []string, proxies map[string]C.Proxy, subRules map[s
|
|||||||
return rules, nil
|
return rules, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHosts(cfg *RawConfig) (*trie.DomainTrie[resolver.HostValue], error) {
|
func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) {
|
||||||
tree := trie.New[resolver.HostValue]()
|
tree := trie.New[netip.Addr]()
|
||||||
|
|
||||||
// add default hosts
|
// add default hosts
|
||||||
hostValue, _ := resolver.NewHostValueByIPs(
|
if err := tree.Insert("localhost", netip.AddrFrom4([4]byte{127, 0, 0, 1})); err != nil {
|
||||||
[]netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1})})
|
|
||||||
if err := tree.Insert("localhost", hostValue); err != nil {
|
|
||||||
log.Errorln("insert localhost to host error: %s", err.Error())
|
log.Errorln("insert localhost to host error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.Hosts) != 0 {
|
if len(cfg.Hosts) != 0 {
|
||||||
for domain, anyValue := range cfg.Hosts {
|
for domain, ipStr := range cfg.Hosts {
|
||||||
if str, ok := anyValue.(string); ok && str == "clash" {
|
ip, err := netip.ParseAddr(ipStr)
|
||||||
if addrs, err := net.InterfaceAddrs(); err != nil {
|
|
||||||
log.Errorln("insert clash to host error: %s", err)
|
|
||||||
} else {
|
|
||||||
ips := make([]netip.Addr, 0)
|
|
||||||
for _, addr := range addrs {
|
|
||||||
if ipnet, ok := addr.(*net.IPNet); ok {
|
|
||||||
if ip, err := netip.ParseAddr(ipnet.IP.String()); err == nil {
|
|
||||||
ips = append(ips, ip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
anyValue = ips
|
|
||||||
}
|
|
||||||
}
|
|
||||||
value, err := resolver.NewHostValue(anyValue)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s is not a valid value", anyValue)
|
return nil, fmt.Errorf("%s is not a valid IP", ipStr)
|
||||||
}
|
}
|
||||||
if value.IsDomain {
|
_ = tree.Insert(domain, ip)
|
||||||
node := tree.Search(value.Domain)
|
|
||||||
for node != nil && node.Data().IsDomain {
|
|
||||||
if node.Data().Domain == domain {
|
|
||||||
return nil, fmt.Errorf("%s, there is a cycle in domain name mapping", domain)
|
|
||||||
}
|
|
||||||
node = tree.Search(node.Data().Domain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ = tree.Insert(domain, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tree.Optimize()
|
tree.Optimize()
|
||||||
@ -987,12 +962,24 @@ func parseNameServerPolicy(nsPolicy map[string]any, preferH3 bool) (map[string][
|
|||||||
policy := map[string][]dns.NameServer{}
|
policy := map[string][]dns.NameServer{}
|
||||||
|
|
||||||
for domain, server := range nsPolicy {
|
for domain, server := range nsPolicy {
|
||||||
|
var (
|
||||||
|
nameservers []dns.NameServer
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
servers, err := utils.ToStringSlice(server)
|
switch reflect.TypeOf(server).Kind() {
|
||||||
if err != nil {
|
case reflect.Slice, reflect.Array:
|
||||||
return nil, err
|
origin := reflect.ValueOf(server)
|
||||||
|
servers := make([]string, 0)
|
||||||
|
for i := 0; i < origin.Len(); i++ {
|
||||||
|
servers = append(servers, fmt.Sprintf("%v", origin.Index(i)))
|
||||||
|
}
|
||||||
|
nameservers, err = parseNameServer(servers, preferH3)
|
||||||
|
case reflect.String:
|
||||||
|
nameservers, err = parseNameServer([]string{fmt.Sprintf("%v", server)}, preferH3)
|
||||||
|
default:
|
||||||
|
return nil, errors.New("server format error, must be string or array")
|
||||||
}
|
}
|
||||||
nameservers, err := parseNameServer(servers, preferH3)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1055,7 +1042,7 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
|
|||||||
return sites, nil
|
return sites, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule) (*DNS, error) {
|
func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[netip.Addr], 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")
|
||||||
@ -1065,7 +1052,6 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
|||||||
Enable: cfg.Enable,
|
Enable: cfg.Enable,
|
||||||
Listen: cfg.Listen,
|
Listen: cfg.Listen,
|
||||||
PreferH3: cfg.PreferH3,
|
PreferH3: cfg.PreferH3,
|
||||||
IPv6Timeout: cfg.IPv6Timeout,
|
|
||||||
IPv6: cfg.IPv6,
|
IPv6: cfg.IPv6,
|
||||||
EnhancedMode: cfg.EnhancedMode,
|
EnhancedMode: cfg.EnhancedMode,
|
||||||
FallbackFilter: FallbackFilter{
|
FallbackFilter: FallbackFilter{
|
||||||
@ -1273,10 +1259,8 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if sniffer.Enable {
|
// Deprecated: Use Sniff instead
|
||||||
// Deprecated: Use Sniff instead
|
log.Warnln("Deprecated: Use Sniff instead")
|
||||||
log.Warnln("Deprecated: Use Sniff instead")
|
|
||||||
}
|
|
||||||
globalPorts, err := parsePortRange(snifferRaw.Ports)
|
globalPorts, err := parsePortRange(snifferRaw.Ports)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -3,8 +3,6 @@ package constant
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,7 +13,7 @@ type PlainContext interface {
|
|||||||
type ConnContext interface {
|
type ConnContext interface {
|
||||||
PlainContext
|
PlainContext
|
||||||
Metadata() *Metadata
|
Metadata() *Metadata
|
||||||
Conn() *N.BufferedConn
|
Conn() net.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
type PacketConnContext interface {
|
type PacketConnContext interface {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
@ -13,11 +12,11 @@ import (
|
|||||||
type ConnContext struct {
|
type ConnContext struct {
|
||||||
id uuid.UUID
|
id uuid.UUID
|
||||||
metadata *C.Metadata
|
metadata *C.Metadata
|
||||||
conn *N.BufferedConn
|
conn net.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext {
|
func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext {
|
||||||
id, _ := utils.UnsafeUUIDGenerator.NewV4()
|
id, _ := uuid.NewV4()
|
||||||
|
|
||||||
return &ConnContext{
|
return &ConnContext{
|
||||||
id: id,
|
id: id,
|
||||||
@ -37,6 +36,6 @@ func (c *ConnContext) Metadata() *C.Metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Conn implement C.ConnContext Conn
|
// Conn implement C.ConnContext Conn
|
||||||
func (c *ConnContext) Conn() *N.BufferedConn {
|
func (c *ConnContext) Conn() net.Conn {
|
||||||
return c.conn
|
return c.conn
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package context
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
@ -23,7 +22,7 @@ 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()
|
id, _ := uuid.NewV4()
|
||||||
return &DNSContext{
|
return &DNSContext{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ package context
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
@ -16,7 +15,7 @@ type PacketConnContext struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewPacketConnContext(metadata *C.Metadata) *PacketConnContext {
|
func NewPacketConnContext(metadata *C.Metadata) *PacketConnContext {
|
||||||
id, _ := utils.UnsafeUUIDGenerator.NewV4()
|
id, _ := uuid.NewV4()
|
||||||
return &PacketConnContext{
|
return &PacketConnContext{
|
||||||
id: id,
|
id: id,
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
@ -15,7 +16,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type client struct {
|
type client struct {
|
||||||
@ -68,7 +68,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
|||||||
} else if len(ips) == 0 {
|
} else if len(ips) == 0 {
|
||||||
return nil, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, c.host)
|
return nil, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, c.host)
|
||||||
}
|
}
|
||||||
ip = ips[fastrand.Intn(len(ips))]
|
ip = ips[rand.Intn(len(ips))]
|
||||||
}
|
}
|
||||||
|
|
||||||
network := "udp"
|
network := "udp"
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/common/cache"
|
"github.com/Dreamacro/clash/common/cache"
|
||||||
"github.com/Dreamacro/clash/common/nnip"
|
"github.com/Dreamacro/clash/common/nnip"
|
||||||
"github.com/Dreamacro/clash/component/fakeip"
|
"github.com/Dreamacro/clash/component/fakeip"
|
||||||
R "github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/trie"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/context"
|
"github.com/Dreamacro/clash/context"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
@ -21,7 +21,7 @@ type (
|
|||||||
middleware func(next handler) handler
|
middleware func(next handler) handler
|
||||||
)
|
)
|
||||||
|
|
||||||
func withHosts(hosts R.Hosts, mapping *cache.LruCache[netip.Addr, string]) middleware {
|
func withHosts(hosts *trie.DomainTrie[netip.Addr], mapping *cache.LruCache[netip.Addr, string]) middleware {
|
||||||
return func(next handler) handler {
|
return func(next handler) handler {
|
||||||
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
|
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
|
||||||
q := r.Question[0]
|
q := r.Question[0]
|
||||||
@ -31,68 +31,40 @@ func withHosts(hosts R.Hosts, mapping *cache.LruCache[netip.Addr, string]) middl
|
|||||||
}
|
}
|
||||||
|
|
||||||
host := strings.TrimRight(q.Name, ".")
|
host := strings.TrimRight(q.Name, ".")
|
||||||
handleCName := func(resp *D.Msg, domain string) {
|
|
||||||
rr := &D.CNAME{}
|
record := hosts.Search(host)
|
||||||
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeCNAME, Class: D.ClassINET, Ttl: 10}
|
if record == nil {
|
||||||
rr.Target = domain + "."
|
|
||||||
resp.Answer = append([]D.RR{rr}, resp.Answer...)
|
|
||||||
}
|
|
||||||
record, ok := hosts.Search(host, q.Qtype != D.TypeA && q.Qtype != D.TypeAAAA)
|
|
||||||
if !ok {
|
|
||||||
if record != nil && record.IsDomain {
|
|
||||||
// replace request domain
|
|
||||||
newR := r.Copy()
|
|
||||||
newR.Question[0].Name = record.Domain + "."
|
|
||||||
resp, err := next(ctx, newR)
|
|
||||||
if err == nil {
|
|
||||||
resp.Id = r.Id
|
|
||||||
resp.Question = r.Question
|
|
||||||
handleCName(resp, record.Domain)
|
|
||||||
}
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
return next(ctx, r)
|
return next(ctx, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ip := record.Data()
|
||||||
msg := r.Copy()
|
msg := r.Copy()
|
||||||
handleIPs := func() {
|
|
||||||
for _, ipAddr := range record.IPs {
|
if ip.Is4() && q.Qtype == D.TypeA {
|
||||||
if ipAddr.Is4() && q.Qtype == D.TypeA {
|
rr := &D.A{}
|
||||||
rr := &D.A{}
|
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: 10}
|
||||||
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeA, Class: D.ClassINET, Ttl: 10}
|
rr.A = ip.AsSlice()
|
||||||
rr.A = ipAddr.AsSlice()
|
|
||||||
msg.Answer = append(msg.Answer, rr)
|
msg.Answer = []D.RR{rr}
|
||||||
if mapping != nil {
|
} else if q.Qtype == D.TypeAAAA {
|
||||||
mapping.SetWithExpire(ipAddr, host, time.Now().Add(time.Second*10))
|
rr := &D.AAAA{}
|
||||||
}
|
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10}
|
||||||
} else if q.Qtype == D.TypeAAAA {
|
ip := ip.As16()
|
||||||
rr := &D.AAAA{}
|
rr.AAAA = ip[:]
|
||||||
rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10}
|
msg.Answer = []D.RR{rr}
|
||||||
ip := ipAddr.As16()
|
} else {
|
||||||
rr.AAAA = ip[:]
|
return next(ctx, r)
|
||||||
msg.Answer = append(msg.Answer, rr)
|
|
||||||
if mapping != nil {
|
|
||||||
mapping.SetWithExpire(ipAddr, host, time.Now().Add(time.Second*10))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch q.Qtype {
|
if mapping != nil {
|
||||||
case D.TypeA:
|
mapping.SetWithExpire(ip, host, time.Now().Add(time.Second*10))
|
||||||
handleIPs()
|
|
||||||
case D.TypeAAAA:
|
|
||||||
handleIPs()
|
|
||||||
case D.TypeCNAME:
|
|
||||||
handleCName(r, record.Domain)
|
|
||||||
default:
|
|
||||||
return next(ctx, r)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.SetType(context.DNSTypeHost)
|
ctx.SetType(context.DNSTypeHost)
|
||||||
msg.SetRcode(r, D.RcodeSuccess)
|
msg.SetRcode(r, D.RcodeSuccess)
|
||||||
msg.Authoritative = true
|
msg.Authoritative = true
|
||||||
msg.RecursionAvailable = true
|
msg.RecursionAvailable = true
|
||||||
|
|
||||||
return msg, nil
|
return msg, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -177,7 +149,6 @@ func withFakeIP(fakePool *fakeip.Pool) middleware {
|
|||||||
func withResolver(resolver *Resolver) handler {
|
func withResolver(resolver *Resolver) handler {
|
||||||
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
|
return func(ctx *context.DNSContext, r *D.Msg) (*D.Msg, error) {
|
||||||
ctx.SetType(context.DNSTypeRaw)
|
ctx.SetType(context.DNSTypeRaw)
|
||||||
|
|
||||||
q := r.Question[0]
|
q := r.Question[0]
|
||||||
|
|
||||||
// return a empty AAAA msg when ipv6 disabled
|
// return a empty AAAA msg when ipv6 disabled
|
||||||
@ -212,7 +183,7 @@ func NewHandler(resolver *Resolver, mapper *ResolverEnhancer) handler {
|
|||||||
middlewares := []middleware{}
|
middlewares := []middleware{}
|
||||||
|
|
||||||
if resolver.hosts != nil {
|
if resolver.hosts != nil {
|
||||||
middlewares = append(middlewares, withHosts(R.NewHosts(resolver.hosts), mapper.mapping))
|
middlewares = append(middlewares, withHosts(resolver.hosts, mapper.mapping))
|
||||||
}
|
}
|
||||||
|
|
||||||
if mapper.mode == C.DNSFakeIP {
|
if mapper.mode == C.DNSFakeIP {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -19,7 +20,6 @@ import (
|
|||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -42,8 +42,7 @@ type geositePolicyRecord struct {
|
|||||||
|
|
||||||
type Resolver struct {
|
type Resolver struct {
|
||||||
ipv6 bool
|
ipv6 bool
|
||||||
ipv6Timeout time.Duration
|
hosts *trie.DomainTrie[netip.Addr]
|
||||||
hosts *trie.DomainTrie[resolver.HostValue]
|
|
||||||
main []dnsClient
|
main []dnsClient
|
||||||
fallback []dnsClient
|
fallback []dnsClient
|
||||||
fallbackDomainFilters []fallbackDomainFilter
|
fallbackDomainFilters []fallbackDomainFilter
|
||||||
@ -92,20 +91,14 @@ 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
|
|
||||||
if r != nil {
|
|
||||||
waitIPv6 = time.NewTimer(r.ipv6Timeout)
|
|
||||||
} else {
|
|
||||||
waitIPv6 = time.NewTimer(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
defer waitIPv6.Stop()
|
|
||||||
select {
|
select {
|
||||||
case ipv6s, open := <-ch:
|
case ipv6s, open := <-ch:
|
||||||
if !open && err != nil {
|
if !open && err != nil {
|
||||||
return nil, resolver.ErrIPNotFound
|
return nil, resolver.ErrIPNotFound
|
||||||
}
|
}
|
||||||
ips = append(ips, ipv6s...)
|
ips = append(ips, ipv6s...)
|
||||||
case <-waitIPv6.C:
|
case <-time.After(30 * time.Millisecond):
|
||||||
// wait ipv6 result
|
// wait ipv6 result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +113,7 @@ func (r *Resolver) ResolveIP(ctx context.Context, host string) (ip netip.Addr, e
|
|||||||
} else if len(ips) == 0 {
|
} else if len(ips) == 0 {
|
||||||
return netip.Addr{}, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, host)
|
return netip.Addr{}, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, host)
|
||||||
}
|
}
|
||||||
return ips[fastrand.Intn(len(ips))], nil
|
return ips[rand.Intn(len(ips))], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupIPv4 request with TypeA
|
// LookupIPv4 request with TypeA
|
||||||
@ -136,7 +129,7 @@ func (r *Resolver) ResolveIPv4(ctx context.Context, host string) (ip netip.Addr,
|
|||||||
} else if len(ips) == 0 {
|
} else if len(ips) == 0 {
|
||||||
return netip.Addr{}, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, host)
|
return netip.Addr{}, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, host)
|
||||||
}
|
}
|
||||||
return ips[fastrand.Intn(len(ips))], nil
|
return ips[rand.Intn(len(ips))], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupIPv6 request with TypeAAAA
|
// LookupIPv6 request with TypeAAAA
|
||||||
@ -152,7 +145,7 @@ func (r *Resolver) ResolveIPv6(ctx context.Context, host string) (ip netip.Addr,
|
|||||||
} else if len(ips) == 0 {
|
} else if len(ips) == 0 {
|
||||||
return netip.Addr{}, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, host)
|
return netip.Addr{}, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, host)
|
||||||
}
|
}
|
||||||
return ips[fastrand.Intn(len(ips))], nil
|
return ips[rand.Intn(len(ips))], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
|
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
|
||||||
@ -426,27 +419,24 @@ type Config struct {
|
|||||||
Default []NameServer
|
Default []NameServer
|
||||||
ProxyServer []NameServer
|
ProxyServer []NameServer
|
||||||
IPv6 bool
|
IPv6 bool
|
||||||
IPv6Timeout uint
|
|
||||||
EnhancedMode C.DNSMode
|
EnhancedMode C.DNSMode
|
||||||
FallbackFilter FallbackFilter
|
FallbackFilter FallbackFilter
|
||||||
Pool *fakeip.Pool
|
Pool *fakeip.Pool
|
||||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
Hosts *trie.DomainTrie[netip.Addr]
|
||||||
Policy map[string][]NameServer
|
Policy map[string][]NameServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolver(config Config) *Resolver {
|
func NewResolver(config Config) *Resolver {
|
||||||
defaultResolver := &Resolver{
|
defaultResolver := &Resolver{
|
||||||
main: transform(config.Default, nil),
|
main: transform(config.Default, nil),
|
||||||
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
|
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
|
||||||
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r := &Resolver{
|
r := &Resolver{
|
||||||
ipv6: config.IPv6,
|
ipv6: config.IPv6,
|
||||||
main: transform(config.Main, defaultResolver),
|
main: transform(config.Main, defaultResolver),
|
||||||
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
|
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
|
||||||
hosts: config.Hosts,
|
hosts: config.Hosts,
|
||||||
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(config.Fallback) != 0 {
|
if len(config.Fallback) != 0 {
|
||||||
@ -512,12 +502,11 @@ func NewResolver(config Config) *Resolver {
|
|||||||
|
|
||||||
func NewProxyServerHostResolver(old *Resolver) *Resolver {
|
func NewProxyServerHostResolver(old *Resolver) *Resolver {
|
||||||
r := &Resolver{
|
r := &Resolver{
|
||||||
ipv6: old.ipv6,
|
ipv6: old.ipv6,
|
||||||
main: old.proxyServer,
|
main: old.proxyServer,
|
||||||
lruCache: old.lruCache,
|
lruCache: old.lruCache,
|
||||||
hosts: old.hosts,
|
hosts: old.hosts,
|
||||||
policy: old.policy,
|
policy: old.policy,
|
||||||
ipv6Timeout: old.ipv6Timeout,
|
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ func setMsgTTL(msg *D.Msg, ttl uint32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isIPRequest(q D.Question) bool {
|
func isIPRequest(q D.Question) bool {
|
||||||
return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA || q.Qtype == D.TypeCNAME)
|
return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA)
|
||||||
}
|
}
|
||||||
|
|
||||||
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||||
|
@ -1,25 +1,26 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
os="clash.meta-linux-"
|
os="clash.meta-linux-"
|
||||||
case $TARGETPLATFORM in
|
arch=`uname -m`
|
||||||
"linux/amd64")
|
case $arch in
|
||||||
|
"x86_64")
|
||||||
arch="amd64-compatible"
|
arch="amd64-compatible"
|
||||||
;;
|
;;
|
||||||
"linux/386")
|
"x86")
|
||||||
arch="386"
|
arch="386-cgo"
|
||||||
;;
|
;;
|
||||||
"linux/arm64")
|
"aarch64")
|
||||||
arch="arm64"
|
arch="arm64"
|
||||||
;;
|
;;
|
||||||
"linux/arm/v7")
|
"armv7l")
|
||||||
arch="armv7"
|
arch="armv7"
|
||||||
;;
|
;;
|
||||||
"riscv64")
|
"riscv64")
|
||||||
arch="riscv64"
|
arch="riscv64-cgo"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Unknown architecture"
|
echo "Unknown architecture"
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
file_name="$os$arch-$(cat bin/version.txt)"
|
file_name="$os$arch"
|
||||||
echo $file_name
|
echo $file_name
|
505
docs/config.yaml
505
docs/config.yaml
@ -7,17 +7,22 @@ mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口
|
|||||||
# tproxy-port: 7893
|
# tproxy-port: 7893
|
||||||
|
|
||||||
allow-lan: true # 允许局域网连接
|
allow-lan: true # 允许局域网连接
|
||||||
bind-address: "*" # 绑定 IP 地址,仅作用于 allow-lan 为 true,'*'表示所有地址
|
bind-address: "*" # 绑定IP地址,仅作用于 allow-lan 为 true,'*'表示所有地址
|
||||||
|
|
||||||
# find-process-mode has 3 values:always, strict, off
|
# find-process-mode has 3 values: always, strict, off
|
||||||
# - always, 开启,强制匹配所有进程
|
# - always, 开启,强制匹配所有进程
|
||||||
# - strict, 默认,由 clash 判断是否开启
|
# - strict, 默认,由clash判断是否开启
|
||||||
# - off, 不匹配进程,推荐在路由器上使用此模式
|
# - off, 不匹配进程,推荐在路由器上使用此模式
|
||||||
find-process-mode: strict
|
find-process-mode: strict
|
||||||
|
|
||||||
|
# global-client-fingerprint:全局TLS指纹,优先低于proxy内的 client-fingerprint
|
||||||
|
# accepts "chrome","firefox","safari","ios","random","none" options.
|
||||||
|
# Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan.
|
||||||
|
global-client-fingerprint: chrome
|
||||||
|
|
||||||
mode: rule
|
mode: rule
|
||||||
|
|
||||||
#自定义 geodata url
|
#自定义 geox-url
|
||||||
geox-url:
|
geox-url:
|
||||||
geoip: "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
|
geoip: "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
|
||||||
geosite: "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
|
geosite: "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
|
||||||
@ -27,30 +32,16 @@ log-level: debug # 日志等级 silent/error/warning/info/debug
|
|||||||
|
|
||||||
ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS 请求 AAAA 记录
|
ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS 请求 AAAA 记录
|
||||||
|
|
||||||
tls:
|
|
||||||
certificate: string # 证书 PEM 格式,或者 证书的路径
|
|
||||||
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
|
|
||||||
custom-certifactes:
|
|
||||||
- |
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
format/pem...
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
|
|
||||||
external-controller: 0.0.0.0:9093 # RESTful API 监听地址
|
external-controller: 0.0.0.0:9093 # RESTful API 监听地址
|
||||||
external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要配置 tls 部分配置文件
|
external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要配置 tls 部分配置文件
|
||||||
# secret: "123456" # `Authorization:Bearer ${secret}`
|
# secret: "123456" # `Authorization: Bearer ${secret}`
|
||||||
|
|
||||||
# tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP
|
# tcp-concurrent: true # TCP并发连接所有IP, 将使用最快握手的TCP
|
||||||
external-ui: /path/to/ui/folder # 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
|
external-ui: /path/to/ui/folder # 配置WEB UI目录,使用http://{{external-controller}}/ui 访问
|
||||||
|
|
||||||
# interface-name: en0 # 设置出口网卡
|
# interface-name: en0 # 设置出口网卡
|
||||||
|
|
||||||
# 全局 TLS 指纹,优先低于 proxy 内的 client-fingerprint
|
# routing-mark: 6666 # 配置 fwmark 仅用于Linux
|
||||||
# 可选: "chrome","firefox","safari","ios","random","none" options.
|
|
||||||
# Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan.
|
|
||||||
global-client-fingerprint: chrome
|
|
||||||
|
|
||||||
# routing-mark:6666 # 配置 fwmark 仅用于 Linux
|
|
||||||
experimental:
|
experimental:
|
||||||
|
|
||||||
# 类似于 /etc/hosts, 仅支持配置单个 IP
|
# 类似于 /etc/hosts, 仅支持配置单个 IP
|
||||||
@ -58,15 +49,6 @@ hosts:
|
|||||||
# '*.clash.dev': 127.0.0.1
|
# '*.clash.dev': 127.0.0.1
|
||||||
# '.dev': 127.0.0.1
|
# '.dev': 127.0.0.1
|
||||||
# 'alpha.clash.dev': '::1'
|
# 'alpha.clash.dev': '::1'
|
||||||
# test.com: [1.1.1.1, 2.2.2.2]
|
|
||||||
# clash.lan: clash # clash 为特别字段,将加入本地所有网卡的地址
|
|
||||||
# baidu.com: google.com # 只允许配置一个别名
|
|
||||||
|
|
||||||
profile: # 存储 select 选择记录
|
|
||||||
store-selected: false
|
|
||||||
|
|
||||||
# 持久化 fake-ip
|
|
||||||
store-fake-ip: true
|
|
||||||
|
|
||||||
# Tun 配置
|
# Tun 配置
|
||||||
tun:
|
tun:
|
||||||
@ -93,10 +75,10 @@ tun:
|
|||||||
#- 1000
|
#- 1000
|
||||||
# exclude_uid_range: # 排除路由的的用户范围
|
# exclude_uid_range: # 排除路由的的用户范围
|
||||||
# - 1000-99999
|
# - 1000-99999
|
||||||
|
|
||||||
# Android 用户和应用规则仅在 Android 下被支持
|
# Android 用户和应用规则仅在 Android 下被支持
|
||||||
# 并且需要 auto_route
|
# 并且需要 auto_route
|
||||||
|
|
||||||
# include_android_user: # 限制被路由的 Android 用户
|
# include_android_user: # 限制被路由的 Android 用户
|
||||||
# - 0
|
# - 0
|
||||||
# - 10
|
# - 10
|
||||||
@ -123,13 +105,15 @@ sniffer:
|
|||||||
# 是否使用嗅探结果作为实际访问,默认 true
|
# 是否使用嗅探结果作为实际访问,默认 true
|
||||||
# 全局配置,优先级低于 sniffer.sniff 实际配置
|
# 全局配置,优先级低于 sniffer.sniff 实际配置
|
||||||
override-destination: false
|
override-destination: false
|
||||||
sniff: # TLS 默认如果不配置 ports 默认嗅探 443
|
sniff:
|
||||||
|
# TLS 默认如果不配置 ports 默认嗅探 443
|
||||||
TLS:
|
TLS:
|
||||||
# ports: [443, 8443]
|
# ports: [443, 8443]
|
||||||
|
|
||||||
# 默认嗅探 80
|
# 默认嗅探 80
|
||||||
HTTP: # 需要嗅探的端口
|
HTTP:
|
||||||
|
# 需要嗅探的端口
|
||||||
|
|
||||||
ports: [80, 8080-8880]
|
ports: [80, 8080-8880]
|
||||||
# 可覆盖 sniffer.override-destination
|
# 可覆盖 sniffer.override-destination
|
||||||
override-destination: true
|
override-destination: true
|
||||||
@ -144,7 +128,7 @@ sniffer:
|
|||||||
- tls
|
- tls
|
||||||
- http
|
- http
|
||||||
# 强制对此域名进行嗅探
|
# 强制对此域名进行嗅探
|
||||||
|
|
||||||
# 仅对白名单中的端口进行嗅探,默认为 443,80
|
# 仅对白名单中的端口进行嗅探,默认为 443,80
|
||||||
# 已废弃,若 sniffer.sniff 配置则此项无效
|
# 已废弃,若 sniffer.sniff 配置则此项无效
|
||||||
port-whitelist:
|
port-whitelist:
|
||||||
@ -152,8 +136,27 @@ sniffer:
|
|||||||
- "443"
|
- "443"
|
||||||
# - 8000-9999
|
# - 8000-9999
|
||||||
|
|
||||||
|
# shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
||||||
|
# ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456
|
||||||
|
# vmess-config: vmess://1:9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68@:12345
|
||||||
|
|
||||||
tunnels: # one line config
|
# tuic服务器入口(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
||||||
|
#tuic-server:
|
||||||
|
# enable: true
|
||||||
|
# listen: 127.0.0.1:10443
|
||||||
|
# token:
|
||||||
|
# - TOKEN
|
||||||
|
# certificate: ./server.crt
|
||||||
|
# private-key: ./server.key
|
||||||
|
# congestion-controller: bbr
|
||||||
|
# max-idle-time: 15000
|
||||||
|
# authentication-timeout: 1000
|
||||||
|
# alpn:
|
||||||
|
# - h3
|
||||||
|
# max-udp-relay-packet-size: 1500
|
||||||
|
|
||||||
|
tunnels:
|
||||||
|
# one line config
|
||||||
- tcp/udp,127.0.0.1:6553,114.114.114.114:53,proxy
|
- tcp/udp,127.0.0.1:6553,114.114.114.114:53,proxy
|
||||||
- tcp,127.0.0.1:6666,rds.mysql.com:3306,vpn
|
- tcp,127.0.0.1:6666,rds.mysql.com:3306,vpn
|
||||||
# full yaml config
|
# full yaml config
|
||||||
@ -162,6 +165,12 @@ tunnels: # one line config
|
|||||||
target: target.com
|
target: target.com
|
||||||
proxy: proxy
|
proxy: proxy
|
||||||
|
|
||||||
|
profile:
|
||||||
|
# 存储select选择记录
|
||||||
|
store-selected: false
|
||||||
|
|
||||||
|
# 持久化fake-ip
|
||||||
|
store-fake-ip: true
|
||||||
|
|
||||||
# DNS配置
|
# DNS配置
|
||||||
dns:
|
dns:
|
||||||
@ -169,7 +178,7 @@ dns:
|
|||||||
prefer-h3: true # 开启 DoH 支持 HTTP/3,将并发尝试
|
prefer-h3: true # 开启 DoH 支持 HTTP/3,将并发尝试
|
||||||
listen: 0.0.0.0:53 # 开启 DNS 服务器监听
|
listen: 0.0.0.0:53 # 开启 DNS 服务器监听
|
||||||
# ipv6: false # false 将返回 AAAA 的空结果
|
# ipv6: false # false 将返回 AAAA 的空结果
|
||||||
# ipv6-timeout: 300 # 单位:ms,内部双栈并发时,向上游查询 AAAA 时,等待 AAAA 的时间,默认 100ms
|
|
||||||
# 用于解析 nameserver,fallback 以及其他DNS服务器配置的,DNS 服务域名
|
# 用于解析 nameserver,fallback 以及其他DNS服务器配置的,DNS 服务域名
|
||||||
# 只能使用纯 IP 地址,可使用加密 DNS
|
# 只能使用纯 IP 地址,可使用加密 DNS
|
||||||
default-nameserver:
|
default-nameserver:
|
||||||
@ -178,16 +187,16 @@ dns:
|
|||||||
- tls://1.12.12.12:853
|
- tls://1.12.12.12:853
|
||||||
- tls://223.5.5.5:853
|
- tls://223.5.5.5:853
|
||||||
enhanced-mode: fake-ip # or redir-host
|
enhanced-mode: fake-ip # or redir-host
|
||||||
|
|
||||||
fake-ip-range: 198.18.0.1/16 # fake-ip 池设置
|
fake-ip-range: 198.18.0.1/16 # fake-ip 池设置
|
||||||
|
|
||||||
# use-hosts: true # 查询 hosts
|
# use-hosts: true # 查询 hosts
|
||||||
|
|
||||||
# 配置不使用fake-ip的域名
|
# 配置不使用fake-ip的域名
|
||||||
# fake-ip-filter:
|
# fake-ip-filter:
|
||||||
# - '*.lan'
|
# - '*.lan'
|
||||||
# - localhost.ptlogin2.qq.com
|
# - localhost.ptlogin2.qq.com
|
||||||
|
|
||||||
# DNS主要域名配置
|
# DNS主要域名配置
|
||||||
# 支持 UDP,TCP,DoT,DoH,DoQ
|
# 支持 UDP,TCP,DoT,DoH,DoQ
|
||||||
# 这部分为主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS
|
# 这部分为主要 DNS 配置,影响所有直连,确保使用对大陆解析精准的 DNS
|
||||||
@ -201,20 +210,20 @@ dns:
|
|||||||
- dhcp://en0 # dns from dhcp
|
- dhcp://en0 # dns from dhcp
|
||||||
- quic://dns.adguard.com:784 # DNS over QUIC
|
- quic://dns.adguard.com:784 # DNS over QUIC
|
||||||
# - '8.8.8.8#en0' # 兼容指定DNS出口网卡
|
# - '8.8.8.8#en0' # 兼容指定DNS出口网卡
|
||||||
|
|
||||||
# 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN,非必要配置
|
# 当配置 fallback 时,会查询 nameserver 中返回的 IP 是否为 CN,非必要配置
|
||||||
# 当不是 CN,则使用 fallback 中的 DNS 查询结果
|
# 当不是 CN,则使用 fallback 中的 DNS 查询结果
|
||||||
# 确保配置 fallback 时能够正常查询
|
# 确保配置 fallback 时能够正常查询
|
||||||
# fallback:
|
# fallback:
|
||||||
# - tcp://1.1.1.1
|
# - tcp://1.1.1.1
|
||||||
# - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询,ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡
|
# - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询,ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡
|
||||||
|
|
||||||
# 专用于节点域名解析的 DNS 服务器,非必要配置项
|
# 专用于节点域名解析的 DNS 服务器,非必要配置项
|
||||||
# 配置服务器若查询失败将使用 nameserver,非并发查询
|
# 配置服务器若查询失败将使用 nameserver,非并发查询
|
||||||
# proxy-server-nameserver:
|
# proxy-server-nameserver:
|
||||||
# - https://dns.google/dns-query
|
# - https://dns.google/dns-query
|
||||||
# - tls://one.one.one.one
|
# - tls://one.one.one.one
|
||||||
|
|
||||||
# 配置 fallback 使用条件
|
# 配置 fallback 使用条件
|
||||||
# fallback-filter:
|
# fallback-filter:
|
||||||
# geoip: true # 配置是否使用 geoip
|
# geoip: true # 配置是否使用 geoip
|
||||||
@ -229,53 +238,14 @@ dns:
|
|||||||
# - '+.google.com'
|
# - '+.google.com'
|
||||||
# - '+.facebook.com'
|
# - '+.facebook.com'
|
||||||
# - '+.youtube.com'
|
# - '+.youtube.com'
|
||||||
|
|
||||||
# 配置查询域名使用的 DNS 服务器
|
|
||||||
nameserver-policy: # 'www.baidu.com': '114.114.114.114'
|
|
||||||
# '+.internal.crop.com': '10.0.0.1'
|
|
||||||
"geosite:cn":
|
|
||||||
- https://doh.pub/dns-query
|
|
||||||
- https://dns.alidns.com/dns-query
|
|
||||||
"www.baidu.com": [https://doh.pub/dns-query, https://dns.alidns.com/dns-query]
|
|
||||||
|
|
||||||
proxies: # socks5
|
# 配置查询域名使用的 DNS 服务器
|
||||||
- name: "socks"
|
nameserver-policy:
|
||||||
type: socks5
|
# 'www.baidu.com': '114.114.114.114'
|
||||||
server: server
|
# '+.internal.crop.com': '10.0.0.1'
|
||||||
port: 443
|
"geosite:cn": "https://doh.pub/dns-query"
|
||||||
# username: username
|
"www.baidu.com": [https://doh.pub/dns-query,https://dns.alidns.com/dns-query]
|
||||||
# password: password
|
proxies:
|
||||||
# tls: true
|
|
||||||
# fingerprint: xxxx
|
|
||||||
# skip-cert-verify: true
|
|
||||||
# udp: true
|
|
||||||
# ip-version: ipv6
|
|
||||||
|
|
||||||
# http
|
|
||||||
- name: "http"
|
|
||||||
type: http
|
|
||||||
server: server
|
|
||||||
port: 443
|
|
||||||
# username: username
|
|
||||||
# password: password
|
|
||||||
# tls: true # https
|
|
||||||
# skip-cert-verify: true
|
|
||||||
# sni: custom.com
|
|
||||||
# fingerprint: xxxx # 同 experimental.fingerprints 使用 sha256 指纹,配置协议独立的指纹,将忽略 experimental.fingerprints
|
|
||||||
# ip-version: dual
|
|
||||||
|
|
||||||
# Snell
|
|
||||||
# Beware that there's currently no UDP support yet
|
|
||||||
- name: "snell"
|
|
||||||
type: snell
|
|
||||||
server: server
|
|
||||||
port: 44046
|
|
||||||
psk: yourpsk
|
|
||||||
# version: 2
|
|
||||||
# obfs-opts:
|
|
||||||
# mode: http # or tls
|
|
||||||
# host: bing.com
|
|
||||||
|
|
||||||
# Shadowsocks
|
# Shadowsocks
|
||||||
# cipher支持:
|
# cipher支持:
|
||||||
# aes-128-gcm aes-192-gcm aes-256-gcm
|
# aes-128-gcm aes-192-gcm aes-256-gcm
|
||||||
@ -298,7 +268,6 @@ proxies: # socks5
|
|||||||
# UDP 则为双栈解析,获取结果中的第一个 IPv4
|
# UDP 则为双栈解析,获取结果中的第一个 IPv4
|
||||||
# ipv6-prefer 同 ipv4-prefer
|
# ipv6-prefer 同 ipv4-prefer
|
||||||
# 现有协议都支持此参数,TCP 效果仅在开启 tcp-concurrent 生效
|
# 现有协议都支持此参数,TCP 效果仅在开启 tcp-concurrent 生效
|
||||||
|
|
||||||
- name: "ss2"
|
- name: "ss2"
|
||||||
type: ss
|
type: ss
|
||||||
server: server
|
server: server
|
||||||
@ -309,7 +278,7 @@ proxies: # socks5
|
|||||||
plugin-opts:
|
plugin-opts:
|
||||||
mode: tls # or http
|
mode: tls # or http
|
||||||
# host: bing.com
|
# host: bing.com
|
||||||
|
|
||||||
- name: "ss3"
|
- name: "ss3"
|
||||||
type: ss
|
type: ss
|
||||||
server: server
|
server: server
|
||||||
@ -319,17 +288,17 @@ proxies: # socks5
|
|||||||
plugin: v2ray-plugin
|
plugin: v2ray-plugin
|
||||||
plugin-opts:
|
plugin-opts:
|
||||||
mode: websocket # no QUIC now
|
mode: websocket # no QUIC now
|
||||||
# tls: true # wss
|
# tls: true # wss
|
||||||
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||||
# 配置指纹将实现 SSL Pining 效果
|
# 配置指纹将实现 SSL Pining 效果
|
||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
# host: bing.com
|
# host: bing.com
|
||||||
# path: "/"
|
# path: "/"
|
||||||
# mux: true
|
# mux: true
|
||||||
# headers:
|
# headers:
|
||||||
# custom: value
|
# custom: value
|
||||||
|
|
||||||
- name: "ss4"
|
- name: "ss4"
|
||||||
type: ss
|
type: ss
|
||||||
server: server
|
server: server
|
||||||
@ -340,8 +309,7 @@ proxies: # socks5
|
|||||||
plugin-opts:
|
plugin-opts:
|
||||||
host: "cloud.tencent.com"
|
host: "cloud.tencent.com"
|
||||||
password: "shadow_tls_password"
|
password: "shadow_tls_password"
|
||||||
version: 2 # support 1/2/3
|
|
||||||
|
|
||||||
# vmess
|
# vmess
|
||||||
# cipher支持 auto/aes-128-gcm/chacha20-poly1305/none
|
# cipher支持 auto/aes-128-gcm/chacha20-poly1305/none
|
||||||
- name: "vmess"
|
- name: "vmess"
|
||||||
@ -364,7 +332,7 @@ proxies: # socks5
|
|||||||
# Host: v2ray.com
|
# Host: v2ray.com
|
||||||
# max-early-data: 2048
|
# max-early-data: 2048
|
||||||
# early-data-header-name: Sec-WebSocket-Protocol
|
# early-data-header-name: Sec-WebSocket-Protocol
|
||||||
|
|
||||||
- name: "vmess-h2"
|
- name: "vmess-h2"
|
||||||
type: vmess
|
type: vmess
|
||||||
server: server
|
server: server
|
||||||
@ -380,7 +348,7 @@ proxies: # socks5
|
|||||||
- http.example.com
|
- http.example.com
|
||||||
- http-alt.example.com
|
- http-alt.example.com
|
||||||
path: /
|
path: /
|
||||||
|
|
||||||
- name: "vmess-http"
|
- name: "vmess-http"
|
||||||
type: vmess
|
type: vmess
|
||||||
server: server
|
server: server
|
||||||
@ -391,15 +359,15 @@ proxies: # socks5
|
|||||||
# udp: true
|
# udp: true
|
||||||
# network: http
|
# network: http
|
||||||
# http-opts:
|
# http-opts:
|
||||||
# method: "GET"
|
# # method: "GET"
|
||||||
# path:
|
# # path:
|
||||||
# - '/'
|
# # - '/'
|
||||||
# - '/video'
|
# # - '/video'
|
||||||
# headers:
|
# # headers:
|
||||||
# Connection:
|
# # Connection:
|
||||||
# - keep-alive
|
# # - keep-alive
|
||||||
# ip-version: ipv4 # 设置使用 IP 类型偏好,可选:ipv4,ipv6,dual,默认值:dual
|
# ip-version: ipv4 # 设置使用 IP 类型偏好,可选:ipv4,ipv6,dual,默认值:dual
|
||||||
|
|
||||||
- name: vmess-grpc
|
- name: vmess-grpc
|
||||||
server: server
|
server: server
|
||||||
port: 443
|
port: 443
|
||||||
@ -415,7 +383,100 @@ proxies: # socks5
|
|||||||
grpc-opts:
|
grpc-opts:
|
||||||
grpc-service-name: "example"
|
grpc-service-name: "example"
|
||||||
# ip-version: ipv4
|
# ip-version: ipv4
|
||||||
|
|
||||||
|
# socks5
|
||||||
|
- name: "socks"
|
||||||
|
type: socks5
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
# username: username
|
||||||
|
# password: password
|
||||||
|
# tls: true
|
||||||
|
# fingerprint: xxxx
|
||||||
|
# skip-cert-verify: true
|
||||||
|
# udp: true
|
||||||
|
# ip-version: ipv6
|
||||||
|
|
||||||
|
# http
|
||||||
|
- name: "http"
|
||||||
|
type: http
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
# username: username
|
||||||
|
# password: password
|
||||||
|
# tls: true # https
|
||||||
|
# skip-cert-verify: true
|
||||||
|
# sni: custom.com
|
||||||
|
# fingerprint: xxxx # 同 experimental.fingerprints 使用 sha256 指纹,配置协议独立的指纹,将忽略 experimental.fingerprints
|
||||||
|
# ip-version: dual
|
||||||
|
|
||||||
|
# Snell
|
||||||
|
# Beware that there's currently no UDP support yet
|
||||||
|
- name: "snell"
|
||||||
|
type: snell
|
||||||
|
server: server
|
||||||
|
port: 44046
|
||||||
|
psk: yourpsk
|
||||||
|
# version: 2
|
||||||
|
# obfs-opts:
|
||||||
|
# mode: http # or tls
|
||||||
|
# host: bing.com
|
||||||
|
|
||||||
|
# Trojan
|
||||||
|
- name: "trojan"
|
||||||
|
type: trojan
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
password: yourpsk
|
||||||
|
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||||
|
# fingerprint: xxxx
|
||||||
|
# udp: true
|
||||||
|
# sni: example.com # aka server name
|
||||||
|
# alpn:
|
||||||
|
# - h2
|
||||||
|
# - http/1.1
|
||||||
|
# skip-cert-verify: true
|
||||||
|
|
||||||
|
- name: trojan-grpc
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
type: trojan
|
||||||
|
password: "example"
|
||||||
|
network: grpc
|
||||||
|
sni: example.com
|
||||||
|
# skip-cert-verify: true
|
||||||
|
# fingerprint: xxxx
|
||||||
|
udp: true
|
||||||
|
grpc-opts:
|
||||||
|
grpc-service-name: "example"
|
||||||
|
|
||||||
|
- name: trojan-ws
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
type: trojan
|
||||||
|
password: "example"
|
||||||
|
network: ws
|
||||||
|
sni: example.com
|
||||||
|
# skip-cert-verify: true
|
||||||
|
# fingerprint: xxxx
|
||||||
|
udp: true
|
||||||
|
# ws-opts:
|
||||||
|
# path: /path
|
||||||
|
# headers:
|
||||||
|
# Host: example.com
|
||||||
|
|
||||||
|
- name: "trojan-xtls"
|
||||||
|
type: trojan
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
password: yourpsk
|
||||||
|
flow: "xtls-rprx-direct" # xtls-rprx-origin xtls-rprx-direct
|
||||||
|
flow-show: true
|
||||||
|
# udp: true
|
||||||
|
# sni: example.com # aka server name
|
||||||
|
# skip-cert-verify: true
|
||||||
|
# fingerprint: xxxx
|
||||||
|
|
||||||
# vless
|
# vless
|
||||||
- name: "vless-tcp"
|
- name: "vless-tcp"
|
||||||
type: vless
|
type: vless
|
||||||
@ -428,53 +489,7 @@ proxies: # socks5
|
|||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||||
|
|
||||||
- name: "vless-vision"
|
|
||||||
type: vless
|
|
||||||
server: server
|
|
||||||
port: 443
|
|
||||||
uuid: uuid
|
|
||||||
network: tcp
|
|
||||||
tls: true
|
|
||||||
udp: true
|
|
||||||
flow: xtls-rprx-vision
|
|
||||||
client-fingerprint: chrome
|
|
||||||
# fingerprint: xxxx
|
|
||||||
# skip-cert-verify: true
|
|
||||||
|
|
||||||
- name: "vless-reality-vision"
|
|
||||||
type: vless
|
|
||||||
server: server
|
|
||||||
port: 443
|
|
||||||
uuid: uuid
|
|
||||||
network: tcp
|
|
||||||
tls: true
|
|
||||||
udp: true
|
|
||||||
flow: xtls-rprx-vision
|
|
||||||
servername: www.microsoft.com # REALITY servername
|
|
||||||
reality-opts:
|
|
||||||
public-key: xxx
|
|
||||||
short-id: xxx # optional
|
|
||||||
client-fingerprint: chrome # cannot be empty
|
|
||||||
|
|
||||||
- name: "vless-reality-grpc"
|
|
||||||
type: vless
|
|
||||||
server: server
|
|
||||||
port: 443
|
|
||||||
uuid: uuid
|
|
||||||
network: grpc
|
|
||||||
tls: true
|
|
||||||
udp: true
|
|
||||||
flow:
|
|
||||||
# skip-cert-verify: true
|
|
||||||
client-fingerprint: chrome
|
|
||||||
servername: testingcf.jsdelivr.net
|
|
||||||
grpc-opts:
|
|
||||||
grpc-service-name: "grpc"
|
|
||||||
reality-opts:
|
|
||||||
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
|
|
||||||
short-id: 10f897e26c4b9478
|
|
||||||
|
|
||||||
- name: "vless-ws"
|
- name: "vless-ws"
|
||||||
type: vless
|
type: vless
|
||||||
server: server
|
server: server
|
||||||
@ -491,62 +506,7 @@ proxies: # socks5
|
|||||||
path: "/"
|
path: "/"
|
||||||
headers:
|
headers:
|
||||||
Host: example.com
|
Host: example.com
|
||||||
|
|
||||||
# Trojan
|
|
||||||
- name: "trojan"
|
|
||||||
type: trojan
|
|
||||||
server: server
|
|
||||||
port: 443
|
|
||||||
password: yourpsk
|
|
||||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
|
||||||
# fingerprint: xxxx
|
|
||||||
# udp: true
|
|
||||||
# sni: example.com # aka server name
|
|
||||||
# alpn:
|
|
||||||
# - h2
|
|
||||||
# - http/1.1
|
|
||||||
# skip-cert-verify: true
|
|
||||||
|
|
||||||
- name: trojan-grpc
|
|
||||||
server: server
|
|
||||||
port: 443
|
|
||||||
type: trojan
|
|
||||||
password: "example"
|
|
||||||
network: grpc
|
|
||||||
sni: example.com
|
|
||||||
# skip-cert-verify: true
|
|
||||||
# fingerprint: xxxx
|
|
||||||
udp: true
|
|
||||||
grpc-opts:
|
|
||||||
grpc-service-name: "example"
|
|
||||||
|
|
||||||
- name: trojan-ws
|
|
||||||
server: server
|
|
||||||
port: 443
|
|
||||||
type: trojan
|
|
||||||
password: "example"
|
|
||||||
network: ws
|
|
||||||
sni: example.com
|
|
||||||
# skip-cert-verify: true
|
|
||||||
# fingerprint: xxxx
|
|
||||||
udp: true
|
|
||||||
# ws-opts:
|
|
||||||
# path: /path
|
|
||||||
# headers:
|
|
||||||
# Host: example.com
|
|
||||||
|
|
||||||
- name: "trojan-xtls"
|
|
||||||
type: trojan
|
|
||||||
server: server
|
|
||||||
port: 443
|
|
||||||
password: yourpsk
|
|
||||||
flow: "xtls-rprx-direct" # xtls-rprx-origin xtls-rprx-direct
|
|
||||||
flow-show: true
|
|
||||||
# udp: true
|
|
||||||
# sni: example.com # aka server name
|
|
||||||
# skip-cert-verify: true
|
|
||||||
# fingerprint: xxxx
|
|
||||||
|
|
||||||
#hysteria
|
#hysteria
|
||||||
- name: "hysteria"
|
- name: "hysteria"
|
||||||
type: hysteria
|
type: hysteria
|
||||||
@ -572,8 +532,7 @@ proxies: # socks5
|
|||||||
# disable_mtu_discovery: false
|
# disable_mtu_discovery: false
|
||||||
# fingerprint: xxxx
|
# fingerprint: xxxx
|
||||||
# fast-open: true # 支持 TCP 快速打开,默认为 false
|
# fast-open: true # 支持 TCP 快速打开,默认为 false
|
||||||
|
|
||||||
# wireguard
|
|
||||||
- name: "wg"
|
- name: "wg"
|
||||||
type: wireguard
|
type: wireguard
|
||||||
server: 162.159.192.1
|
server: 162.159.192.1
|
||||||
@ -583,11 +542,7 @@ proxies: # socks5
|
|||||||
private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
|
private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
|
||||||
public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
|
public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
|
||||||
udp: true
|
udp: true
|
||||||
reserved: "U4An"
|
# reserved: 'U4An'
|
||||||
# 数组格式也是合法的
|
|
||||||
# reserved: [209,98,59]
|
|
||||||
|
|
||||||
# tuic
|
|
||||||
- name: tuic
|
- name: tuic
|
||||||
server: www.example.com
|
server: www.example.com
|
||||||
port: 10443
|
port: 10443
|
||||||
@ -596,17 +551,16 @@ proxies: # socks5
|
|||||||
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
|
# ip: 127.0.0.1 # for overwriting the DNS lookup result of the server address set in option 'server'
|
||||||
# heartbeat-interval: 10000
|
# heartbeat-interval: 10000
|
||||||
# alpn: [h3]
|
# alpn: [h3]
|
||||||
disable-sni: true
|
# disable-sni: true
|
||||||
reduce-rtt: true
|
reduce-rtt: true
|
||||||
request-timeout: 8000
|
# request-timeout: 8000
|
||||||
udp-relay-mode: native # Available: "native", "quic". Default: "native"
|
udp-relay-mode: native # Available: "native", "quic". Default: "native"
|
||||||
# congestion-controller: bbr # Available: "cubic", "new_reno", "bbr". Default: "cubic"
|
# congestion-controller: bbr # Available: "cubic", "new_reno", "bbr". Default: "cubic"
|
||||||
# max-udp-relay-packet-size: 1500
|
# max-udp-relay-packet-size: 1500
|
||||||
# fast-open: true
|
# fast-open: true
|
||||||
# skip-cert-verify: true
|
# skip-cert-verify: true
|
||||||
# max-open-streams: 20 # default 100, too many open streams may hurt performance
|
# max-open-streams: 20 # default 100, too many open streams may hurt performance
|
||||||
# sni: example.com
|
|
||||||
|
|
||||||
# ShadowsocksR
|
# ShadowsocksR
|
||||||
# The supported ciphers (encryption methods): all stream ciphers in ss
|
# The supported ciphers (encryption methods): all stream ciphers in ss
|
||||||
# The supported obfses:
|
# The supported obfses:
|
||||||
@ -627,7 +581,8 @@ proxies: # socks5
|
|||||||
# protocol-param: "#"
|
# protocol-param: "#"
|
||||||
# udp: true
|
# udp: true
|
||||||
|
|
||||||
proxy-groups: # 代理链,若落地协议支持 UDP over TCP 则可支持 UDP
|
proxy-groups:
|
||||||
|
# 代理链,若落地协议支持 UDP over TCP 则可支持 UDP
|
||||||
# Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet
|
# Traffic: clash <-> http <-> vmess <-> ss1 <-> ss2 <-> Internet
|
||||||
- name: "relay"
|
- name: "relay"
|
||||||
type: relay
|
type: relay
|
||||||
@ -636,7 +591,7 @@ proxy-groups: # 代理链,若落地协议支持 UDP over TCP 则可支持 UDP
|
|||||||
- vmess
|
- vmess
|
||||||
- ss1
|
- ss1
|
||||||
- ss2
|
- ss2
|
||||||
|
|
||||||
# url-test 将按照 url 测试结果使用延迟最低节点
|
# url-test 将按照 url 测试结果使用延迟最低节点
|
||||||
- name: "auto"
|
- name: "auto"
|
||||||
type: url-test
|
type: url-test
|
||||||
@ -648,7 +603,7 @@ proxy-groups: # 代理链,若落地协议支持 UDP over TCP 则可支持 UDP
|
|||||||
# lazy: true
|
# lazy: true
|
||||||
url: "https://cp.cloudflare.com/generate_204"
|
url: "https://cp.cloudflare.com/generate_204"
|
||||||
interval: 300
|
interval: 300
|
||||||
|
|
||||||
# fallback 将按照 url 测试结果按照节点顺序选择
|
# fallback 将按照 url 测试结果按照节点顺序选择
|
||||||
- name: "fallback-auto"
|
- name: "fallback-auto"
|
||||||
type: fallback
|
type: fallback
|
||||||
@ -658,7 +613,7 @@ proxy-groups: # 代理链,若落地协议支持 UDP over TCP 则可支持 UDP
|
|||||||
- vmess1
|
- vmess1
|
||||||
url: "https://cp.cloudflare.com/generate_204"
|
url: "https://cp.cloudflare.com/generate_204"
|
||||||
interval: 300
|
interval: 300
|
||||||
|
|
||||||
# load-balance 将按照算法随机选择节点
|
# load-balance 将按照算法随机选择节点
|
||||||
- name: "load-balance"
|
- name: "load-balance"
|
||||||
type: load-balance
|
type: load-balance
|
||||||
@ -668,8 +623,8 @@ proxy-groups: # 代理链,若落地协议支持 UDP over TCP 则可支持 UDP
|
|||||||
- vmess1
|
- vmess1
|
||||||
url: "https://cp.cloudflare.com/generate_204"
|
url: "https://cp.cloudflare.com/generate_204"
|
||||||
interval: 300
|
interval: 300
|
||||||
# strategy: consistent-hashing # 可选 round-robin 和 sticky-sessions
|
# strategy: consistent-hashing # 可选 round-robin 和 sticky-sessions
|
||||||
|
|
||||||
# select 用户自行选择节点
|
# select 用户自行选择节点
|
||||||
- name: Proxy
|
- name: Proxy
|
||||||
type: select
|
type: select
|
||||||
@ -679,7 +634,7 @@ proxy-groups: # 代理链,若落地协议支持 UDP over TCP 则可支持 UDP
|
|||||||
- ss2
|
- ss2
|
||||||
- vmess1
|
- vmess1
|
||||||
- auto
|
- auto
|
||||||
|
|
||||||
# 配置指定 interface-name 和 fwmark 的 DIRECT
|
# 配置指定 interface-name 和 fwmark 的 DIRECT
|
||||||
- name: en1
|
- name: en1
|
||||||
type: select
|
type: select
|
||||||
@ -687,7 +642,7 @@ proxy-groups: # 代理链,若落地协议支持 UDP over TCP 则可支持 UDP
|
|||||||
routing-mark: 6667
|
routing-mark: 6667
|
||||||
proxies:
|
proxies:
|
||||||
- DIRECT
|
- DIRECT
|
||||||
|
|
||||||
- name: UseProvider
|
- name: UseProvider
|
||||||
type: select
|
type: select
|
||||||
filter: "HK|TW" # 正则表达式,过滤 provider1 中节点名包含 HK 或 TW
|
filter: "HK|TW" # 正则表达式,过滤 provider1 中节点名包含 HK 或 TW
|
||||||
@ -734,8 +689,7 @@ rules:
|
|||||||
- DOMAIN-KEYWORD,google,ss1
|
- DOMAIN-KEYWORD,google,ss1
|
||||||
- IP-CIDR,1.1.1.1/32,ss1
|
- IP-CIDR,1.1.1.1/32,ss1
|
||||||
- IP-CIDR6,2409::/64,DIRECT
|
- IP-CIDR6,2409::/64,DIRECT
|
||||||
# 当满足条件是 TCP 或 UDP 流量时,使用名为 sub-rule-name1 的规则集
|
- SUB-RULE,(OR,((NETWORK,TCP),(NETWORK,UDP))),sub-rule-name1 # 当满足条件是 TCP 或 UDP 流量时,使用名为 sub-rule-name1 当规则集
|
||||||
- SUB-RULE,(OR,((NETWORK,TCP),(NETWORK,UDP))),sub-rule-name1
|
|
||||||
- SUB-RULE,(AND,((NETWORK,UDP))),sub-rule-name2
|
- SUB-RULE,(AND,((NETWORK,UDP))),sub-rule-name2
|
||||||
# 定义多个子规则集,规则将以分叉匹配,使用 SUB-RULE 使用
|
# 定义多个子规则集,规则将以分叉匹配,使用 SUB-RULE 使用
|
||||||
# google.com(not match)--> baidu.com(match)
|
# google.com(not match)--> baidu.com(match)
|
||||||
@ -762,6 +716,15 @@ sub-rules:
|
|||||||
- IP-CIDR,8.8.8.8/32,ss1
|
- IP-CIDR,8.8.8.8/32,ss1
|
||||||
- DOMAIN,dns.alidns.com,REJECT
|
- DOMAIN,dns.alidns.com,REJECT
|
||||||
|
|
||||||
|
tls:
|
||||||
|
certificate: string # 证书 PEM 格式,或者 证书的路径
|
||||||
|
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||||
|
# 自定义证书验证,将加入 Clash 证书验证中,绝大多数 TLS 相关支持,如:DNS
|
||||||
|
# 可用于自定义证书的验证
|
||||||
|
custom-certificates:
|
||||||
|
- certificate: string # 证书 PEM 格式,或者 证书的路径
|
||||||
|
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||||
|
|
||||||
# 流量入站
|
# 流量入站
|
||||||
listeners:
|
listeners:
|
||||||
- name: socks5-in-1
|
- name: socks5-in-1
|
||||||
@ -771,14 +734,14 @@ listeners:
|
|||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理
|
||||||
# udp: false # 默认 true
|
# udp: false # 默认 true
|
||||||
|
|
||||||
- name: http-in-1
|
- name: http-in-1
|
||||||
type: http
|
type: http
|
||||||
port: 10809
|
port: 10809
|
||||||
listen: 0.0.0.0
|
listen: 0.0.0.0
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||||
|
|
||||||
- name: mixed-in-1
|
- name: mixed-in-1
|
||||||
type: mixed # HTTP(S) 和 SOCKS 代理混合
|
type: mixed # HTTP(S) 和 SOCKS 代理混合
|
||||||
port: 10810
|
port: 10810
|
||||||
@ -786,14 +749,14 @@ listeners:
|
|||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||||
# udp: false # 默认 true
|
# udp: false # 默认 true
|
||||||
|
|
||||||
- name: reidr-in-1
|
- name: reidr-in-1
|
||||||
type: redir
|
type: redir
|
||||||
port: 10811
|
port: 10811
|
||||||
listen: 0.0.0.0
|
listen: 0.0.0.0
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||||
|
|
||||||
- name: tproxy-in-1
|
- name: tproxy-in-1
|
||||||
type: tproxy
|
type: tproxy
|
||||||
port: 10812
|
port: 10812
|
||||||
@ -801,7 +764,7 @@ listeners:
|
|||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||||
# udp: false # 默认 true
|
# udp: false # 默认 true
|
||||||
|
|
||||||
- name: shadowsocks-in-1
|
- name: shadowsocks-in-1
|
||||||
type: shadowsocks
|
type: shadowsocks
|
||||||
port: 10813
|
port: 10813
|
||||||
@ -810,7 +773,7 @@ listeners:
|
|||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||||
password: vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=
|
password: vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=
|
||||||
cipher: 2022-blake3-aes-256-gcm
|
cipher: 2022-blake3-aes-256-gcm
|
||||||
|
|
||||||
- name: vmess-in-1
|
- name: vmess-in-1
|
||||||
type: vmess
|
type: vmess
|
||||||
port: 10814
|
port: 10814
|
||||||
@ -821,7 +784,7 @@ listeners:
|
|||||||
- username: 1
|
- username: 1
|
||||||
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
|
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
|
||||||
alterId: 1
|
alterId: 1
|
||||||
|
|
||||||
- name: tuic-in-1
|
- name: tuic-in-1
|
||||||
type: tuic
|
type: tuic
|
||||||
port: 10815
|
port: 10815
|
||||||
@ -838,7 +801,7 @@ listeners:
|
|||||||
# alpn:
|
# alpn:
|
||||||
# - h3
|
# - h3
|
||||||
# max-udp-relay-packet-size: 1500
|
# max-udp-relay-packet-size: 1500
|
||||||
|
|
||||||
- name: tunnel-in-1
|
- name: tunnel-in-1
|
||||||
type: tunnel
|
type: tunnel
|
||||||
port: 10816
|
port: 10816
|
||||||
@ -847,7 +810,7 @@ listeners:
|
|||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错)
|
||||||
network: [tcp, udp]
|
network: [tcp, udp]
|
||||||
target: target.com
|
target: target.com
|
||||||
|
|
||||||
- name: tun-in-1
|
- name: tun-in-1
|
||||||
type: tun
|
type: tun
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
@ -863,25 +826,25 @@ listeners:
|
|||||||
inet6-address: # 必须手动设置ipv6地址段
|
inet6-address: # 必须手动设置ipv6地址段
|
||||||
- "fdfe:dcba:9877::1/126"
|
- "fdfe:dcba:9877::1/126"
|
||||||
# strict_route: true # 将所有连接路由到tun来防止泄漏,但你的设备将无法其他设备被访问
|
# strict_route: true # 将所有连接路由到tun来防止泄漏,但你的设备将无法其他设备被访问
|
||||||
# inet4_route_address: # 启用 auto_route 时使用自定义路由而不是默认路由
|
# inet4_route_address: # 启用 auto_route 时使用自定义路由而不是默认路由
|
||||||
# - 0.0.0.0/1
|
# - 0.0.0.0/1
|
||||||
# - 128.0.0.0/1
|
# - 128.0.0.0/1
|
||||||
# inet6_route_address: # 启用 auto_route 时使用自定义路由而不是默认路由
|
# inet6_route_address: # 启用 auto_route 时使用自定义路由而不是默认路由
|
||||||
# - "::/1"
|
# - "::/1"
|
||||||
# - "8000::/1"
|
# - "8000::/1"
|
||||||
# endpoint_independent_nat: false # 启用独立于端点的 NAT
|
# endpoint_independent_nat: false # 启用独立于端点的 NAT
|
||||||
# include_uid: # UID 规则仅在 Linux 下被支持,并且需要 auto_route
|
# include_uid: # UID 规则仅在 Linux 下被支持,并且需要 auto_route
|
||||||
# - 0
|
# - 0
|
||||||
# include_uid_range: # 限制被路由的的用户范围
|
# include_uid_range: # 限制被路由的的用户范围
|
||||||
# - 1000-99999
|
# - 1000-99999
|
||||||
# exclude_uid: # 排除路由的的用户
|
# exclude_uid: # 排除路由的的用户
|
||||||
# - 1000
|
#- 1000
|
||||||
# exclude_uid_range: # 排除路由的的用户范围
|
# exclude_uid_range: # 排除路由的的用户范围
|
||||||
# - 1000-99999
|
# - 1000-99999
|
||||||
|
|
||||||
# Android 用户和应用规则仅在 Android 下被支持
|
# Android 用户和应用规则仅在 Android 下被支持
|
||||||
# 并且需要 auto_route
|
# 并且需要 auto_route
|
||||||
|
|
||||||
# include_android_user: # 限制被路由的 Android 用户
|
# include_android_user: # 限制被路由的 Android 用户
|
||||||
# - 0
|
# - 0
|
||||||
# - 10
|
# - 10
|
||||||
@ -889,23 +852,3 @@ listeners:
|
|||||||
# - com.android.chrome
|
# - com.android.chrome
|
||||||
# exclude_package: # 排除被路由的 Android 应用包名
|
# exclude_package: # 排除被路由的 Android 应用包名
|
||||||
# - com.android.captiveportallogin
|
# - com.android.captiveportallogin
|
||||||
|
|
||||||
# 入口配置与 Listener 等价,传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理
|
|
||||||
# shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
|
||||||
# ss-config: ss://2022-blake3-aes-256-gcm:vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg=@:23456
|
|
||||||
# vmess-config: vmess://1:9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68@:12345
|
|
||||||
|
|
||||||
# tuic服务器入口(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理)
|
|
||||||
# tuic-server:
|
|
||||||
# enable: true
|
|
||||||
# listen: 127.0.0.1:10443
|
|
||||||
# token:
|
|
||||||
# - TOKEN
|
|
||||||
# certificate: ./server.crt
|
|
||||||
# private-key: ./server.key
|
|
||||||
# congestion-controller: bbr
|
|
||||||
# max-idle-time: 15000
|
|
||||||
# authentication-timeout: 1000
|
|
||||||
# alpn:
|
|
||||||
# - h3
|
|
||||||
# max-udp-relay-packet-size: 1500
|
|
||||||
|
39
go.mod
39
go.mod
@ -10,7 +10,7 @@ require (
|
|||||||
github.com/go-chi/chi/v5 v5.0.8
|
github.com/go-chi/chi/v5 v5.0.8
|
||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/go-chi/render v1.0.2
|
github.com/go-chi/render v1.0.2
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible
|
github.com/gofrs/uuid v4.3.1+incompatible
|
||||||
github.com/google/gopacket v1.1.19
|
github.com/google/gopacket v1.1.19
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/hashicorp/golang-lru v0.5.4
|
github.com/hashicorp/golang-lru v0.5.4
|
||||||
@ -19,40 +19,38 @@ require (
|
|||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||||
github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7
|
github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7
|
||||||
github.com/metacubex/quic-go v0.32.0
|
github.com/metacubex/quic-go v0.32.0
|
||||||
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947
|
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230202072246-e2bef5f088c7
|
||||||
github.com/metacubex/sing-tun v0.1.1-0.20230304153753-5058534177f3
|
github.com/metacubex/sing-tun v0.1.1-0.20230213124625-28d27a0c236b
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20230310035749-f7595fcae5cb
|
github.com/metacubex/sing-wireguard v0.0.0-20230213124601-d04406a109b4
|
||||||
github.com/miekg/dns v1.1.50
|
github.com/miekg/dns v1.1.50
|
||||||
github.com/mroth/weightedrand/v2 v2.0.0
|
github.com/mroth/weightedrand/v2 v2.0.0
|
||||||
github.com/oschwald/geoip2-golang v1.8.0
|
github.com/oschwald/geoip2-golang v1.8.0
|
||||||
|
github.com/refraction-networking/utls v1.2.0
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
|
||||||
github.com/sagernet/sing v0.1.8
|
github.com/sagernet/sing v0.1.7-0.20230207063819-27d2950cdbe9
|
||||||
github.com/sagernet/sing-shadowtls v0.1.0
|
github.com/sagernet/sing-vmess v0.1.1-0.20230212211128-cb4e47dd0acb
|
||||||
github.com/sagernet/sing-vmess v0.1.3-0.20230307060529-d110e81a50bc
|
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d
|
||||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
|
||||||
github.com/samber/lo v1.37.0
|
github.com/samber/lo v1.37.0
|
||||||
github.com/sirupsen/logrus v1.9.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837
|
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837
|
||||||
github.com/zhangyunhao116/fastrand v0.3.0
|
|
||||||
go.etcd.io/bbolt v1.3.6
|
go.etcd.io/bbolt v1.3.6
|
||||||
go.uber.org/atomic v1.10.0
|
go.uber.org/atomic v1.10.0
|
||||||
go.uber.org/automaxprocs v1.5.1
|
go.uber.org/automaxprocs v1.5.1
|
||||||
golang.org/x/crypto v0.6.0
|
golang.org/x/crypto v0.5.0
|
||||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db
|
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db
|
||||||
golang.org/x/net v0.7.0
|
golang.org/x/net v0.5.0
|
||||||
golang.org/x/sync v0.1.0
|
golang.org/x/sync v0.1.0
|
||||||
golang.org/x/sys v0.5.0
|
golang.org/x/sys v0.4.0
|
||||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d
|
google.golang.org/protobuf v1.28.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
lukechampine.com/blake3 v1.1.7
|
lukechampine.com/blake3 v1.1.7
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ajg/form v1.5.1 // indirect
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||||
@ -61,10 +59,10 @@ require (
|
|||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.15.15 // indirect
|
github.com/klauspost/compress v1.15.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||||
github.com/mdlayher/socket v0.4.0 // indirect
|
github.com/mdlayher/socket v0.4.0 // indirect
|
||||||
github.com/metacubex/gvisor v0.0.0-20230304153416-e2bb9c726005 // indirect
|
github.com/metacubex/gvisor v0.0.0-20230213124051-7a16c835d80e // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
@ -72,13 +70,14 @@ require (
|
|||||||
github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
|
github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-19 v0.2.0 // indirect
|
github.com/quic-go/qtls-go1-19 v0.2.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.0 // indirect
|
github.com/quic-go/qtls-go1-20 v0.1.0 // indirect
|
||||||
|
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e // indirect
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
|
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||||
golang.org/x/mod v0.7.0 // indirect
|
golang.org/x/mod v0.6.0 // indirect
|
||||||
golang.org/x/text v0.7.0 // indirect
|
golang.org/x/text v0.6.0 // indirect
|
||||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||||
golang.org/x/tools v0.5.0 // indirect
|
golang.org/x/tools v0.2.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace go.uber.org/atomic v1.10.0 => github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370
|
replace go.uber.org/atomic v1.10.0 => github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370
|
||||||
|
78
go.sum
78
go.sum
@ -2,8 +2,8 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH
|
|||||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
@ -28,8 +28,8 @@ github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
|
|||||||
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
github.com/gofrs/uuid v4.3.1+incompatible h1:0/KbAdpx3UXAx1kEOWHJeOkpbgRFGHVgv+CFIY7dBJI=
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.3.1+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
@ -67,8 +67,8 @@ github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGu
|
|||||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||||
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
|
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM=
|
||||||
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||||
@ -87,16 +87,16 @@ github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZ
|
|||||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||||
github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=
|
github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=
|
||||||
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
|
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
|
||||||
github.com/metacubex/gvisor v0.0.0-20230304153416-e2bb9c726005 h1:0TEvReK/D6YLszjGj/bdx4d7amQSjQ2X/98r4ZiUbxU=
|
github.com/metacubex/gvisor v0.0.0-20230213124051-7a16c835d80e h1:j4j2dlV2d//FAsQlRUriH6nvv36AEAhECbNy7narf1M=
|
||||||
github.com/metacubex/gvisor v0.0.0-20230304153416-e2bb9c726005/go.mod h1:wqEuzdImyqD2MCGE8CYRJXbB77oSEJeoSSXXdwKjnsE=
|
github.com/metacubex/gvisor v0.0.0-20230213124051-7a16c835d80e/go.mod h1:abc7OdNmWlhcNHz84ECEosd5ND5pnWQmD8W55p/4cuc=
|
||||||
github.com/metacubex/quic-go v0.32.0 h1:dSD8LB4MSeBuD4otd8y1DUZcRdDcEB0Ax5esPOqn2Hw=
|
github.com/metacubex/quic-go v0.32.0 h1:dSD8LB4MSeBuD4otd8y1DUZcRdDcEB0Ax5esPOqn2Hw=
|
||||||
github.com/metacubex/quic-go v0.32.0/go.mod h1:yParIzDYUd/t/pzFlDtZKhnvSqbUu0bPChlKEGmJStA=
|
github.com/metacubex/quic-go v0.32.0/go.mod h1:yParIzDYUd/t/pzFlDtZKhnvSqbUu0bPChlKEGmJStA=
|
||||||
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947 h1:NnjC2+aIiyzzvFlo+C2WzBOJdsp+HAtu18FZomqYhUE=
|
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230202072246-e2bef5f088c7 h1:MNCGIpXhxXn9ck5bxfm/cW9Nr2FGQ5cakcGK0yKZcak=
|
||||||
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230226153717-4e80da7e6947/go.mod h1:U2gwhxzqgbhKCgn2B4z3t0Cj0LpMWFl/02BGCoG421w=
|
github.com/metacubex/sing-shadowsocks v0.1.1-0.20230202072246-e2bef5f088c7/go.mod h1:8pBSYDKVxTtqUtGZyEh4ZpFJXwP6wBVVKrs6oQiOwmQ=
|
||||||
github.com/metacubex/sing-tun v0.1.1-0.20230304153753-5058534177f3 h1:oQLThm1a8E7hHmoM9XF2cO0FZPsHVynC4YXW4b3liUI=
|
github.com/metacubex/sing-tun v0.1.1-0.20230213124625-28d27a0c236b h1:ZF/oNrSCaxIFoZmFQCiUx67t9aENZjyuqw2n4zw3L2o=
|
||||||
github.com/metacubex/sing-tun v0.1.1-0.20230304153753-5058534177f3/go.mod h1:b/19bRRhwampNPV+1gVDyDsQHmuGDaplxPQkwJh1kj4=
|
github.com/metacubex/sing-tun v0.1.1-0.20230213124625-28d27a0c236b/go.mod h1:TjuaYuR/g1MaY3um89xTfRNt61FJ2IcI/m5zD8QBxw4=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20230310035749-f7595fcae5cb h1:uhvzbtOvyg2c1k1H2EeVPuPvTEjDHCq4+U0AljG40P8=
|
github.com/metacubex/sing-wireguard v0.0.0-20230213124601-d04406a109b4 h1:d96mCF/LYyC9kULd2xwcXfP0Jd8klrOngmRxuUIZg/8=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20230310035749-f7595fcae5cb/go.mod h1:7mPG9qYln+CLKBcDt7Dk4c7b3S53VzEfexMVPe6T6FM=
|
github.com/metacubex/sing-wireguard v0.0.0-20230213124601-d04406a109b4/go.mod h1:p2VpJuxRefgVMxc8cmatMGSFNvYbjMYMsXJOe7qFstw=
|
||||||
github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370 h1:UkViS4DCESAUEYgbIEQdD02hyMacFt6Dny+1MOJtNIo=
|
github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370 h1:UkViS4DCESAUEYgbIEQdD02hyMacFt6Dny+1MOJtNIo=
|
||||||
github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||||
@ -121,22 +121,22 @@ github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4
|
|||||||
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
|
github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||||
|
github.com/refraction-networking/utls v1.2.0 h1:U5f8wkij2NVinfLuJdFP3gCMwIHs+EzvhxmYdXgiapo=
|
||||||
|
github.com/refraction-networking/utls v1.2.0/go.mod h1:NPq+cVqzH7D1BeOkmOcb5O/8iVewAsiVt2x1/eO0hgQ=
|
||||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||||
|
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e h1:5CFRo8FJbCuf5s/eTBdZpmMbn8Fe2eSMLNAYfKanA34=
|
||||||
|
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e/go.mod h1:qbt0dWObotCfcjAJJ9AxtFPNSDUfZF+6dCpgKEOBn/g=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.1.8 h1:6DKo2FkSHn0nUcjO7bAext/ai7y7pCusK/+fScBJ5Jk=
|
github.com/sagernet/sing v0.1.7-0.20230207063819-27d2950cdbe9 h1:qnXh4RjHsNjdZXkfbqwVqAzYUfc160gfkS5gepmsA+A=
|
||||||
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
github.com/sagernet/sing v0.1.7-0.20230207063819-27d2950cdbe9/go.mod h1:JLSXsPTGRJFo/3X7EcAOCUgJH2/gAoxSJgBsnCZRp/w=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.0 h1:05MYce8aR5xfKIn+y7xRFsdKhKt44QZTSEQW+lG5IWQ=
|
github.com/sagernet/sing-vmess v0.1.1-0.20230212211128-cb4e47dd0acb h1:oyd3w17fXNmWVYFUe17YVHJW5CLW9X2mxJFDP/IWrAM=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.0/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
|
github.com/sagernet/sing-vmess v0.1.1-0.20230212211128-cb4e47dd0acb/go.mod h1:9KkmnQzTL4Gvv8U2TRAH2BOITCGsGPpHtUPP5sxn5sY=
|
||||||
github.com/sagernet/sing-vmess v0.1.3-0.20230307060529-d110e81a50bc h1:vqlYWupvVDRpvv2F+RtECJN+VbuKjLtmQculQvOecls=
|
|
||||||
github.com/sagernet/sing-vmess v0.1.3-0.20230307060529-d110e81a50bc/go.mod h1:V14iffGwhZPU2S7wgIiPlLWXygSjAXazYzD1w0ejBl4=
|
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d h1:trP/l6ZPWvQ/5Gv99Z7/t/v8iYy06akDMejxW1sznUk=
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d h1:trP/l6ZPWvQ/5Gv99Z7/t/v8iYy06akDMejxW1sznUk=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g=
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g=
|
||||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4=
|
|
||||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
|
||||||
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
|
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
|
||||||
@ -162,8 +162,6 @@ github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1
|
|||||||
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 h1:AHhUwwFJGl27E46OpdJHplZkK09m7aETNBNzhT6t15M=
|
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 h1:AHhUwwFJGl27E46OpdJHplZkK09m7aETNBNzhT6t15M=
|
||||||
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837/go.mod h1:YJTRELIWrGxR1s8xcEBgxcxBfwQfMGjdvNLTjN9XFgY=
|
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837/go.mod h1:YJTRELIWrGxR1s8xcEBgxcxBfwQfMGjdvNLTjN9XFgY=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/zhangyunhao116/fastrand v0.3.0 h1:7bwe124xcckPulX6fxtr2lFdO2KQqaefdtbk+mqO/Ig=
|
|
||||||
github.com/zhangyunhao116/fastrand v0.3.0/go.mod h1:0v5KgHho0VE6HU192HnY15de/oDS8UrbBChIFjIhBtc=
|
|
||||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||||
go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
|
go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
|
||||||
@ -171,15 +169,15 @@ go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
|
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
|
||||||
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
|
||||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
@ -192,8 +190,8 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
@ -222,31 +220,31 @@ golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4=
|
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
|
||||||
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
|
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d h1:qp0AnQCvRCMlu9jBjtdbTaaEmThIgZOrbVyDEOcmKhQ=
|
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||||
google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
@ -81,13 +81,13 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||||||
updateRules(cfg.Rules, cfg.SubRules, cfg.RuleProviders)
|
updateRules(cfg.Rules, cfg.SubRules, cfg.RuleProviders)
|
||||||
updateSniffer(cfg.Sniffer)
|
updateSniffer(cfg.Sniffer)
|
||||||
updateHosts(cfg.Hosts)
|
updateHosts(cfg.Hosts)
|
||||||
updateGeneral(cfg.General)
|
|
||||||
initInnerTcp()
|
initInnerTcp()
|
||||||
updateDNS(cfg.DNS, cfg.General.IPv6)
|
updateDNS(cfg.DNS, cfg.General.IPv6)
|
||||||
loadProxyProvider(cfg.Providers)
|
loadProxyProvider(cfg.Providers)
|
||||||
updateProfile(cfg)
|
updateProfile(cfg)
|
||||||
loadRuleProvider(cfg.RuleProviders)
|
loadRuleProvider(cfg.RuleProviders)
|
||||||
updateListeners(cfg.General, cfg.Listeners, force)
|
updateGeneral(cfg.General, force)
|
||||||
|
updateListeners(cfg.Listeners)
|
||||||
updateIPTables(cfg)
|
updateIPTables(cfg)
|
||||||
updateTun(cfg.General)
|
updateTun(cfg.General)
|
||||||
updateExperimental(cfg)
|
updateExperimental(cfg)
|
||||||
@ -128,40 +128,18 @@ func GetGeneral() *config.General {
|
|||||||
GeodataLoader: G.LoaderName(),
|
GeodataLoader: G.LoaderName(),
|
||||||
Interface: dialer.DefaultInterface.Load(),
|
Interface: dialer.DefaultInterface.Load(),
|
||||||
Sniffing: tunnel.IsSniffing(),
|
Sniffing: tunnel.IsSniffing(),
|
||||||
TCPConcurrent: dialer.GetTcpConcurrent(),
|
TCPConcurrent: dialer.GetDial(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return general
|
return general
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateListeners(general *config.General, listeners map[string]C.InboundListener, force bool) {
|
func updateListeners(listeners map[string]C.InboundListener) {
|
||||||
tcpIn := tunnel.TCPIn()
|
tcpIn := tunnel.TCPIn()
|
||||||
udpIn := tunnel.UDPIn()
|
udpIn := tunnel.UDPIn()
|
||||||
natTable := tunnel.NatTable()
|
natTable := tunnel.NatTable()
|
||||||
|
|
||||||
listener.PatchInboundListeners(listeners, tcpIn, udpIn, natTable, true)
|
listener.PatchInboundListeners(listeners, tcpIn, udpIn, natTable, true)
|
||||||
if !force {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if general.Interface == "" && (!general.Tun.Enable || !general.Tun.AutoDetectInterface) {
|
|
||||||
dialer.DefaultInterface.Store(general.Interface)
|
|
||||||
}
|
|
||||||
|
|
||||||
allowLan := general.AllowLan
|
|
||||||
listener.SetAllowLan(allowLan)
|
|
||||||
|
|
||||||
bindAddress := general.BindAddress
|
|
||||||
listener.SetBindAddress(bindAddress)
|
|
||||||
listener.ReCreateHTTP(general.Port, tcpIn)
|
|
||||||
listener.ReCreateSocks(general.SocksPort, tcpIn, udpIn)
|
|
||||||
listener.ReCreateRedir(general.RedirPort, tcpIn, udpIn, natTable)
|
|
||||||
listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tcpIn, udpIn)
|
|
||||||
listener.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn, natTable)
|
|
||||||
listener.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
|
|
||||||
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tcpIn, udpIn)
|
|
||||||
listener.ReCreateVmess(general.VmessConfig, tcpIn, udpIn)
|
|
||||||
listener.ReCreateTuic(LC.TuicServer(general.TuicServer), tcpIn, udpIn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateExperimental(c *config.Config) {
|
func updateExperimental(c *config.Config) {
|
||||||
@ -169,11 +147,9 @@ func updateExperimental(c *config.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func preUpdateExperimental(c *config.Config) {
|
func preUpdateExperimental(c *config.Config) {
|
||||||
CTLS.ResetCertificate()
|
CTLS.AddCertificate(c.TLS.PrivateKey, c.TLS.Certificate)
|
||||||
for _, c := range c.TLS.CustomTrustCert {
|
for _, c := range c.TLS.CustomTrustCert {
|
||||||
if err := CTLS.AddCertificate(c); err != nil {
|
CTLS.AddCertificate(c.PrivateKey, c.Certificate)
|
||||||
log.Warnln("%s\nadd error: %s", c, err.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +166,6 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
|
|||||||
Main: c.NameServer,
|
Main: c.NameServer,
|
||||||
Fallback: c.Fallback,
|
Fallback: c.Fallback,
|
||||||
IPv6: c.IPv6 && generalIPv6,
|
IPv6: c.IPv6 && generalIPv6,
|
||||||
IPv6Timeout: c.IPv6Timeout,
|
|
||||||
EnhancedMode: c.EnhancedMode,
|
EnhancedMode: c.EnhancedMode,
|
||||||
Pool: c.FakeIPRange,
|
Pool: c.FakeIPRange,
|
||||||
Hosts: c.Hosts,
|
Hosts: c.Hosts,
|
||||||
@ -226,8 +201,8 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
|
|||||||
dns.ReCreateServer(c.Listen, r, m)
|
dns.ReCreateServer(c.Listen, r, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateHosts(tree *trie.DomainTrie[resolver.HostValue]) {
|
func updateHosts(tree *trie.DomainTrie[netip.Addr]) {
|
||||||
resolver.DefaultHosts = resolver.NewHosts(tree)
|
resolver.DefaultHosts = tree
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {
|
func updateProxies(proxies map[string]C.Proxy, providers map[string]provider.ProxyProvider) {
|
||||||
@ -329,28 +304,25 @@ func updateTunnels(tunnels []LC.Tunnel) {
|
|||||||
listener.PatchTunnel(tunnels, tunnel.TCPIn(), tunnel.UDPIn())
|
listener.PatchTunnel(tunnels, tunnel.TCPIn(), tunnel.UDPIn())
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateGeneral(general *config.General) {
|
func updateGeneral(general *config.General, force bool) {
|
||||||
tunnel.SetMode(general.Mode)
|
tunnel.SetMode(general.Mode)
|
||||||
tunnel.SetFindProcessMode(general.FindProcessMode)
|
tunnel.SetFindProcessMode(general.FindProcessMode)
|
||||||
resolver.DisableIPv6 = !general.IPv6
|
dialer.DisableIPv6 = !general.IPv6
|
||||||
|
if !dialer.DisableIPv6 {
|
||||||
|
log.Infoln("Use IPv6")
|
||||||
|
}
|
||||||
|
resolver.DisableIPv6 = dialer.DisableIPv6
|
||||||
|
|
||||||
if general.TCPConcurrent {
|
if general.TCPConcurrent {
|
||||||
dialer.SetTcpConcurrent(general.TCPConcurrent)
|
dialer.SetDial(general.TCPConcurrent)
|
||||||
log.Infoln("Use tcp concurrent")
|
log.Infoln("Use tcp concurrent")
|
||||||
}
|
}
|
||||||
|
|
||||||
inbound.SetTfo(general.InboundTfo)
|
|
||||||
|
|
||||||
adapter.UnifiedDelay.Store(general.UnifiedDelay)
|
adapter.UnifiedDelay.Store(general.UnifiedDelay)
|
||||||
// Avoid reload configuration clean the value, causing traffic loops
|
dialer.DefaultInterface.Store(general.Interface)
|
||||||
if listener.GetTunConf().Enable && listener.GetTunConf().AutoDetectInterface {
|
|
||||||
// changed only when the name is specified
|
if dialer.DefaultInterface.Load() != "" {
|
||||||
// if name is empty, setting delay until after tun loaded
|
log.Infoln("Use interface name: %s", general.Interface)
|
||||||
if general.Interface != "" && (!general.Tun.Enable || !general.Tun.AutoDetectInterface) {
|
|
||||||
dialer.DefaultInterface.Store(general.Interface)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dialer.DefaultInterface.Store(general.Interface)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dialer.DefaultRoutingMark.Store(int32(general.RoutingMark))
|
dialer.DefaultRoutingMark.Store(int32(general.RoutingMark))
|
||||||
@ -359,8 +331,35 @@ func updateGeneral(general *config.General) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
iface.FlushCache()
|
iface.FlushCache()
|
||||||
|
|
||||||
|
if !force {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
geodataLoader := general.GeodataLoader
|
geodataLoader := general.GeodataLoader
|
||||||
G.SetLoader(geodataLoader)
|
G.SetLoader(geodataLoader)
|
||||||
|
|
||||||
|
allowLan := general.AllowLan
|
||||||
|
listener.SetAllowLan(allowLan)
|
||||||
|
|
||||||
|
bindAddress := general.BindAddress
|
||||||
|
listener.SetBindAddress(bindAddress)
|
||||||
|
|
||||||
|
inbound.SetTfo(general.InboundTfo)
|
||||||
|
|
||||||
|
tcpIn := tunnel.TCPIn()
|
||||||
|
udpIn := tunnel.UDPIn()
|
||||||
|
natTable := tunnel.NatTable()
|
||||||
|
|
||||||
|
listener.ReCreateHTTP(general.Port, tcpIn)
|
||||||
|
listener.ReCreateSocks(general.SocksPort, tcpIn, udpIn)
|
||||||
|
listener.ReCreateRedir(general.RedirPort, tcpIn, udpIn, natTable)
|
||||||
|
listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tcpIn, udpIn)
|
||||||
|
listener.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn, natTable)
|
||||||
|
listener.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
|
||||||
|
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tcpIn, udpIn)
|
||||||
|
listener.ReCreateVmess(general.VmessConfig, tcpIn, udpIn)
|
||||||
|
listener.ReCreateTuic(LC.TuicServer(general.TuicServer), tcpIn, udpIn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUsers(users []auth.AuthUser) {
|
func updateUsers(users []auth.AuthUser) {
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/config"
|
"github.com/Dreamacro/clash/config"
|
||||||
"github.com/Dreamacro/clash/hub/executor"
|
"github.com/Dreamacro/clash/hub/executor"
|
||||||
"github.com/Dreamacro/clash/hub/route"
|
"github.com/Dreamacro/clash/hub/route"
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Option func(*config.Config)
|
type Option func(*config.Config)
|
||||||
@ -44,7 +43,7 @@ func Parse(options ...Option) error {
|
|||||||
|
|
||||||
if cfg.General.ExternalController != "" {
|
if cfg.General.ExternalController != "" {
|
||||||
go route.Start(cfg.General.ExternalController, cfg.General.ExternalControllerTLS,
|
go route.Start(cfg.General.ExternalController, cfg.General.ExternalControllerTLS,
|
||||||
cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey, cfg.General.LogLevel == log.DEBUG)
|
cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
executor.ApplyConfig(cfg, true)
|
executor.ApplyConfig(cfg, true)
|
||||||
|
@ -228,7 +228,7 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if general.TcpConcurrent != nil {
|
if general.TcpConcurrent != nil {
|
||||||
dialer.SetTcpConcurrent(*general.TcpConcurrent)
|
dialer.SetDial(*general.TcpConcurrent)
|
||||||
}
|
}
|
||||||
|
|
||||||
if general.InterfaceName != nil {
|
if general.InterfaceName != nil {
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime/debug"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -14,8 +13,8 @@ import (
|
|||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/Dreamacro/clash/tunnel/statistic"
|
"github.com/Dreamacro/clash/tunnel/statistic"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
@ -44,7 +43,7 @@ func SetUIPath(path string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Start(addr string, tlsAddr string, secret string,
|
func Start(addr string, tlsAddr string, secret string,
|
||||||
certificat, privateKey string, isDebug bool) {
|
certificat, privateKey string) {
|
||||||
if serverAddr != "" {
|
if serverAddr != "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -60,17 +59,6 @@ func Start(addr string, tlsAddr string, secret string,
|
|||||||
MaxAge: 300,
|
MaxAge: 300,
|
||||||
})
|
})
|
||||||
r.Use(corsM.Handler)
|
r.Use(corsM.Handler)
|
||||||
if isDebug {
|
|
||||||
r.Mount("/debug", func() http.Handler {
|
|
||||||
r := chi.NewRouter()
|
|
||||||
r.Put("/gc", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
debug.FreeOSMemory()
|
|
||||||
})
|
|
||||||
handler := middleware.Profiler
|
|
||||||
r.Mount("/", handler())
|
|
||||||
return r
|
|
||||||
}())
|
|
||||||
}
|
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(authentication)
|
r.Use(authentication)
|
||||||
r.Get("/", hello)
|
r.Get("/", hello)
|
||||||
|
41
listener/sing/log.go
Normal file
41
listener/sing/log.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package sing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
|
L "github.com/sagernet/sing/common/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type logger struct{}
|
||||||
|
|
||||||
|
func (l logger) Trace(args ...any) {
|
||||||
|
log.Debugln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Debug(args ...any) {
|
||||||
|
log.Debugln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Info(args ...any) {
|
||||||
|
log.Infoln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Warn(args ...any) {
|
||||||
|
log.Warnln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Error(args ...any) {
|
||||||
|
log.Errorln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Fatal(args ...any) {
|
||||||
|
log.Fatalln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l logger) Panic(args ...any) {
|
||||||
|
log.Fatalln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
var Logger L.Logger = logger{}
|
@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/adapter/inbound"
|
"github.com/Dreamacro/clash/adapter/inbound"
|
||||||
"github.com/Dreamacro/clash/common/sockopt"
|
"github.com/Dreamacro/clash/common/sockopt"
|
||||||
@ -64,7 +63,7 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C
|
|||||||
case common.Contains(shadowaead.List, config.Cipher):
|
case common.Contains(shadowaead.List, config.Cipher):
|
||||||
sl.service, err = shadowaead.NewService(config.Cipher, nil, config.Password, udpTimeout, h)
|
sl.service, err = shadowaead.NewService(config.Cipher, nil, config.Password, udpTimeout, h)
|
||||||
case common.Contains(shadowaead_2022.List, config.Cipher):
|
case common.Contains(shadowaead_2022.List, config.Cipher):
|
||||||
sl.service, err = shadowaead_2022.NewServiceWithPassword(config.Cipher, config.Password, udpTimeout, h, time.Now)
|
sl.service, err = shadowaead_2022.NewServiceWithPassword(config.Cipher, config.Password, udpTimeout, h)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("shadowsocks: unsupported method: %s", config.Cipher)
|
err = fmt.Errorf("shadowsocks: unsupported method: %s", config.Cipher)
|
||||||
return embedSS.New(config, tcpIn, udpIn)
|
return embedSS.New(config, tcpIn, udpIn)
|
||||||
|
@ -67,26 +67,6 @@ func CalculateInterfaceName(name string) (tunName string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkTunName(tunName string) (ok bool) {
|
|
||||||
defer func() {
|
|
||||||
if !ok {
|
|
||||||
log.Warnln("[TUN] Unsupported tunName(%s) in %s, force regenerate by ourselves.", tunName, runtime.GOOS)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
if len(tunName) <= 4 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if tunName[:4] != "utun" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if _, parseErr := strconv.ParseInt(tunName[4:], 10, 16); parseErr != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, additions ...inbound.Addition) (l *Listener, err error) {
|
func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, additions ...inbound.Addition) (l *Listener, err error) {
|
||||||
if len(additions) == 0 {
|
if len(additions) == 0 {
|
||||||
additions = []inbound.Addition{
|
additions = []inbound.Addition{
|
||||||
@ -95,7 +75,7 @@ func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
tunName := options.Device
|
tunName := options.Device
|
||||||
if tunName == "" || !checkTunName(tunName) {
|
if tunName == "" {
|
||||||
tunName = CalculateInterfaceName(InterfaceName)
|
tunName = CalculateInterfaceName(InterfaceName)
|
||||||
options.Device = tunName
|
options.Device = tunName
|
||||||
}
|
}
|
||||||
@ -221,7 +201,7 @@ func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapte
|
|||||||
err = E.Cause(err, "build android rules")
|
err = E.Cause(err, "build android rules")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tunIf, err := tunNew(tunOptions)
|
tunIf, err := tunOpen(tunOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = E.Cause(err, "configure tun interface")
|
err = E.Cause(err, "configure tun interface")
|
||||||
return
|
return
|
||||||
@ -237,7 +217,7 @@ func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapte
|
|||||||
EndpointIndependentNat: options.EndpointIndependentNat,
|
EndpointIndependentNat: options.EndpointIndependentNat,
|
||||||
UDPTimeout: udpTimeout,
|
UDPTimeout: udpTimeout,
|
||||||
Handler: handler,
|
Handler: handler,
|
||||||
Logger: log.SingLogger,
|
Logger: sing.Logger,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -6,6 +6,6 @@ import (
|
|||||||
tun "github.com/metacubex/sing-tun"
|
tun "github.com/metacubex/sing-tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
func tunNew(options tun.Options) (tun.Tun, error) {
|
func tunOpen(options tun.Options) (tun.Tun, error) {
|
||||||
return tun.New(options)
|
return tun.Open(options)
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,11 @@ import (
|
|||||||
tun "github.com/metacubex/sing-tun"
|
tun "github.com/metacubex/sing-tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
func tunNew(options tun.Options) (tunIf tun.Tun, err error) {
|
func tunOpen(options tun.Options) (tunIf tun.Tun, err error) {
|
||||||
maxRetry := 3
|
maxRetry := 3
|
||||||
for i := 0; i < maxRetry; i++ {
|
for i := 0; i < maxRetry; i++ {
|
||||||
timeBegin := time.Now()
|
timeBegin := time.Now()
|
||||||
tunIf, err = tun.New(options)
|
tunIf, err = tun.Open(options)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ func (l *Listener) handleTCP(conn net.Conn, in chan<- C.ConnContext, additions .
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New(addr, target, proxy string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) {
|
func New(addr, target, proxy string, in chan<- C.ConnContext, additions ...inbound.Addition) (*Listener, error) {
|
||||||
l, err := inbound.Listen("tcp", addr)
|
l, err := net.Listen("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
68
log/sing.go
68
log/sing.go
@ -1,68 +0,0 @@
|
|||||||
package log
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
L "github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type singLogger struct{}
|
|
||||||
|
|
||||||
func (l singLogger) TraceContext(ctx context.Context, args ...any) {
|
|
||||||
Debugln(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l singLogger) DebugContext(ctx context.Context, args ...any) {
|
|
||||||
Debugln(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l singLogger) InfoContext(ctx context.Context, args ...any) {
|
|
||||||
Infoln(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l singLogger) WarnContext(ctx context.Context, args ...any) {
|
|
||||||
Warnln(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l singLogger) ErrorContext(ctx context.Context, args ...any) {
|
|
||||||
Errorln(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l singLogger) FatalContext(ctx context.Context, args ...any) {
|
|
||||||
Fatalln(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l singLogger) PanicContext(ctx context.Context, args ...any) {
|
|
||||||
Fatalln(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l singLogger) Trace(args ...any) {
|
|
||||||
Debugln(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l singLogger) Debug(args ...any) {
|
|
||||||
Debugln(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l singLogger) Info(args ...any) {
|
|
||||||
Infoln(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l singLogger) Warn(args ...any) {
|
|
||||||
Warnln(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l singLogger) Error(args ...any) {
|
|
||||||
Errorln(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l singLogger) Fatal(args ...any) {
|
|
||||||
Fatalln(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l singLogger) Panic(args ...any) {
|
|
||||||
Fatalln(fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
var SingLogger L.ContextLogger = singLogger{}
|
|
@ -20,7 +20,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/common/buf"
|
"github.com/Dreamacro/clash/common/buf"
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
|
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
@ -190,7 +189,7 @@ func (g *Conn) SetDeadline(t time.Time) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, realityConfig *tlsC.RealityConfig) *TransportWrap {
|
func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string) *TransportWrap {
|
||||||
wrap := TransportWrap{}
|
wrap := TransportWrap{}
|
||||||
|
|
||||||
dialFunc := func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
dialFunc := func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||||
@ -202,37 +201,20 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re
|
|||||||
wrap.remoteAddr = pconn.RemoteAddr()
|
wrap.remoteAddr = pconn.RemoteAddr()
|
||||||
|
|
||||||
if len(Fingerprint) != 0 {
|
if len(Fingerprint) != 0 {
|
||||||
if realityConfig == nil {
|
if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists {
|
||||||
if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists {
|
utlsConn := tlsC.UClient(pconn, cfg, fingerprint)
|
||||||
utlsConn := tlsC.UClient(pconn, cfg, fingerprint)
|
if err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx); err != nil {
|
||||||
if err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx); err != nil {
|
|
||||||
pconn.Close()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
state := utlsConn.(*tlsC.UConn).ConnectionState()
|
|
||||||
if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
|
|
||||||
utlsConn.Close()
|
|
||||||
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
|
|
||||||
}
|
|
||||||
return utlsConn, nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
realityConn, err := tlsC.GetRealityConn(ctx, pconn, Fingerprint, cfg, realityConfig)
|
|
||||||
if err != nil {
|
|
||||||
pconn.Close()
|
pconn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
//state := realityConn.(*utls.UConn).ConnectionState()
|
state := utlsConn.(*tlsC.UConn).ConnectionState()
|
||||||
//if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
|
if p := state.NegotiatedProtocol; p != http2.NextProtoTLS {
|
||||||
// realityConn.Close()
|
utlsConn.Close()
|
||||||
// return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
|
return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS)
|
||||||
//}
|
}
|
||||||
return realityConn, nil
|
return utlsConn, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if realityConfig != nil {
|
|
||||||
return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := tls.Client(pconn, cfg)
|
conn := tls.Client(pconn, cfg)
|
||||||
if err := conn.HandshakeContext(ctx); err != nil {
|
if err := conn.HandshakeContext(ctx); err != nil {
|
||||||
@ -292,11 +274,11 @@ func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, er
|
|||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func StreamGunWithConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config, realityConfig *tlsC.RealityConfig) (net.Conn, error) {
|
func StreamGunWithConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config) (net.Conn, error) {
|
||||||
dialFn := func(network, addr string) (net.Conn, error) {
|
dialFn := func(network, addr string) (net.Conn, error) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
transport := NewHTTP2Client(dialFn, tlsConfig, cfg.ClientFingerprint, realityConfig)
|
transport := NewHTTP2Client(dialFn, tlsConfig, cfg.ClientFingerprint)
|
||||||
return StreamGunWithTransport(transport, cfg)
|
return StreamGunWithTransport(transport, cfg)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package udp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -11,8 +12,6 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/transport/hysteria/obfs"
|
"github.com/Dreamacro/clash/transport/hysteria/obfs"
|
||||||
"github.com/Dreamacro/clash/transport/hysteria/utils"
|
"github.com/Dreamacro/clash/transport/hysteria/utils"
|
||||||
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -86,7 +85,7 @@ func NewObfsUDPHopClientPacketConn(server string, serverPorts string, hopInterva
|
|||||||
serverAddrs: serverAddrs,
|
serverAddrs: serverAddrs,
|
||||||
hopInterval: hopInterval,
|
hopInterval: hopInterval,
|
||||||
obfs: obfs,
|
obfs: obfs,
|
||||||
addrIndex: fastrand.Intn(len(serverAddrs)),
|
addrIndex: rand.Intn(len(serverAddrs)),
|
||||||
recvQueue: make(chan *udpPacket, packetQueueSize),
|
recvQueue: make(chan *udpPacket, packetQueueSize),
|
||||||
closeChan: make(chan struct{}),
|
closeChan: make(chan struct{}),
|
||||||
bufPool: sync.Pool{
|
bufPool: sync.Pool{
|
||||||
@ -177,7 +176,7 @@ func (c *ObfsUDPHopClientPacketConn) hop(dialer utils.PacketDialer, rAddr net.Ad
|
|||||||
_ = trySetPacketConnWriteBuffer(c.currentConn, c.writeBufferSize)
|
_ = trySetPacketConnWriteBuffer(c.currentConn, c.writeBufferSize)
|
||||||
}
|
}
|
||||||
go c.recvRoutine(c.currentConn)
|
go c.recvRoutine(c.currentConn)
|
||||||
c.addrIndex = fastrand.Intn(len(c.serverAddrs))
|
c.addrIndex = rand.Intn(len(c.serverAddrs))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ObfsUDPHopClientPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
func (c *ObfsUDPHopClientPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
|
@ -2,14 +2,12 @@ package wechat
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
"github.com/Dreamacro/clash/transport/hysteria/obfs"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
"github.com/Dreamacro/clash/transport/hysteria/obfs"
|
|
||||||
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const udpBufferSize = 65535
|
const udpBufferSize = 65535
|
||||||
@ -31,7 +29,7 @@ func NewObfsWeChatUDPConn(orig net.PacketConn, obfs obfs.Obfuscator) *ObfsWeChat
|
|||||||
obfs: obfs,
|
obfs: obfs,
|
||||||
readBuf: make([]byte, udpBufferSize),
|
readBuf: make([]byte, udpBufferSize),
|
||||||
writeBuf: make([]byte, udpBufferSize),
|
writeBuf: make([]byte, udpBufferSize),
|
||||||
sn: fastrand.Uint32() & 0xFFFF,
|
sn: rand.Uint32() & 0xFFFF,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,20 +6,20 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lunixbochs/struc"
|
||||||
|
"github.com/metacubex/quic-go"
|
||||||
|
"github.com/metacubex/quic-go/congestion"
|
||||||
|
|
||||||
"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"
|
"github.com/Dreamacro/clash/transport/hysteria/utils"
|
||||||
|
|
||||||
"github.com/lunixbochs/struc"
|
|
||||||
"github.com/metacubex/quic-go"
|
|
||||||
"github.com/metacubex/quic-go/congestion"
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -408,7 +408,7 @@ func (c *quicPktConn) WriteTo(p []byte, addr string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
if errSize, ok := err.(quic.ErrMessageTooLarge); ok {
|
if errSize, ok := err.(quic.ErrMessageTooLarge); ok {
|
||||||
// need to frag
|
// need to frag
|
||||||
msg.MsgID = uint16(fastrand.Intn(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
|
msg.MsgID = uint16(rand.Intn(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
|
||||||
fragMsgs := fragUDPMessage(msg, int(errSize))
|
fragMsgs := fragUDPMessage(msg, int(errSize))
|
||||||
for _, fragMsg := range fragMsgs {
|
for _, fragMsg := range fragMsgs {
|
||||||
msgBuf.Reset()
|
msgBuf.Reset()
|
||||||
|
@ -2,8 +2,9 @@ package obfs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
"math/rand"
|
||||||
"github.com/zhangyunhao116/fastrand"
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// [salt][obfuscated payload]
|
// [salt][obfuscated payload]
|
||||||
@ -11,12 +12,16 @@ import (
|
|||||||
const saltLen = 16
|
const saltLen = 16
|
||||||
|
|
||||||
type XPlusObfuscator struct {
|
type XPlusObfuscator struct {
|
||||||
Key []byte
|
Key []byte
|
||||||
|
RandSrc *rand.Rand
|
||||||
|
|
||||||
|
lk sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewXPlusObfuscator(key []byte) *XPlusObfuscator {
|
func NewXPlusObfuscator(key []byte) *XPlusObfuscator {
|
||||||
return &XPlusObfuscator{
|
return &XPlusObfuscator{
|
||||||
Key: key,
|
Key: key,
|
||||||
|
RandSrc: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +40,9 @@ func (x *XPlusObfuscator) Deobfuscate(in []byte, out []byte) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (x *XPlusObfuscator) Obfuscate(in []byte, out []byte) int {
|
func (x *XPlusObfuscator) Obfuscate(in []byte, out []byte) int {
|
||||||
_, _ = fastrand.Read(out[:saltLen]) // salt
|
x.lk.Lock()
|
||||||
|
_, _ = x.RandSrc.Read(out[:saltLen]) // salt
|
||||||
|
x.lk.Unlock()
|
||||||
// Obfuscate the payload
|
// Obfuscate the payload
|
||||||
key := sha256.Sum256(append(x.Key, out[:saltLen]...))
|
key := sha256.Sum256(append(x.Key, out[:saltLen]...))
|
||||||
for i, c := range in {
|
for i, c := range in {
|
||||||
|
@ -5,12 +5,11 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTPObfs is shadowsocks http simple-obfs implementation
|
// HTTPObfs is shadowsocks http simple-obfs implementation
|
||||||
@ -64,9 +63,9 @@ func (ho *HTTPObfs) Read(b []byte) (int, error) {
|
|||||||
func (ho *HTTPObfs) Write(b []byte) (int, error) {
|
func (ho *HTTPObfs) Write(b []byte) (int, error) {
|
||||||
if ho.firstRequest {
|
if ho.firstRequest {
|
||||||
randBytes := make([]byte, 16)
|
randBytes := make([]byte, 16)
|
||||||
fastrand.Read(randBytes)
|
rand.Read(randBytes)
|
||||||
req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:]))
|
req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s/", ho.host), bytes.NewBuffer(b[:]))
|
||||||
req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", fastrand.Int()%54, fastrand.Int()%2))
|
req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2))
|
||||||
req.Header.Set("Upgrade", "websocket")
|
req.Header.Set("Upgrade", "websocket")
|
||||||
req.Header.Set("Connection", "Upgrade")
|
req.Header.Set("Connection", "Upgrade")
|
||||||
req.Host = ho.host
|
req.Host = ho.host
|
||||||
|
@ -4,14 +4,17 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().Unix())
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024
|
chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024
|
||||||
)
|
)
|
||||||
@ -127,8 +130,8 @@ func NewTLSObfs(conn net.Conn, server string) net.Conn {
|
|||||||
func makeClientHelloMsg(data []byte, server string) []byte {
|
func makeClientHelloMsg(data []byte, server string) []byte {
|
||||||
random := make([]byte, 28)
|
random := make([]byte, 28)
|
||||||
sessionID := make([]byte, 32)
|
sessionID := make([]byte, 32)
|
||||||
fastrand.Read(random)
|
rand.Read(random)
|
||||||
fastrand.Read(sessionID)
|
rand.Read(sessionID)
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
package sing_shadowtls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-shadowtls"
|
|
||||||
sing_common "github.com/sagernet/sing/common"
|
|
||||||
utls "github.com/sagernet/utls"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Mode string = "shadow-tls"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
DefaultALPN = []string{"h2", "http/1.1"}
|
|
||||||
)
|
|
||||||
|
|
||||||
type ShadowTLSOption struct {
|
|
||||||
Password string
|
|
||||||
Host string
|
|
||||||
Fingerprint string
|
|
||||||
ClientFingerprint string
|
|
||||||
SkipCertVerify bool
|
|
||||||
Version int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewShadowTLS(ctx context.Context, conn net.Conn, option *ShadowTLSOption) (net.Conn, error) {
|
|
||||||
tlsConfig := &tls.Config{
|
|
||||||
NextProtos: DefaultALPN,
|
|
||||||
MinVersion: tls.VersionTLS12,
|
|
||||||
InsecureSkipVerify: option.SkipCertVerify,
|
|
||||||
ServerName: option.Host,
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if len(option.Fingerprint) == 0 {
|
|
||||||
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
|
|
||||||
} else {
|
|
||||||
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsHandshake := shadowtls.DefaultTLSHandshakeFunc(option.Password, tlsConfig)
|
|
||||||
if len(option.ClientFingerprint) != 0 {
|
|
||||||
if fingerprint, exists := tlsC.GetFingerprint(option.ClientFingerprint); exists {
|
|
||||||
tlsHandshake = uTLSHandshakeFunc(tlsConfig, *fingerprint.ClientHelloID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client, err := shadowtls.NewClient(shadowtls.ClientConfig{
|
|
||||||
Version: option.Version,
|
|
||||||
Password: option.Password,
|
|
||||||
TLSHandshake: tlsHandshake,
|
|
||||||
Logger: log.SingLogger,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return client.DialContextConn(ctx, conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func uTLSHandshakeFunc(config *tls.Config, clientHelloID utls.ClientHelloID) shadowtls.TLSHandshakeFunc {
|
|
||||||
return func(ctx context.Context, conn net.Conn, sessionIDGenerator shadowtls.TLSSessionIDGeneratorFunc) error {
|
|
||||||
tlsConfig := &utls.Config{
|
|
||||||
Rand: config.Rand,
|
|
||||||
Time: config.Time,
|
|
||||||
VerifyPeerCertificate: config.VerifyPeerCertificate,
|
|
||||||
RootCAs: config.RootCAs,
|
|
||||||
NextProtos: config.NextProtos,
|
|
||||||
ServerName: config.ServerName,
|
|
||||||
InsecureSkipVerify: config.InsecureSkipVerify,
|
|
||||||
CipherSuites: config.CipherSuites,
|
|
||||||
MinVersion: config.MinVersion,
|
|
||||||
MaxVersion: config.MaxVersion,
|
|
||||||
CurvePreferences: sing_common.Map(config.CurvePreferences, func(it tls.CurveID) utls.CurveID {
|
|
||||||
return utls.CurveID(it)
|
|
||||||
}),
|
|
||||||
SessionTicketsDisabled: config.SessionTicketsDisabled,
|
|
||||||
Renegotiation: utls.RenegotiationSupport(config.Renegotiation),
|
|
||||||
SessionIDGenerator: sessionIDGenerator,
|
|
||||||
}
|
|
||||||
tlsConn := utls.UClient(conn, tlsConfig, clientHelloID)
|
|
||||||
return tlsConn.HandshakeContext(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,13 +4,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"io"
|
"io"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -82,7 +81,7 @@ func (c *httpConn) Write(b []byte) (int, error) {
|
|||||||
bLength := len(b)
|
bLength := len(b)
|
||||||
headDataLength := bLength
|
headDataLength := bLength
|
||||||
if bLength-headLength > 64 {
|
if bLength-headLength > 64 {
|
||||||
headDataLength = headLength + fastrand.Intn(65)
|
headDataLength = headLength + rand.Intn(65)
|
||||||
}
|
}
|
||||||
headData := b[:headDataLength]
|
headData := b[:headDataLength]
|
||||||
b = b[headDataLength:]
|
b = b[headDataLength:]
|
||||||
@ -100,7 +99,7 @@ func (c *httpConn) Write(b []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
hosts := strings.Split(host, ",")
|
hosts := strings.Split(host, ",")
|
||||||
host = hosts[fastrand.Intn(len(hosts))]
|
host = hosts[rand.Intn(len(hosts))]
|
||||||
|
|
||||||
buf := pool.GetBuffer()
|
buf := pool.GetBuffer()
|
||||||
defer pool.PutBuffer(buf)
|
defer pool.PutBuffer(buf)
|
||||||
@ -119,7 +118,7 @@ func (c *httpConn) Write(b []byte) (int, error) {
|
|||||||
buf.WriteString(body + "\r\n\r\n")
|
buf.WriteString(body + "\r\n\r\n")
|
||||||
} else {
|
} else {
|
||||||
buf.WriteString("User-Agent: ")
|
buf.WriteString("User-Agent: ")
|
||||||
buf.WriteString(userAgent[fastrand.Intn(len(userAgent))])
|
buf.WriteString(userAgent[rand.Intn(len(userAgent))])
|
||||||
buf.WriteString("\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Encoding: gzip, deflate\r\n")
|
buf.WriteString("\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Encoding: gzip, deflate\r\n")
|
||||||
if c.post {
|
if c.post {
|
||||||
packBoundary(buf)
|
packBoundary(buf)
|
||||||
@ -147,7 +146,7 @@ func packBoundary(buf *bytes.Buffer) {
|
|||||||
buf.WriteString("Content-Type: multipart/form-data; boundary=")
|
buf.WriteString("Content-Type: multipart/form-data; boundary=")
|
||||||
set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
set := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
for i := 0; i < 32; i++ {
|
for i := 0; i < 32; i++ {
|
||||||
buf.WriteByte(set[fastrand.Intn(62)])
|
buf.WriteByte(set[rand.Intn(62)])
|
||||||
}
|
}
|
||||||
buf.WriteString("\r\n")
|
buf.WriteString("\r\n")
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,10 @@ package obfs
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -54,10 +53,10 @@ func (c *randomHeadConn) Write(b []byte) (int, error) {
|
|||||||
c.buf = append(c.buf, b...)
|
c.buf = append(c.buf, b...)
|
||||||
if !c.hasSentHeader {
|
if !c.hasSentHeader {
|
||||||
c.hasSentHeader = true
|
c.hasSentHeader = true
|
||||||
dataLength := fastrand.Intn(96) + 4
|
dataLength := rand.Intn(96) + 4
|
||||||
buf := pool.Get(dataLength + 4)
|
buf := pool.Get(dataLength + 4)
|
||||||
defer pool.Put(buf)
|
defer pool.Put(buf)
|
||||||
fastrand.Read(buf[:dataLength])
|
rand.Read(buf[:dataLength])
|
||||||
binary.LittleEndian.PutUint32(buf[dataLength:], 0xffffffff-crc32.ChecksumIEEE(buf[:dataLength]))
|
binary.LittleEndian.PutUint32(buf[dataLength:], 0xffffffff-crc32.ChecksumIEEE(buf[:dataLength]))
|
||||||
_, err := c.Conn.Write(buf)
|
_, err := c.Conn.Write(buf)
|
||||||
return len(b), err
|
return len(b), err
|
||||||
|
@ -4,14 +4,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/transport/ssr/tools"
|
"github.com/Dreamacro/clash/transport/ssr/tools"
|
||||||
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -26,7 +25,7 @@ type tls12Ticket struct {
|
|||||||
|
|
||||||
func newTLS12Ticket(b *Base) Obfs {
|
func newTLS12Ticket(b *Base) Obfs {
|
||||||
r := &tls12Ticket{Base: b, authData: &authData{}}
|
r := &tls12Ticket{Base: b, authData: &authData{}}
|
||||||
fastrand.Read(r.clientID[:])
|
rand.Read(r.clientID[:])
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +90,7 @@ func (c *tls12TicketConn) Write(b []byte) (int, error) {
|
|||||||
buf := pool.GetBuffer()
|
buf := pool.GetBuffer()
|
||||||
defer pool.PutBuffer(buf)
|
defer pool.PutBuffer(buf)
|
||||||
for len(b) > 2048 {
|
for len(b) > 2048 {
|
||||||
size := fastrand.Intn(4096) + 100
|
size := rand.Intn(4096) + 100
|
||||||
if len(b) < size {
|
if len(b) < size {
|
||||||
size = len(b)
|
size = len(b)
|
||||||
}
|
}
|
||||||
@ -197,7 +196,7 @@ func packSNIData(buf *bytes.Buffer, u string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *tls12TicketConn) packTicketBuf(buf *bytes.Buffer, u string) {
|
func (c *tls12TicketConn) packTicketBuf(buf *bytes.Buffer, u string) {
|
||||||
length := 16 * (fastrand.Intn(17) + 8)
|
length := 16 * (rand.Intn(17) + 8)
|
||||||
buf.Write([]byte{0, 0x23})
|
buf.Write([]byte{0, 0x23})
|
||||||
binary.Write(buf, binary.BigEndian, uint16(length))
|
binary.Write(buf, binary.BigEndian, uint16(length))
|
||||||
tools.AppendRandBytes(buf, length)
|
tools.AppendRandBytes(buf, length)
|
||||||
@ -222,6 +221,6 @@ func (t *tls12Ticket) getHost() string {
|
|||||||
host = ""
|
host = ""
|
||||||
}
|
}
|
||||||
hosts := strings.Split(host, ",")
|
hosts := strings.Split(host, ",")
|
||||||
host = hosts[fastrand.Intn(len(hosts))]
|
host = hosts[rand.Intn(len(hosts))]
|
||||||
return host
|
return host
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"math"
|
"math"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -11,8 +12,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/Dreamacro/clash/transport/ssr/tools"
|
"github.com/Dreamacro/clash/transport/ssr/tools"
|
||||||
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -65,7 +64,7 @@ func (a *authAES128) initUserData() {
|
|||||||
}
|
}
|
||||||
if len(a.userKey) == 0 {
|
if len(a.userKey) == 0 {
|
||||||
a.userKey = a.Key
|
a.userKey = a.Key
|
||||||
fastrand.Read(a.userID[:])
|
rand.Read(a.userID[:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,7 +198,7 @@ func (a *authAES128) packData(poolBuf *bytes.Buffer, data []byte, fullDataLength
|
|||||||
}
|
}
|
||||||
|
|
||||||
func trapezoidRandom(max int, d float64) int {
|
func trapezoidRandom(max int, d float64) int {
|
||||||
base := fastrand.Float64()
|
base := rand.Float64()
|
||||||
if d-0 > 1e-6 {
|
if d-0 > 1e-6 {
|
||||||
a := 1 - d
|
a := 1 - d
|
||||||
base = (math.Sqrt(a*a+4*d*base) - a) / (2 * d)
|
base = (math.Sqrt(a*a+4*d*base) - a) / (2 * d)
|
||||||
@ -220,10 +219,10 @@ func (a *authAES128) getRandDataLengthForPackData(dataLength, fullDataLength int
|
|||||||
if revLength > -1460 {
|
if revLength > -1460 {
|
||||||
return trapezoidRandom(revLength+1460, -0.3)
|
return trapezoidRandom(revLength+1460, -0.3)
|
||||||
}
|
}
|
||||||
return fastrand.Intn(32)
|
return rand.Intn(32)
|
||||||
}
|
}
|
||||||
if dataLength > 900 {
|
if dataLength > 900 {
|
||||||
return fastrand.Intn(revLength)
|
return rand.Intn(revLength)
|
||||||
}
|
}
|
||||||
return trapezoidRandom(revLength, -0.3)
|
return trapezoidRandom(revLength, -0.3)
|
||||||
}
|
}
|
||||||
@ -248,7 +247,7 @@ func (a *authAES128) packAuthData(poolBuf *bytes.Buffer, data []byte) {
|
|||||||
copy(macKey, a.iv)
|
copy(macKey, a.iv)
|
||||||
copy(macKey[len(a.iv):], a.Key)
|
copy(macKey[len(a.iv):], a.Key)
|
||||||
|
|
||||||
poolBuf.WriteByte(byte(fastrand.Intn(256)))
|
poolBuf.WriteByte(byte(rand.Intn(256)))
|
||||||
poolBuf.Write(a.hmac(macKey, poolBuf.Bytes())[:6])
|
poolBuf.Write(a.hmac(macKey, poolBuf.Bytes())[:6])
|
||||||
poolBuf.Write(a.userID[:])
|
poolBuf.Write(a.userID[:])
|
||||||
err := a.authData.putEncryptedData(poolBuf, a.userKey, [2]int{packedAuthDataLength, randDataLength}, a.salt)
|
err := a.authData.putEncryptedData(poolBuf, a.userKey, [2]int{packedAuthDataLength, randDataLength}, a.salt)
|
||||||
@ -264,9 +263,9 @@ func (a *authAES128) packAuthData(poolBuf *bytes.Buffer, data []byte) {
|
|||||||
|
|
||||||
func (a *authAES128) getRandDataLengthForPackAuthData(size int) int {
|
func (a *authAES128) getRandDataLengthForPackAuthData(size int) int {
|
||||||
if size > 400 {
|
if size > 400 {
|
||||||
return fastrand.Intn(512)
|
return rand.Intn(512)
|
||||||
}
|
}
|
||||||
return fastrand.Intn(1024)
|
return rand.Intn(1024)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authAES128) packRandData(poolBuf *bytes.Buffer, size int) {
|
func (a *authAES128) packRandData(poolBuf *bytes.Buffer, size int) {
|
||||||
|
@ -5,12 +5,11 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"hash/adler32"
|
"hash/adler32"
|
||||||
"hash/crc32"
|
"hash/crc32"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/transport/ssr/tools"
|
"github.com/Dreamacro/clash/transport/ssr/tools"
|
||||||
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -177,7 +176,7 @@ func (a *authSHA1V4) getRandDataLength(size int) int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
if size > 400 {
|
if size > 400 {
|
||||||
return fastrand.Intn(256)
|
return rand.Intn(256)
|
||||||
}
|
}
|
||||||
return fastrand.Intn(512)
|
return rand.Intn(512)
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,13 @@ import (
|
|||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"math/rand"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
"github.com/Dreamacro/clash/transport/shadowsocks/core"
|
"github.com/Dreamacro/clash/transport/shadowsocks/core"
|
||||||
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Base struct {
|
type Base struct {
|
||||||
@ -38,8 +37,8 @@ func (a *authData) next() *authData {
|
|||||||
a.mutex.Lock()
|
a.mutex.Lock()
|
||||||
defer a.mutex.Unlock()
|
defer a.mutex.Unlock()
|
||||||
if a.connectionID > 0xff000000 || a.connectionID == 0 {
|
if a.connectionID > 0xff000000 || a.connectionID == 0 {
|
||||||
fastrand.Read(a.clientID[:])
|
rand.Read(a.clientID[:])
|
||||||
a.connectionID = fastrand.Uint32() & 0xffffff
|
a.connectionID = rand.Uint32() & 0xffffff
|
||||||
}
|
}
|
||||||
a.connectionID++
|
a.connectionID++
|
||||||
copy(r.clientID[:], a.clientID[:])
|
copy(r.clientID[:], a.clientID[:])
|
||||||
|
@ -4,9 +4,8 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -69,7 +68,7 @@ func getHeadSize(b []byte, defaultValue int) int {
|
|||||||
|
|
||||||
func getDataLength(b []byte) int {
|
func getDataLength(b []byte) int {
|
||||||
bLength := len(b)
|
bLength := len(b)
|
||||||
dataLength := getHeadSize(b, 30) + fastrand.Intn(32)
|
dataLength := getHeadSize(b, 30) + rand.Intn(32)
|
||||||
if bLength < dataLength {
|
if bLength < dataLength {
|
||||||
return bLength
|
return bLength
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import (
|
|||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
"github.com/Dreamacro/clash/transport/vless"
|
"github.com/Dreamacro/clash/transport/vless"
|
||||||
"github.com/Dreamacro/clash/transport/vmess"
|
"github.com/Dreamacro/clash/transport/vmess"
|
||||||
|
|
||||||
xtls "github.com/xtls/go"
|
xtls "github.com/xtls/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -55,7 +54,6 @@ type Option struct {
|
|||||||
Flow string
|
Flow string
|
||||||
FlowShow bool
|
FlowShow bool
|
||||||
ClientFingerprint string
|
ClientFingerprint string
|
||||||
Reality *tlsC.RealityConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebsocketOption struct {
|
type WebsocketOption struct {
|
||||||
@ -119,24 +117,16 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(t.option.ClientFingerprint) != 0 {
|
if len(t.option.ClientFingerprint) != 0 {
|
||||||
if t.option.Reality == nil {
|
utlsConn, valid := vmess.GetUtlsConnWithClientFingerprint(conn, t.option.ClientFingerprint, tlsConfig)
|
||||||
utlsConn, valid := vmess.GetUTLSConn(conn, t.option.ClientFingerprint, tlsConfig)
|
if valid {
|
||||||
if valid {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
|
||||||
return utlsConn, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
return tlsC.GetRealityConn(ctx, conn, t.option.ClientFingerprint, tlsConfig, t.option.Reality)
|
|
||||||
|
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
||||||
|
return utlsConn, err
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t.option.Reality != nil {
|
|
||||||
return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConn := tls.Client(conn, tlsConfig)
|
tlsConn := tls.Client(conn, tlsConfig)
|
||||||
|
|
||||||
|
@ -6,19 +6,19 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/metacubex/quic-go"
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
"github.com/Dreamacro/clash/log"
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
"github.com/metacubex/quic-go"
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -352,7 +352,7 @@ func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Met
|
|||||||
pipe1, pipe2 := net.Pipe()
|
pipe1, pipe2 := net.Pipe()
|
||||||
var connId uint32
|
var connId uint32
|
||||||
for {
|
for {
|
||||||
connId = fastrand.Uint32()
|
connId = rand.Uint32()
|
||||||
_, loaded := t.udpInputMap.LoadOrStore(connId, pipe1)
|
_, loaded := t.udpInputMap.LoadOrStore(connId, pipe1)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
break
|
break
|
||||||
|
@ -5,11 +5,11 @@ package congestion
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/metacubex/quic-go/congestion"
|
"github.com/metacubex/quic-go/congestion"
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -780,7 +780,7 @@ func (b *bbrSender) EnterProbeBandwidthMode(now time.Time) {
|
|||||||
// Pick a random offset for the gain cycle out of {0, 2..7} range. 1 is
|
// Pick a random offset for the gain cycle out of {0, 2..7} range. 1 is
|
||||||
// excluded because in that case increased gain and decreased gain would not
|
// excluded because in that case increased gain and decreased gain would not
|
||||||
// follow each other.
|
// follow each other.
|
||||||
b.cycleCurrentOffset = fastrand.Int() % (GainCycleLength - 1)
|
b.cycleCurrentOffset = rand.Int() % (GainCycleLength - 1)
|
||||||
if b.cycleCurrentOffset >= 1 {
|
if b.cycleCurrentOffset >= 1 {
|
||||||
b.cycleCurrentOffset += 1
|
b.cycleCurrentOffset += 1
|
||||||
}
|
}
|
||||||
|
@ -11,14 +11,13 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/common/pool"
|
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
|
||||||
"github.com/Dreamacro/clash/transport/socks5"
|
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
"github.com/metacubex/quic-go"
|
"github.com/metacubex/quic-go"
|
||||||
|
|
||||||
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
|
"github.com/Dreamacro/clash/common/pool"
|
||||||
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerOption struct {
|
type ServerOption struct {
|
||||||
@ -56,7 +55,7 @@ func (s *Server) Serve() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
SetCongestionController(conn, s.CongestionController)
|
SetCongestionController(conn, s.CongestionController)
|
||||||
uuid, err := utils.UnsafeUUIDGenerator.NewV4()
|
uuid, err := uuid.NewV4()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -76,7 +75,7 @@ func (s *Server) Close() error {
|
|||||||
|
|
||||||
type serverHandler struct {
|
type serverHandler struct {
|
||||||
*Server
|
*Server
|
||||||
quicConn quic.EarlyConnection
|
quicConn quic.Connection
|
||||||
uuid uuid.UUID
|
uuid uuid.UUID
|
||||||
|
|
||||||
authCh chan struct{}
|
authCh chan struct{}
|
||||||
@ -87,6 +86,13 @@ type serverHandler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *serverHandler) handle() {
|
func (s *serverHandler) handle() {
|
||||||
|
time.AfterFunc(s.AuthenticationTimeout, func() {
|
||||||
|
s.authOnce.Do(func() {
|
||||||
|
_ = s.quicConn.CloseWithError(AuthenticationTimeout, "")
|
||||||
|
s.authOk = false
|
||||||
|
close(s.authCh)
|
||||||
|
})
|
||||||
|
})
|
||||||
go func() {
|
go func() {
|
||||||
_ = s.handleUniStream()
|
_ = s.handleUniStream()
|
||||||
}()
|
}()
|
||||||
@ -96,15 +102,6 @@ func (s *serverHandler) handle() {
|
|||||||
go func() {
|
go func() {
|
||||||
_ = s.handleMessage()
|
_ = s.handleMessage()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
<-s.quicConn.HandshakeComplete().Done()
|
|
||||||
time.AfterFunc(s.AuthenticationTimeout, func() {
|
|
||||||
s.authOnce.Do(func() {
|
|
||||||
_ = s.quicConn.CloseWithError(AuthenticationTimeout, "AuthenticationTimeout")
|
|
||||||
s.authOk = false
|
|
||||||
close(s.authCh)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serverHandler) handleMessage() (err error) {
|
func (s *serverHandler) handleMessage() (err error) {
|
||||||
@ -241,7 +238,7 @@ func (s *serverHandler) handleUniStream() (err error) {
|
|||||||
}
|
}
|
||||||
s.authOnce.Do(func() {
|
s.authOnce.Do(func() {
|
||||||
if !ok {
|
if !ok {
|
||||||
_ = s.quicConn.CloseWithError(AuthenticationFailed, "AuthenticationFailed")
|
_ = s.quicConn.CloseWithError(AuthenticationFailed, "")
|
||||||
}
|
}
|
||||||
s.authOk = ok
|
s.authOk = ok
|
||||||
close(s.authCh)
|
close(s.authCh)
|
||||||
|
@ -1,298 +1,105 @@
|
|||||||
package vless
|
package vless
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/subtle"
|
|
||||||
gotls "crypto/tls"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"reflect"
|
|
||||||
"sync"
|
"sync"
|
||||||
"unsafe"
|
"time"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/buf"
|
"github.com/Dreamacro/clash/common/buf"
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
utls "github.com/sagernet/utls"
|
|
||||||
xtls "github.com/xtls/go"
|
xtls "github.com/xtls/go"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
N.ExtendedWriter
|
N.ExtendedConn
|
||||||
N.ExtendedReader
|
|
||||||
net.Conn
|
|
||||||
dst *DstAddr
|
dst *DstAddr
|
||||||
id *uuid.UUID
|
id *uuid.UUID
|
||||||
addons *Addons
|
addons *Addons
|
||||||
received bool
|
received bool
|
||||||
|
|
||||||
|
handshake chan struct{}
|
||||||
handshakeMutex sync.Mutex
|
handshakeMutex sync.Mutex
|
||||||
needHandshake bool
|
|
||||||
err error
|
err error
|
||||||
|
|
||||||
tlsConn net.Conn
|
|
||||||
input *bytes.Reader
|
|
||||||
rawInput *bytes.Buffer
|
|
||||||
|
|
||||||
packetsToFilter int
|
|
||||||
isTLS bool
|
|
||||||
isTLS12orAbove bool
|
|
||||||
enableXTLS bool
|
|
||||||
cipher uint16
|
|
||||||
remainingServerHello uint16
|
|
||||||
readRemainingContent int
|
|
||||||
readRemainingPadding int
|
|
||||||
readProcess bool
|
|
||||||
readFilterUUID bool
|
|
||||||
readLastCommand byte
|
|
||||||
writeFilterApplicationData bool
|
|
||||||
writeDirect bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vc *Conn) Read(b []byte) (int, error) {
|
func (vc *Conn) Read(b []byte) (int, error) {
|
||||||
if vc.received {
|
if vc.received {
|
||||||
if vc.readProcess {
|
return vc.ExtendedConn.Read(b)
|
||||||
buffer := buf.With(b)
|
|
||||||
err := vc.ReadBuffer(buffer)
|
|
||||||
return buffer.Len(), err
|
|
||||||
}
|
|
||||||
return vc.ExtendedReader.Read(b)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := vc.recvResponse(); err != nil {
|
if err := vc.recvResponse(); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
vc.received = true
|
vc.received = true
|
||||||
return vc.Read(b)
|
return vc.ExtendedConn.Read(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error {
|
func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error {
|
||||||
if vc.received {
|
if vc.received {
|
||||||
toRead := buffer.FreeBytes()
|
return vc.ExtendedConn.ReadBuffer(buffer)
|
||||||
if vc.readRemainingContent > 0 {
|
|
||||||
if vc.readRemainingContent < buffer.FreeLen() {
|
|
||||||
toRead = toRead[:vc.readRemainingContent]
|
|
||||||
}
|
|
||||||
n, err := vc.ExtendedReader.Read(toRead)
|
|
||||||
buffer.Truncate(n)
|
|
||||||
vc.readRemainingContent -= n
|
|
||||||
vc.FilterTLS(toRead)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if vc.readRemainingPadding > 0 {
|
|
||||||
_, err := io.CopyN(io.Discard, vc.ExtendedReader, int64(vc.readRemainingPadding))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
vc.readRemainingPadding = 0
|
|
||||||
}
|
|
||||||
if vc.readProcess {
|
|
||||||
switch vc.readLastCommand {
|
|
||||||
case commandPaddingContinue:
|
|
||||||
//if vc.isTLS || vc.packetsToFilter > 0 {
|
|
||||||
headerUUIDLen := 0
|
|
||||||
if vc.readFilterUUID {
|
|
||||||
headerUUIDLen = uuid.Size
|
|
||||||
}
|
|
||||||
var header []byte
|
|
||||||
if need := headerUUIDLen + paddingHeaderLen; buffer.FreeLen() < need {
|
|
||||||
header = make([]byte, need)
|
|
||||||
} else {
|
|
||||||
header = buffer.FreeBytes()[:need]
|
|
||||||
}
|
|
||||||
_, err := io.ReadFull(vc.ExtendedReader, header)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pos := 0
|
|
||||||
if vc.readFilterUUID {
|
|
||||||
vc.readFilterUUID = false
|
|
||||||
pos = uuid.Size
|
|
||||||
if subtle.ConstantTimeCompare(vc.id.Bytes(), header[:uuid.Size]) != 1 {
|
|
||||||
err = fmt.Errorf("XTLS Vision server responded unknown UUID: %s",
|
|
||||||
uuid.FromBytesOrNil(header[:uuid.Size]).String())
|
|
||||||
log.Errorln(err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vc.readLastCommand = header[pos]
|
|
||||||
vc.readRemainingContent = int(binary.BigEndian.Uint16(header[pos+1:]))
|
|
||||||
vc.readRemainingPadding = int(binary.BigEndian.Uint16(header[pos+3:]))
|
|
||||||
log.Debugln("XTLS Vision read padding: command=%d, payloadLen=%d, paddingLen=%d",
|
|
||||||
vc.readLastCommand, vc.readRemainingContent, vc.readRemainingPadding)
|
|
||||||
return vc.ReadBuffer(buffer)
|
|
||||||
//}
|
|
||||||
case commandPaddingEnd:
|
|
||||||
vc.readProcess = false
|
|
||||||
return vc.ReadBuffer(buffer)
|
|
||||||
case commandPaddingDirect:
|
|
||||||
needReturn := false
|
|
||||||
if vc.input != nil {
|
|
||||||
_, err := buffer.ReadFrom(vc.input)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if vc.input.Len() == 0 {
|
|
||||||
needReturn = true
|
|
||||||
vc.input = nil
|
|
||||||
} else { // buffer is full
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if vc.rawInput != nil {
|
|
||||||
_, err := buffer.ReadFrom(vc.rawInput)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
needReturn = true
|
|
||||||
if vc.rawInput.Len() == 0 {
|
|
||||||
vc.rawInput = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if vc.input == nil && vc.rawInput == nil {
|
|
||||||
vc.readProcess = false
|
|
||||||
vc.ExtendedReader = N.NewExtendedReader(vc.Conn)
|
|
||||||
log.Debugln("XTLS Vision direct read start")
|
|
||||||
}
|
|
||||||
if needReturn {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("XTLS Vision read unknown command: %d", vc.readLastCommand)
|
|
||||||
log.Debugln(err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vc.ExtendedReader.ReadBuffer(buffer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := vc.recvResponse(); err != nil {
|
if err := vc.recvResponse(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
vc.received = true
|
vc.received = true
|
||||||
return vc.ReadBuffer(buffer)
|
return vc.ExtendedConn.ReadBuffer(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vc *Conn) Write(p []byte) (int, error) {
|
func (vc *Conn) Write(p []byte) (int, error) {
|
||||||
if vc.needHandshake {
|
select {
|
||||||
vc.handshakeMutex.Lock()
|
case <-vc.handshake:
|
||||||
if vc.needHandshake {
|
default:
|
||||||
vc.needHandshake = false
|
if vc.sendRequest(p) {
|
||||||
if vc.sendRequest(p) {
|
|
||||||
vc.handshakeMutex.Unlock()
|
|
||||||
if vc.err != nil {
|
|
||||||
return 0, vc.err
|
|
||||||
}
|
|
||||||
return len(p), vc.err
|
|
||||||
}
|
|
||||||
if vc.err != nil {
|
if vc.err != nil {
|
||||||
vc.handshakeMutex.Unlock()
|
|
||||||
return 0, vc.err
|
return 0, vc.err
|
||||||
}
|
}
|
||||||
|
return len(p), vc.err
|
||||||
}
|
}
|
||||||
vc.handshakeMutex.Unlock()
|
if vc.err != nil {
|
||||||
}
|
return 0, vc.err
|
||||||
|
|
||||||
if vc.writeFilterApplicationData {
|
|
||||||
_buffer := buf.StackNew()
|
|
||||||
defer buf.KeepAlive(_buffer)
|
|
||||||
buffer := buf.Dup(_buffer)
|
|
||||||
defer buffer.Release()
|
|
||||||
buffer.Write(p)
|
|
||||||
err := vc.WriteBuffer(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
}
|
||||||
return len(p), nil
|
|
||||||
}
|
}
|
||||||
return vc.ExtendedWriter.Write(p)
|
return vc.ExtendedConn.Write(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vc *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
func (vc *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
if vc.needHandshake {
|
select {
|
||||||
vc.handshakeMutex.Lock()
|
case <-vc.handshake:
|
||||||
if vc.needHandshake {
|
default:
|
||||||
vc.needHandshake = false
|
if vc.sendRequest(buffer.Bytes()) {
|
||||||
if vc.sendRequest(buffer.Bytes()) {
|
return vc.err
|
||||||
vc.handshakeMutex.Unlock()
|
}
|
||||||
return vc.err
|
if vc.err != nil {
|
||||||
}
|
return vc.err
|
||||||
if vc.err != nil {
|
|
||||||
vc.handshakeMutex.Unlock()
|
|
||||||
return vc.err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
vc.handshakeMutex.Unlock()
|
|
||||||
}
|
}
|
||||||
|
return vc.ExtendedConn.WriteBuffer(buffer)
|
||||||
if vc.writeFilterApplicationData {
|
|
||||||
buffer2 := ReshapeBuffer(buffer)
|
|
||||||
defer buffer2.Release()
|
|
||||||
vc.FilterTLS(buffer.Bytes())
|
|
||||||
command := commandPaddingContinue
|
|
||||||
if !vc.isTLS {
|
|
||||||
command = commandPaddingEnd
|
|
||||||
|
|
||||||
// disable XTLS
|
|
||||||
//vc.readProcess = false
|
|
||||||
vc.writeFilterApplicationData = false
|
|
||||||
vc.packetsToFilter = 0
|
|
||||||
} else if buffer.Len() > 6 && bytes.Equal(buffer.To(3), tlsApplicationDataStart) || vc.packetsToFilter <= 0 {
|
|
||||||
command = commandPaddingEnd
|
|
||||||
if vc.enableXTLS {
|
|
||||||
command = commandPaddingDirect
|
|
||||||
vc.writeDirect = true
|
|
||||||
}
|
|
||||||
vc.writeFilterApplicationData = false
|
|
||||||
}
|
|
||||||
ApplyPadding(buffer, command, nil, vc.isTLS)
|
|
||||||
err := vc.ExtendedWriter.WriteBuffer(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if vc.writeDirect {
|
|
||||||
vc.ExtendedWriter = N.NewExtendedWriter(vc.Conn)
|
|
||||||
log.Debugln("XTLS Vision direct write start")
|
|
||||||
//time.Sleep(5 * time.Millisecond)
|
|
||||||
}
|
|
||||||
if buffer2 != nil {
|
|
||||||
if vc.writeDirect || !vc.isTLS {
|
|
||||||
return vc.ExtendedWriter.WriteBuffer(buffer2)
|
|
||||||
}
|
|
||||||
vc.FilterTLS(buffer2.Bytes())
|
|
||||||
command = commandPaddingContinue
|
|
||||||
if buffer2.Len() > 6 && bytes.Equal(buffer2.To(3), tlsApplicationDataStart) || vc.packetsToFilter <= 0 {
|
|
||||||
command = commandPaddingEnd
|
|
||||||
if vc.enableXTLS {
|
|
||||||
command = commandPaddingDirect
|
|
||||||
vc.writeDirect = true
|
|
||||||
}
|
|
||||||
vc.writeFilterApplicationData = false
|
|
||||||
}
|
|
||||||
ApplyPadding(buffer2, command, nil, vc.isTLS)
|
|
||||||
err = vc.ExtendedWriter.WriteBuffer(buffer2)
|
|
||||||
if vc.writeDirect {
|
|
||||||
vc.ExtendedWriter = N.NewExtendedWriter(vc.Conn)
|
|
||||||
log.Debugln("XTLS Vision direct write start")
|
|
||||||
//time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
/*if vc.writeDirect {
|
|
||||||
log.Debugln("XTLS Vision Direct write, payloadLen=%d", buffer.Len())
|
|
||||||
}*/
|
|
||||||
return vc.ExtendedWriter.WriteBuffer(buffer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vc *Conn) sendRequest(p []byte) bool {
|
func (vc *Conn) sendRequest(p []byte) bool {
|
||||||
|
vc.handshakeMutex.Lock()
|
||||||
|
defer vc.handshakeMutex.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-vc.handshake:
|
||||||
|
// The handshake has been completed before.
|
||||||
|
// So return false to remind the caller.
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
defer close(vc.handshake)
|
||||||
|
|
||||||
|
requestLen := 1 // protocol version
|
||||||
|
requestLen += 16 // UUID
|
||||||
|
requestLen += 1 // addons length
|
||||||
var addonsBytes []byte
|
var addonsBytes []byte
|
||||||
if vc.addons != nil {
|
if vc.addons != nil {
|
||||||
addonsBytes, vc.err = proto.Marshal(vc.addons)
|
addonsBytes, vc.err = proto.Marshal(vc.addons)
|
||||||
@ -300,32 +107,19 @@ func (vc *Conn) sendRequest(p []byte) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
isVision := vc.IsXTLSVisionEnabled()
|
requestLen += len(addonsBytes)
|
||||||
|
requestLen += 1 // command
|
||||||
var buffer *buf.Buffer
|
if !vc.dst.Mux {
|
||||||
if isVision {
|
requestLen += 2 // port
|
||||||
_buffer := buf.StackNew()
|
requestLen += 1 // addr type
|
||||||
defer buf.KeepAlive(_buffer)
|
requestLen += len(vc.dst.Addr)
|
||||||
buffer = buf.Dup(_buffer)
|
|
||||||
defer buffer.Release()
|
|
||||||
} else {
|
|
||||||
requestLen := 1 // protocol version
|
|
||||||
requestLen += 16 // UUID
|
|
||||||
requestLen += 1 // addons length
|
|
||||||
requestLen += len(addonsBytes)
|
|
||||||
requestLen += 1 // command
|
|
||||||
if !vc.dst.Mux {
|
|
||||||
requestLen += 2 // port
|
|
||||||
requestLen += 1 // addr type
|
|
||||||
requestLen += len(vc.dst.Addr)
|
|
||||||
}
|
|
||||||
requestLen += len(p)
|
|
||||||
|
|
||||||
_buffer := buf.StackNewSize(requestLen)
|
|
||||||
defer buf.KeepAlive(_buffer)
|
|
||||||
buffer = buf.Dup(_buffer)
|
|
||||||
defer buffer.Release()
|
|
||||||
}
|
}
|
||||||
|
requestLen += len(p)
|
||||||
|
|
||||||
|
_buffer := buf.StackNewSize(requestLen)
|
||||||
|
defer buf.KeepAlive(_buffer)
|
||||||
|
buffer := buf.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
|
||||||
buf.Must(
|
buf.Must(
|
||||||
buffer.WriteByte(Version), // protocol version
|
buffer.WriteByte(Version), // protocol version
|
||||||
@ -350,103 +144,47 @@ func (vc *Conn) sendRequest(p []byte) bool {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isVision && !vc.dst.UDP && !vc.dst.Mux {
|
buf.Must(buf.Error(buffer.Write(p)))
|
||||||
if len(p) == 0 {
|
|
||||||
WriteWithPadding(buffer, nil, commandPaddingContinue, vc.id, vc.isTLS)
|
|
||||||
} else {
|
|
||||||
vc.FilterTLS(p)
|
|
||||||
//if vc.isTLS {
|
|
||||||
WriteWithPadding(buffer, p, commandPaddingContinue, vc.id, vc.isTLS)
|
|
||||||
//} else {
|
|
||||||
// buf.Must(buf.Error(buffer.Write(p)))
|
|
||||||
//
|
|
||||||
// // disable XTLS
|
|
||||||
// vc.readProcess = false
|
|
||||||
// vc.writeFilterApplicationData = false
|
|
||||||
// vc.packetsToFilter = 0
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
buf.Must(buf.Error(buffer.Write(p)))
|
|
||||||
}
|
|
||||||
|
|
||||||
_, vc.err = vc.ExtendedWriter.Write(buffer.Bytes())
|
_, vc.err = vc.ExtendedConn.Write(buffer.Bytes())
|
||||||
if vc.err != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if isVision {
|
|
||||||
switch underlying := vc.tlsConn.(type) {
|
|
||||||
case *gotls.Conn:
|
|
||||||
if underlying.ConnectionState().Version != gotls.VersionTLS13 {
|
|
||||||
vc.err = ErrNotTLS13
|
|
||||||
}
|
|
||||||
case *utls.UConn:
|
|
||||||
if underlying.ConnectionState().Version != utls.VersionTLS13 {
|
|
||||||
vc.err = ErrNotTLS13
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
vc.err = fmt.Errorf(`failed to use %s, maybe "security" is not "tls" or "utls"`, vc.addons.Flow)
|
|
||||||
}
|
|
||||||
vc.tlsConn = nil
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vc *Conn) recvResponse() error {
|
func (vc *Conn) recvResponse() error {
|
||||||
var buffer [1]byte
|
var buf [1]byte
|
||||||
_, vc.err = io.ReadFull(vc.ExtendedReader, buffer[:])
|
_, vc.err = io.ReadFull(vc.ExtendedConn, buf[:])
|
||||||
if vc.err != nil {
|
if vc.err != nil {
|
||||||
return vc.err
|
return vc.err
|
||||||
}
|
}
|
||||||
|
|
||||||
if buffer[0] != Version {
|
if buf[0] != Version {
|
||||||
return errors.New("unexpected response version")
|
return errors.New("unexpected response version")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, vc.err = io.ReadFull(vc.ExtendedReader, buffer[:])
|
_, vc.err = io.ReadFull(vc.ExtendedConn, buf[:])
|
||||||
if vc.err != nil {
|
if vc.err != nil {
|
||||||
return vc.err
|
return vc.err
|
||||||
}
|
}
|
||||||
|
|
||||||
length := int64(buffer[0])
|
length := int64(buf[0])
|
||||||
if length != 0 { // addon data length > 0
|
if length != 0 { // addon data length > 0
|
||||||
io.CopyN(io.Discard, vc.ExtendedReader, length) // just discard
|
io.CopyN(io.Discard, vc.ExtendedConn, length) // just discard
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vc *Conn) FrontHeadroom() int {
|
|
||||||
if vc.IsXTLSVisionEnabled() {
|
|
||||||
return paddingHeaderLen
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vc *Conn) Upstream() any {
|
func (vc *Conn) Upstream() any {
|
||||||
if vc.tlsConn == nil {
|
return vc.ExtendedConn
|
||||||
return vc.Conn
|
|
||||||
}
|
|
||||||
return vc.tlsConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vc *Conn) NeedHandshake() bool {
|
|
||||||
return vc.needHandshake
|
|
||||||
}
|
|
||||||
|
|
||||||
func (vc *Conn) IsXTLSVisionEnabled() bool {
|
|
||||||
return vc.addons != nil && vc.addons.Flow == XRV
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newConn return a Conn instance
|
// newConn return a Conn instance
|
||||||
func newConn(conn net.Conn, client *Client, dst *DstAddr) (*Conn, error) {
|
func newConn(conn net.Conn, client *Client, dst *DstAddr) (*Conn, error) {
|
||||||
c := &Conn{
|
c := &Conn{
|
||||||
ExtendedReader: N.NewExtendedReader(conn),
|
ExtendedConn: N.NewExtendedConn(conn),
|
||||||
ExtendedWriter: N.NewExtendedWriter(conn),
|
id: client.uuid,
|
||||||
Conn: conn,
|
dst: dst,
|
||||||
id: client.uuid,
|
handshake: make(chan struct{}),
|
||||||
dst: dst,
|
|
||||||
needHandshake: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !dst.UDP && client.Addons != nil {
|
if !dst.UDP && client.Addons != nil {
|
||||||
@ -467,46 +205,15 @@ func newConn(conn net.Conn, client *Client, dst *DstAddr) (*Conn, error) {
|
|||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("failed to use %s, maybe \"security\" is not \"xtls\"", client.Addons.Flow)
|
return nil, fmt.Errorf("failed to use %s, maybe \"security\" is not \"xtls\"", client.Addons.Flow)
|
||||||
}
|
}
|
||||||
case XRV:
|
|
||||||
c.packetsToFilter = 6
|
|
||||||
c.readProcess = true
|
|
||||||
c.readFilterUUID = true
|
|
||||||
c.writeFilterApplicationData = true
|
|
||||||
c.addons = client.Addons
|
|
||||||
var t reflect.Type
|
|
||||||
var p uintptr
|
|
||||||
switch underlying := conn.(type) {
|
|
||||||
case *gotls.Conn:
|
|
||||||
//log.Debugln("type tls")
|
|
||||||
c.Conn = underlying.NetConn()
|
|
||||||
c.tlsConn = underlying
|
|
||||||
t = reflect.TypeOf(underlying).Elem()
|
|
||||||
p = uintptr(unsafe.Pointer(underlying))
|
|
||||||
case *utls.UConn:
|
|
||||||
//log.Debugln("type *utls.UConn")
|
|
||||||
c.Conn = underlying.NetConn()
|
|
||||||
c.tlsConn = underlying
|
|
||||||
t = reflect.TypeOf(underlying.Conn).Elem()
|
|
||||||
p = uintptr(unsafe.Pointer(underlying.Conn))
|
|
||||||
case *tlsC.UConn:
|
|
||||||
//log.Debugln("type *tlsC.UConn")
|
|
||||||
c.Conn = underlying.NetConn()
|
|
||||||
c.tlsConn = underlying.UConn
|
|
||||||
t = reflect.TypeOf(underlying.Conn).Elem()
|
|
||||||
//log.Debugln("t:%v", t)
|
|
||||||
p = uintptr(unsafe.Pointer(underlying.Conn))
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf(`failed to use %s, maybe "security" is not "tls" or "utls"`, client.Addons.Flow)
|
|
||||||
}
|
|
||||||
i, _ := t.FieldByName("input")
|
|
||||||
r, _ := t.FieldByName("rawInput")
|
|
||||||
c.input = (*bytes.Reader)(unsafe.Pointer(p + i.Offset))
|
|
||||||
c.rawInput = (*bytes.Buffer)(unsafe.Pointer(p + r.Offset))
|
|
||||||
//if _, ok := c.Conn.(*net.TCPConn); !ok {
|
|
||||||
// log.Debugln("XTLS underlying conn is not *net.TCPConn, got %T", c.Conn)
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-c.handshake:
|
||||||
|
case <-time.After(200 * time.Millisecond):
|
||||||
|
c.sendRequest(nil)
|
||||||
|
}
|
||||||
|
}()
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
package vless
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
tls13SupportedVersions = []byte{0x00, 0x2b, 0x00, 0x02, 0x03, 0x04}
|
|
||||||
tlsClientHandshakeStart = []byte{0x16, 0x03}
|
|
||||||
tlsServerHandshakeStart = []byte{0x16, 0x03, 0x03}
|
|
||||||
tlsApplicationDataStart = []byte{0x17, 0x03, 0x03}
|
|
||||||
|
|
||||||
tls13CipherSuiteMap = map[uint16]string{
|
|
||||||
0x1301: "TLS_AES_128_GCM_SHA256",
|
|
||||||
0x1302: "TLS_AES_256_GCM_SHA384",
|
|
||||||
0x1303: "TLS_CHACHA20_POLY1305_SHA256",
|
|
||||||
0x1304: "TLS_AES_128_CCM_SHA256",
|
|
||||||
0x1305: "TLS_AES_128_CCM_8_SHA256",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
tlsHandshakeTypeClientHello byte = 0x01
|
|
||||||
tlsHandshakeTypeServerHello byte = 0x02
|
|
||||||
)
|
|
||||||
|
|
||||||
func (vc *Conn) FilterTLS(buffer []byte) (index int) {
|
|
||||||
if vc.packetsToFilter <= 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
lenP := len(buffer)
|
|
||||||
vc.packetsToFilter--
|
|
||||||
if index = bytes.Index(buffer, tlsServerHandshakeStart); index != -1 {
|
|
||||||
if lenP >= index+5 {
|
|
||||||
if buffer[0] == 22 && buffer[1] == 3 && buffer[2] == 3 {
|
|
||||||
vc.isTLS = true
|
|
||||||
if buffer[5] == tlsHandshakeTypeServerHello {
|
|
||||||
//log.Debugln("isTLS12orAbove")
|
|
||||||
vc.remainingServerHello = binary.BigEndian.Uint16(buffer[index+3:]) + 5
|
|
||||||
vc.isTLS12orAbove = true
|
|
||||||
if lenP-index >= 79 && vc.remainingServerHello >= 79 {
|
|
||||||
sessionIDLen := int(buffer[index+43])
|
|
||||||
vc.cipher = binary.BigEndian.Uint16(buffer[index+43+sessionIDLen+1:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if index = bytes.Index(buffer, tlsClientHandshakeStart); index != -1 {
|
|
||||||
if lenP >= index+5 && buffer[index+5] == tlsHandshakeTypeClientHello {
|
|
||||||
vc.isTLS = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if vc.remainingServerHello > 0 {
|
|
||||||
end := int(vc.remainingServerHello)
|
|
||||||
i := index
|
|
||||||
if i < 0 {
|
|
||||||
i = 0
|
|
||||||
}
|
|
||||||
if i+end > lenP {
|
|
||||||
end = lenP
|
|
||||||
vc.remainingServerHello -= uint16(end - i)
|
|
||||||
} else {
|
|
||||||
vc.remainingServerHello -= uint16(end)
|
|
||||||
end += i
|
|
||||||
}
|
|
||||||
if bytes.Contains(buffer[i:end], tls13SupportedVersions) {
|
|
||||||
// TLS 1.3 Client Hello
|
|
||||||
cs, ok := tls13CipherSuiteMap[vc.cipher]
|
|
||||||
if ok && cs != "TLS_AES_128_CCM_8_SHA256" {
|
|
||||||
vc.enableXTLS = true
|
|
||||||
}
|
|
||||||
log.Debugln("XTLS Vision found TLS 1.3, packetLength=%d, CipherSuite=%s", lenP, cs)
|
|
||||||
vc.packetsToFilter = 0
|
|
||||||
return
|
|
||||||
} else if vc.remainingServerHello <= 0 {
|
|
||||||
log.Debugln("XTLS Vision found TLS 1.2, packetLength=%d", lenP)
|
|
||||||
vc.packetsToFilter = 0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Debugln("XTLS Vision found inconclusive server hello, packetLength=%d, remainingServerHelloBytes=%d", lenP, vc.remainingServerHello)
|
|
||||||
}
|
|
||||||
if vc.packetsToFilter <= 0 {
|
|
||||||
log.Debugln("XTLS Vision stop filtering")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
package vless
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/buf"
|
|
||||||
"github.com/Dreamacro/clash/log"
|
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
paddingHeaderLen = 1 + 2 + 2 // =5
|
|
||||||
|
|
||||||
commandPaddingContinue byte = 0x00
|
|
||||||
commandPaddingEnd byte = 0x01
|
|
||||||
commandPaddingDirect byte = 0x02
|
|
||||||
)
|
|
||||||
|
|
||||||
var mutex sync.RWMutex
|
|
||||||
|
|
||||||
func WriteWithPadding(buffer *buf.Buffer, p []byte, command byte, userUUID *uuid.UUID, paddingTLS bool) {
|
|
||||||
contentLen := int32(len(p))
|
|
||||||
var paddingLen int32
|
|
||||||
mutex.Lock()
|
|
||||||
if contentLen < 900 {
|
|
||||||
if paddingTLS {
|
|
||||||
//log.Debugln("long padding")
|
|
||||||
paddingLen = fastrand.Int31n(500) + 900 - contentLen
|
|
||||||
} else {
|
|
||||||
paddingLen = fastrand.Int31n(256)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mutex.Unlock()
|
|
||||||
if userUUID != nil { // unnecessary, but keep the same with Xray
|
|
||||||
buffer.Write(userUUID.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.WriteByte(command)
|
|
||||||
binary.BigEndian.PutUint16(buffer.Extend(2), uint16(contentLen))
|
|
||||||
binary.BigEndian.PutUint16(buffer.Extend(2), uint16(paddingLen))
|
|
||||||
buffer.Write(p)
|
|
||||||
|
|
||||||
buffer.Extend(int(paddingLen))
|
|
||||||
log.Debugln("XTLS Vision write padding1: command=%v, payloadLen=%v, paddingLen=%v", command, contentLen, paddingLen)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ApplyPadding(buffer *buf.Buffer, command byte, userUUID *uuid.UUID, paddingTLS bool) {
|
|
||||||
contentLen := int32(buffer.Len())
|
|
||||||
var paddingLen int32
|
|
||||||
mutex.Lock()
|
|
||||||
if contentLen < 900 {
|
|
||||||
if paddingTLS {
|
|
||||||
//log.Debugln("long padding")
|
|
||||||
paddingLen = fastrand.Int31n(500) + 900 - contentLen
|
|
||||||
} else {
|
|
||||||
paddingLen = fastrand.Int31n(256)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mutex.Unlock()
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(paddingLen))
|
|
||||||
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(contentLen))
|
|
||||||
buffer.ExtendHeader(1)[0] = command
|
|
||||||
if userUUID != nil { // unnecessary, but keep the same with Xray
|
|
||||||
copy(buffer.ExtendHeader(uuid.Size), userUUID.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.Extend(int(paddingLen))
|
|
||||||
log.Debugln("XTLS Vision write padding2: command=%d, payloadLen=%d, paddingLen=%d", command, contentLen, paddingLen)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReshapeBuffer(buffer *buf.Buffer) *buf.Buffer {
|
|
||||||
if buffer.Len() <= buf.BufferSize-paddingHeaderLen {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cutAt := bytes.LastIndex(buffer.Bytes(), tlsApplicationDataStart)
|
|
||||||
if cutAt == -1 {
|
|
||||||
cutAt = buf.BufferSize / 2
|
|
||||||
}
|
|
||||||
buffer2 := buf.New()
|
|
||||||
buffer2.Write(buffer.From(cutAt))
|
|
||||||
buffer.Truncate(cutAt)
|
|
||||||
return buffer2
|
|
||||||
}
|
|
@ -12,7 +12,6 @@ const (
|
|||||||
XRO = "xtls-rprx-origin"
|
XRO = "xtls-rprx-origin"
|
||||||
XRD = "xtls-rprx-direct"
|
XRD = "xtls-rprx-direct"
|
||||||
XRS = "xtls-rprx-splice"
|
XRS = "xtls-rprx-splice"
|
||||||
XRV = "xtls-rprx-vision"
|
|
||||||
|
|
||||||
Version byte = 0 // protocol version. preview version is 0
|
Version byte = 0 // protocol version. preview version is 0
|
||||||
)
|
)
|
||||||
|
@ -2,7 +2,6 @@ package vless
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
@ -10,10 +9,6 @@ import (
|
|||||||
xtls "github.com/xtls/go"
|
xtls "github.com/xtls/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNotTLS13 = errors.New("XTLS Vision based on TLS 1.3 outer connection")
|
|
||||||
)
|
|
||||||
|
|
||||||
type XTLSConfig struct {
|
type XTLSConfig struct {
|
||||||
Host string
|
Host string
|
||||||
SkipCertVerify bool
|
SkipCertVerify bool
|
||||||
|
@ -11,13 +11,17 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"io"
|
"io"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
// Conn wrapper a net.Conn with vmess protocol
|
// Conn wrapper a net.Conn with vmess protocol
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
@ -72,7 +76,7 @@ func (vc *Conn) sendRequest() error {
|
|||||||
buf.WriteByte(vc.respV)
|
buf.WriteByte(vc.respV)
|
||||||
buf.WriteByte(OptionChunkStream)
|
buf.WriteByte(OptionChunkStream)
|
||||||
|
|
||||||
p := fastrand.Intn(16)
|
p := rand.Intn(16)
|
||||||
// P Sec Reserve Cmd
|
// P Sec Reserve Cmd
|
||||||
buf.WriteByte(byte(p<<4) | byte(vc.security))
|
buf.WriteByte(byte(p<<4) | byte(vc.security))
|
||||||
buf.WriteByte(0)
|
buf.WriteByte(0)
|
||||||
@ -90,7 +94,7 @@ func (vc *Conn) sendRequest() error {
|
|||||||
// padding
|
// padding
|
||||||
if p > 0 {
|
if p > 0 {
|
||||||
padding := make([]byte, p)
|
padding := make([]byte, p)
|
||||||
fastrand.Read(padding)
|
rand.Read(padding)
|
||||||
buf.Write(padding)
|
buf.Write(padding)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +200,7 @@ func hashTimestamp(t time.Time) []byte {
|
|||||||
// newConn return a Conn instance
|
// newConn return a Conn instance
|
||||||
func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security, isAead bool) (*Conn, error) {
|
func newConn(conn net.Conn, id *ID, dst *DstAddr, security Security, isAead bool) (*Conn, error) {
|
||||||
randBytes := make([]byte, 33)
|
randBytes := make([]byte, 33)
|
||||||
fastrand.Read(randBytes)
|
rand.Read(randBytes)
|
||||||
reqBodyIV := make([]byte, 16)
|
reqBodyIV := make([]byte, 16)
|
||||||
reqBodyKey := make([]byte, 16)
|
reqBodyKey := make([]byte, 16)
|
||||||
copy(reqBodyIV[:], randBytes[:16])
|
copy(reqBodyIV[:], randBytes[:16])
|
||||||
|
@ -2,11 +2,11 @@ package vmess
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ type H2Config struct {
|
|||||||
func (hc *h2Conn) establishConn() error {
|
func (hc *h2Conn) establishConn() error {
|
||||||
preader, pwriter := io.Pipe()
|
preader, pwriter := io.Pipe()
|
||||||
|
|
||||||
host := hc.cfg.Hosts[fastrand.Intn(len(hc.cfg.Hosts))]
|
host := hc.cfg.Hosts[rand.Intn(len(hc.cfg.Hosts))]
|
||||||
path := hc.cfg.Path
|
path := hc.cfg.Path
|
||||||
// TODO: connect use VMess Host instead of H2 Host
|
// TODO: connect use VMess Host instead of H2 Host
|
||||||
req := http.Request{
|
req := http.Request{
|
||||||
|
@ -4,11 +4,10 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
|
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type httpConn struct {
|
type httpConn struct {
|
||||||
@ -52,16 +51,16 @@ func (hc *httpConn) Write(b []byte) (int, error) {
|
|||||||
return hc.Conn.Write(b)
|
return hc.Conn.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
path := hc.cfg.Path[fastrand.Intn(len(hc.cfg.Path))]
|
path := hc.cfg.Path[rand.Intn(len(hc.cfg.Path))]
|
||||||
host := hc.cfg.Host
|
host := hc.cfg.Host
|
||||||
if header := hc.cfg.Headers["Host"]; len(header) != 0 {
|
if header := hc.cfg.Headers["Host"]; len(header) != 0 {
|
||||||
host = header[fastrand.Intn(len(header))]
|
host = header[rand.Intn(len(header))]
|
||||||
}
|
}
|
||||||
|
|
||||||
u := fmt.Sprintf("http://%s%s", host, path)
|
u := fmt.Sprintf("http://%s%s", host, path)
|
||||||
req, _ := http.NewRequest("GET", u, bytes.NewBuffer(b))
|
req, _ := http.NewRequest("GET", u, bytes.NewBuffer(b))
|
||||||
for key, list := range hc.cfg.Headers {
|
for key, list := range hc.cfg.Headers {
|
||||||
req.Header.Set(key, list[fastrand.Intn(len(list))])
|
req.Header.Set(key, list[rand.Intn(len(list))])
|
||||||
}
|
}
|
||||||
req.ContentLength = int64(len(b))
|
req.ContentLength = int64(len(b))
|
||||||
if err := req.Write(hc.Conn); err != nil {
|
if err := req.Write(hc.Conn); err != nil {
|
||||||
|
@ -3,7 +3,6 @@ package vmess
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
@ -16,7 +15,6 @@ type TLSConfig struct {
|
|||||||
FingerPrint string
|
FingerPrint string
|
||||||
ClientFingerprint string
|
ClientFingerprint string
|
||||||
NextProtos []string
|
NextProtos []string
|
||||||
Reality *tlsC.RealityConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
||||||
@ -36,25 +34,15 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.ClientFingerprint) != 0 {
|
if len(cfg.ClientFingerprint) != 0 {
|
||||||
if cfg.Reality == nil {
|
utlsConn, valid := GetUtlsConnWithClientFingerprint(conn, cfg.ClientFingerprint, tlsConfig)
|
||||||
utlsConn, valid := GetUTLSConn(conn, cfg.ClientFingerprint, tlsConfig)
|
if valid {
|
||||||
if valid {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
|
||||||
return utlsConn, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
return tlsC.GetRealityConn(ctx, conn, cfg.ClientFingerprint, tlsConfig, cfg.Reality)
|
|
||||||
|
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
||||||
|
return utlsConn, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if cfg.Reality != nil {
|
|
||||||
return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConn := tls.Client(conn, tlsConfig)
|
tlsConn := tls.Client(conn, tlsConfig)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
@ -64,7 +52,7 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
|||||||
return tlsConn, err
|
return tlsConn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUTLSConn(conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config) (net.Conn, bool) {
|
func GetUtlsConnWithClientFingerprint(conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config) (net.Conn, bool) {
|
||||||
|
|
||||||
if fingerprint, exists := tlsC.GetFingerprint(ClientFingerprint); exists {
|
if fingerprint, exists := tlsC.GetFingerprint(ClientFingerprint); exists {
|
||||||
utlsConn := tlsC.UClient(conn, tlsConfig, fingerprint)
|
utlsConn := tlsC.UClient(conn, tlsConfig, fingerprint)
|
||||||
|
@ -2,13 +2,12 @@ package vmess
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/Dreamacro/clash/common/utils"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version of vmess
|
// Version of vmess
|
||||||
@ -78,7 +77,7 @@ type Config struct {
|
|||||||
|
|
||||||
// StreamConn return a Conn with net.Conn and DstAddr
|
// StreamConn return a Conn with net.Conn and DstAddr
|
||||||
func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) {
|
func (c *Client) StreamConn(conn net.Conn, dst *DstAddr) (net.Conn, error) {
|
||||||
r := fastrand.Intn(len(c.user))
|
r := rand.Intn(len(c.user))
|
||||||
return newConn(conn, c.user[r], dst, c.security, c.isAead)
|
return newConn(conn, c.user[r], dst, c.security, c.isAead)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,9 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"io"
|
"io"
|
||||||
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -20,9 +22,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/common/buf"
|
"github.com/Dreamacro/clash/common/buf"
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/zhangyunhao116/fastrand"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type websocketConn struct {
|
type websocketConn struct {
|
||||||
@ -120,7 +120,7 @@ func (wsc *websocketConn) WriteBuffer(buffer *buf.Buffer) error {
|
|||||||
binary.BigEndian.PutUint64(header[2:], uint64(dataLen))
|
binary.BigEndian.PutUint64(header[2:], uint64(dataLen))
|
||||||
}
|
}
|
||||||
|
|
||||||
maskKey := fastrand.Uint32()
|
maskKey := rand.Uint32()
|
||||||
binary.LittleEndian.PutUint32(header[1+payloadBitLength:], maskKey)
|
binary.LittleEndian.PutUint32(header[1+payloadBitLength:], maskKey)
|
||||||
N.MaskWebSocket(maskKey, data)
|
N.MaskWebSocket(maskKey, data)
|
||||||
|
|
||||||
@ -301,27 +301,15 @@ func (wsedc *websocketWithEarlyDataConn) SetWriteDeadline(t time.Time) error {
|
|||||||
return wsedc.Conn.SetWriteDeadline(t)
|
return wsedc.Conn.SetWriteDeadline(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wsedc *websocketWithEarlyDataConn) FrontHeadroom() int {
|
func (wsedc *websocketWithEarlyDataConn) LazyHeadroom() bool {
|
||||||
return 14
|
return wsedc.Conn == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wsedc *websocketWithEarlyDataConn) Upstream() any {
|
func (wsedc *websocketWithEarlyDataConn) Upstream() any {
|
||||||
return wsedc.underlay
|
if wsedc.Conn == nil { // ensure return a nil interface not an interface with nil value
|
||||||
}
|
return nil
|
||||||
|
}
|
||||||
//func (wsedc *websocketWithEarlyDataConn) LazyHeadroom() bool {
|
return wsedc.Conn
|
||||||
// return wsedc.Conn == nil
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func (wsedc *websocketWithEarlyDataConn) Upstream() any {
|
|
||||||
// if wsedc.Conn == nil { // ensure return a nil interface not an interface with nil value
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
// return wsedc.Conn
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (wsedc *websocketWithEarlyDataConn) NeedHandshake() bool {
|
|
||||||
return wsedc.Conn == nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func streamWebsocketWithEarlyDataConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
|
func streamWebsocketWithEarlyDataConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) {
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/Dreamacro/clash/common/buf"
|
"github.com/Dreamacro/clash/common/buf"
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
N "github.com/Dreamacro/clash/common/net"
|
||||||
"github.com/Dreamacro/clash/common/utils"
|
|
||||||
C "github.com/Dreamacro/clash/constant"
|
C "github.com/Dreamacro/clash/constant"
|
||||||
|
|
||||||
"github.com/gofrs/uuid"
|
"github.com/gofrs/uuid"
|
||||||
@ -82,8 +81,8 @@ func (tt *tcpTracker) Upstream() any {
|
|||||||
return tt.Conn
|
return tt.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule, uploadTotal int64, downloadTotal int64) *tcpTracker {
|
func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) *tcpTracker {
|
||||||
uuid, _ := utils.UnsafeUUIDGenerator.NewV4()
|
uuid, _ := uuid.NewV4()
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
if tcpAddr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
if tcpAddr, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
||||||
metadata.RemoteDst = tcpAddr.IP.String()
|
metadata.RemoteDst = tcpAddr.IP.String()
|
||||||
@ -101,8 +100,8 @@ func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R
|
|||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
Chain: conn.Chains(),
|
Chain: conn.Chains(),
|
||||||
Rule: "",
|
Rule: "",
|
||||||
UploadTotal: atomic.NewInt64(uploadTotal),
|
UploadTotal: atomic.NewInt64(0),
|
||||||
DownloadTotal: atomic.NewInt64(downloadTotal),
|
DownloadTotal: atomic.NewInt64(0),
|
||||||
},
|
},
|
||||||
extendedReader: N.NewExtendedReader(conn),
|
extendedReader: N.NewExtendedReader(conn),
|
||||||
extendedWriter: N.NewExtendedWriter(conn),
|
extendedWriter: N.NewExtendedWriter(conn),
|
||||||
@ -148,8 +147,8 @@ func (ut *udpTracker) Close() error {
|
|||||||
return ut.PacketConn.Close()
|
return ut.PacketConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule, uploadTotal int64, downloadTotal int64) *udpTracker {
|
func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, rule C.Rule) *udpTracker {
|
||||||
uuid, _ := utils.UnsafeUUIDGenerator.NewV4()
|
uuid, _ := uuid.NewV4()
|
||||||
metadata.RemoteDst = conn.RemoteDestination()
|
metadata.RemoteDst = conn.RemoteDestination()
|
||||||
|
|
||||||
ut := &udpTracker{
|
ut := &udpTracker{
|
||||||
@ -161,8 +160,8 @@ func NewUDPTracker(conn C.PacketConn, manager *Manager, metadata *C.Metadata, ru
|
|||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
Chain: conn.Chains(),
|
Chain: conn.Chains(),
|
||||||
Rule: "",
|
Rule: "",
|
||||||
UploadTotal: atomic.NewInt64(uploadTotal),
|
UploadTotal: atomic.NewInt64(0),
|
||||||
DownloadTotal: atomic.NewInt64(downloadTotal),
|
DownloadTotal: atomic.NewInt64(0),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
"github.com/jpillora/backoff"
|
"github.com/jpillora/backoff"
|
||||||
|
|
||||||
N "github.com/Dreamacro/clash/common/net"
|
|
||||||
"github.com/Dreamacro/clash/component/nat"
|
"github.com/Dreamacro/clash/component/nat"
|
||||||
P "github.com/Dreamacro/clash/component/process"
|
P "github.com/Dreamacro/clash/component/process"
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
@ -201,18 +200,13 @@ func preHandleMetadata(metadata *C.Metadata) error {
|
|||||||
if resolver.FakeIPEnabled() {
|
if resolver.FakeIPEnabled() {
|
||||||
metadata.DstIP = netip.Addr{}
|
metadata.DstIP = netip.Addr{}
|
||||||
metadata.DNSMode = C.DNSFakeIP
|
metadata.DNSMode = C.DNSFakeIP
|
||||||
} else if node, ok := resolver.DefaultHosts.Search(host, false); ok {
|
} else if node := resolver.DefaultHosts.Search(host); node != nil {
|
||||||
// redir-host should lookup the hosts
|
// redir-host should lookup the hosts
|
||||||
metadata.DstIP, _ = node.RandIP()
|
metadata.DstIP = node.Data()
|
||||||
} else if node != nil && node.IsDomain {
|
|
||||||
metadata.Host = node.Domain
|
|
||||||
}
|
}
|
||||||
} else if resolver.IsFakeIP(metadata.DstIP) {
|
} else if resolver.IsFakeIP(metadata.DstIP) {
|
||||||
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
|
return fmt.Errorf("fake DNS record %s missing", metadata.DstIP)
|
||||||
}
|
}
|
||||||
} else if node, ok := resolver.DefaultHosts.Search(metadata.Host, true); ok {
|
|
||||||
// try use domain mapping
|
|
||||||
metadata.Host = node.Domain
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -328,7 +322,7 @@ func handleUDPConn(packet C.PacketAdapter) {
|
|||||||
}
|
}
|
||||||
pCtx.InjectPacketConn(rawPc)
|
pCtx.InjectPacketConn(rawPc)
|
||||||
|
|
||||||
pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule, 0, 0)
|
pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule)
|
||||||
|
|
||||||
switch true {
|
switch true {
|
||||||
case metadata.SpecialProxy != "":
|
case metadata.SpecialProxy != "":
|
||||||
@ -372,21 +366,8 @@ func handleTCPConn(connCtx C.ConnContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := connCtx.Conn()
|
|
||||||
conn.ResetPeeked() // reset before sniffer
|
|
||||||
if sniffer.Dispatcher.Enable() && sniffingEnable {
|
if sniffer.Dispatcher.Enable() && sniffingEnable {
|
||||||
sniffer.Dispatcher.TCPSniff(conn, metadata)
|
sniffer.Dispatcher.TCPSniff(connCtx.Conn(), metadata)
|
||||||
}
|
|
||||||
|
|
||||||
peekMutex := sync.Mutex{}
|
|
||||||
if !conn.Peeked() {
|
|
||||||
peekMutex.Lock()
|
|
||||||
go func() {
|
|
||||||
defer peekMutex.Unlock()
|
|
||||||
_ = conn.SetReadDeadline(time.Now().Add(200 * time.Millisecond))
|
|
||||||
_, _ = conn.Peek(1)
|
|
||||||
_ = conn.SetReadDeadline(time.Time{})
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy, rule, err := resolveMetadata(connCtx, metadata)
|
proxy, rule, err := resolveMetadata(connCtx, metadata)
|
||||||
@ -397,8 +378,8 @@ func handleTCPConn(connCtx C.ConnContext) {
|
|||||||
|
|
||||||
dialMetadata := metadata
|
dialMetadata := metadata
|
||||||
if len(metadata.Host) > 0 {
|
if len(metadata.Host) > 0 {
|
||||||
if node, ok := resolver.DefaultHosts.Search(metadata.Host, false); ok {
|
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
|
||||||
if dstIp, _ := node.RandIP(); !FakeIPRange().Contains(dstIp) {
|
if dstIp := node.Data(); !FakeIPRange().Contains(dstIp) {
|
||||||
dialMetadata.DstIP = dstIp
|
dialMetadata.DstIP = dstIp
|
||||||
dialMetadata.DNSMode = C.DNSHosts
|
dialMetadata.DNSMode = C.DNSHosts
|
||||||
dialMetadata = dialMetadata.Pure()
|
dialMetadata = dialMetadata.Pure()
|
||||||
@ -406,41 +387,10 @@ func handleTCPConn(connCtx C.ConnContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var peekBytes []byte
|
|
||||||
var peekLen int
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTCPTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
remoteConn, err := retry(ctx, func(ctx context.Context) (remoteConn C.Conn, err error) {
|
remoteConn, err := retry(ctx, func(ctx context.Context) (C.Conn, error) {
|
||||||
remoteConn, err = proxy.DialContext(ctx, dialMetadata)
|
return proxy.DialContext(ctx, dialMetadata)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if N.NeedHandshake(remoteConn) {
|
|
||||||
defer func() {
|
|
||||||
for _, chain := range remoteConn.Chains() {
|
|
||||||
if chain == "REJECT" {
|
|
||||||
err = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
remoteConn = nil
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
peekMutex.Lock()
|
|
||||||
defer peekMutex.Unlock()
|
|
||||||
peekBytes, _ = conn.Peek(conn.Buffered())
|
|
||||||
_, err = remoteConn.Write(peekBytes)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if peekLen = len(peekBytes); peekLen > 0 {
|
|
||||||
_, _ = conn.Discard(peekLen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}, func(err error) {
|
}, func(err error) {
|
||||||
if rule == nil {
|
if rule == nil {
|
||||||
log.Warnln(
|
log.Warnln(
|
||||||
@ -458,7 +408,7 @@ func handleTCPConn(connCtx C.ConnContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule, 0, int64(peekLen))
|
remoteConn = statistic.NewTCPTracker(remoteConn, statistic.DefaultManager, metadata, rule)
|
||||||
defer func(remoteConn C.Conn) {
|
defer func(remoteConn C.Conn) {
|
||||||
_ = remoteConn.Close()
|
_ = remoteConn.Close()
|
||||||
}(remoteConn)
|
}(remoteConn)
|
||||||
@ -484,10 +434,6 @@ func handleTCPConn(connCtx C.ConnContext) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = conn.SetReadDeadline(time.Now()) // stop unfinished peek
|
|
||||||
peekMutex.Lock()
|
|
||||||
defer peekMutex.Unlock()
|
|
||||||
_ = conn.SetReadDeadline(time.Time{}) // reset
|
|
||||||
handleSocket(connCtx, remoteConn)
|
handleSocket(connCtx, remoteConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -503,8 +449,8 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) {
|
|||||||
processFound bool
|
processFound bool
|
||||||
)
|
)
|
||||||
|
|
||||||
if node, ok := resolver.DefaultHosts.Search(metadata.Host, false); ok {
|
if node := resolver.DefaultHosts.Search(metadata.Host); node != nil {
|
||||||
metadata.DstIP, _ = node.RandIP()
|
metadata.DstIP = node.Data()
|
||||||
resolved = true
|
resolved = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user