mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-05-18 02:37:59 +03:00
Compare commits
63 Commits
dev-wip
...
v1.11.13-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
824eac453b | ||
|
|
85a1a8a53b | ||
|
|
ae9e7aa5f4 | ||
|
|
52b71c6f00 | ||
|
|
5d04673783 | ||
|
|
307c429715 | ||
|
|
6801b58d96 | ||
|
|
6272596ebc | ||
|
|
7c4c2d5ca8 | ||
|
|
6768c77fa0 | ||
|
|
0dff811977 | ||
|
|
e1a58d12fe | ||
|
|
4e74a4108d | ||
|
|
0238c54261 | ||
|
|
255068fd40 | ||
|
|
098a00b025 | ||
|
|
dba0b5276b | ||
|
|
78ae935468 | ||
|
|
3ea5f76470 | ||
|
|
b4d294c05e | ||
|
|
5c911c97d8 | ||
|
|
2cfc8092ad | ||
|
|
83cf5f5c6a | ||
|
|
e7b3a8eebe | ||
|
|
ee3a42a67e | ||
|
|
50227c0f5f | ||
|
|
26bd698462 | ||
|
|
bc5eb1e1a5 | ||
|
|
995267a042 | ||
|
|
41226a6075 | ||
|
|
81d32181ce | ||
|
|
c5ecca3938 | ||
|
|
900888731c | ||
|
|
13e648e4b1 | ||
|
|
aff12ff671 | ||
|
|
101fb88255 | ||
|
|
8b489354e4 | ||
|
|
7dea6eb7a6 | ||
|
|
af1bfe4e3e | ||
|
|
d574e9eb52 | ||
|
|
2d7df1e1f2 | ||
|
|
1c0ffcf5b1 | ||
|
|
348cc39975 | ||
|
|
987899f94a | ||
|
|
d8b2d5142f | ||
|
|
134802d1ee | ||
|
|
e5e81b4de1 | ||
|
|
300c961efa | ||
|
|
7c7f512405 | ||
|
|
03e8d029c2 | ||
|
|
787b5f1931 | ||
|
|
56a7624618 | ||
|
|
3a84acf122 | ||
|
|
f600e02e47 | ||
|
|
e6d19de58a | ||
|
|
f2bbf6b2aa | ||
|
|
c54d50fd36 | ||
|
|
6a051054db | ||
|
|
49498f6439 | ||
|
|
144a890c71 | ||
|
|
afb4993445 | ||
|
|
4c9455b944 | ||
|
|
5fdc051a08 |
30
.fpm_openwrt
Normal file
30
.fpm_openwrt
Normal file
@@ -0,0 +1,30 @@
|
||||
-s dir
|
||||
--name sing-box
|
||||
--category net
|
||||
--license GPL-3.0-or-later
|
||||
--description "The universal proxy platform."
|
||||
--url "https://sing-box.sagernet.org/"
|
||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||
--no-deb-generate-changes
|
||||
|
||||
--config-files /etc/config/sing-box
|
||||
--config-files /etc/sing-box/config.json
|
||||
|
||||
--depends ca-bundle
|
||||
--depends kmod-inet-diag
|
||||
--depends kmod-tun
|
||||
--depends firewall4
|
||||
|
||||
--before-remove release/config/openwrt.prerm
|
||||
|
||||
release/config/config.json=/etc/sing-box/config.json
|
||||
|
||||
release/config/openwrt.conf=/etc/config/sing-box
|
||||
release/config/openwrt.init=/etc/init.d/sing-box
|
||||
release/config/openwrt.keep=/lib/upgrade/keep.d/sing-box
|
||||
|
||||
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
||||
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
||||
release/completions/sing-box.zsh=/usr/share/zsh/site-functions/_sing-box
|
||||
|
||||
LICENSE=/usr/share/licenses/sing-box/LICENSE
|
||||
@@ -1,11 +1,12 @@
|
||||
-s dir
|
||||
--name sing-box
|
||||
--category net
|
||||
--license GPLv3-or-later
|
||||
--license GPL-3.0-or-later
|
||||
--description "The universal proxy platform."
|
||||
--url "https://sing-box.sagernet.org/"
|
||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||
--no-deb-generate-changes
|
||||
--config-files /etc/sing-box/config.json
|
||||
|
||||
release/config/config.json=/etc/sing-box/config.json
|
||||
28
.github/deb2ipk.sh
vendored
Executable file
28
.github/deb2ipk.sh
vendored
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# mod from https://gist.github.com/pldubouilh/c5703052986bfdd404005951dee54683
|
||||
|
||||
set -e -o pipefail
|
||||
|
||||
PROJECT=$(dirname "$0")/../..
|
||||
TMP_PATH=`mktemp -d`
|
||||
cp $2 $TMP_PATH
|
||||
pushd $TMP_PATH
|
||||
|
||||
DEB_NAME=`ls *.deb`
|
||||
ar x $DEB_NAME
|
||||
|
||||
mkdir control
|
||||
pushd control
|
||||
tar xf ../control.tar.gz
|
||||
rm md5sums
|
||||
sed "s/Architecture:\\ \w*/Architecture:\\ $1/g" ./control -i
|
||||
cat control
|
||||
tar czf ../control.tar.gz ./*
|
||||
popd
|
||||
|
||||
DEB_NAME=${DEB_NAME%.deb}
|
||||
tar czf $DEB_NAME.ipk control.tar.gz data.tar.gz debian-binary
|
||||
popd
|
||||
|
||||
cp $TMP_PATH/$DEB_NAME.ipk $3
|
||||
rm -r $TMP_PATH
|
||||
89
.github/workflows/build.yml
vendored
89
.github/workflows/build.yml
vendored
@@ -68,32 +68,39 @@ jobs:
|
||||
- calculate_version
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ linux, windows, darwin, android ]
|
||||
arch: [ "386", amd64, arm64 ]
|
||||
legacy_go: [ false ]
|
||||
include:
|
||||
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64 }
|
||||
- { os: linux, arch: "386", debian: i386, rpm: i386 }
|
||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl }
|
||||
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl }
|
||||
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64 }
|
||||
- { os: linux, arch: mips64le, debian: mips64el, rpm: mips64el }
|
||||
- { os: linux, arch: mipsle, debian: mipsel, rpm: mipsel }
|
||||
- { os: linux, arch: amd64, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" }
|
||||
- { os: linux, arch: "386", go386: sse2, debian: i386, rpm: i386, openwrt: "i386_pentium4" }
|
||||
- { os: linux, arch: "386", go386: softfloat, openwrt: "i386_pentium-mmx" }
|
||||
- { os: linux, arch: arm64, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" }
|
||||
- { os: linux, arch: arm, goarm: "5", openwrt: "arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" }
|
||||
- { os: linux, arch: arm, goarm: "6", debian: armel, rpm: armv6hl, openwrt: "arm_arm1176jzf-s_vfp" }
|
||||
- { os: linux, arch: arm, goarm: "7", debian: armhf, rpm: armv7hl, pacman: armv7hl, openwrt: "arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" }
|
||||
- { os: linux, arch: mips, gomips: softfloat, openwrt: "mips_24kc mips_4kec mips_mips32" }
|
||||
- { os: linux, arch: mipsle, gomips: hardfloat, debian: mipsel, rpm: mipsel, openwrt: "mipsel_24kc_24kf" }
|
||||
- { os: linux, arch: mipsle, gomips: softfloat, openwrt: "mipsel_24kc mipsel_74kc mipsel_mips32" }
|
||||
- { os: linux, arch: mips64, gomips: softfloat, openwrt: "mips64_mips64r2 mips64_octeonplus" }
|
||||
- { os: linux, arch: mips64le, gomips: hardfloat, debian: mips64el, rpm: mips64el }
|
||||
- { os: linux, arch: mips64le, gomips: softfloat, openwrt: "mips64el_mips64r2" }
|
||||
- { os: linux, arch: s390x, debian: s390x, rpm: s390x }
|
||||
- { os: linux, arch: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64 }
|
||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64 }
|
||||
- { os: linux, arch: riscv64, debian: riscv64, rpm: riscv64, openwrt: "riscv64_generic" }
|
||||
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
|
||||
|
||||
- { os: windows, arch: "386", legacy_go: true }
|
||||
- { os: windows, arch: amd64 }
|
||||
- { os: windows, arch: amd64, legacy_go: true }
|
||||
- { os: darwin, arch: amd64, legacy_go: true }
|
||||
- { os: windows, arch: "386" }
|
||||
- { os: windows, arch: "386", legacy_go: true }
|
||||
- { os: windows, arch: arm64 }
|
||||
|
||||
- { os: darwin, arch: amd64 }
|
||||
- { os: darwin, arch: amd64, legacy_go: true }
|
||||
- { os: darwin, arch: arm64 }
|
||||
|
||||
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
||||
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
|
||||
- { os: android, arch: arm64, ndk: "aarch64-linux-android21" }
|
||||
- { os: android, arch: arm, ndk: "armv7a-linux-androideabi21" }
|
||||
exclude:
|
||||
- { os: darwin, arch: "386" }
|
||||
- { os: android, arch: amd64, ndk: "x86_64-linux-android21" }
|
||||
- { os: android, arch: "386", ndk: "i686-linux-android21" }
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
@@ -139,7 +146,10 @@ jobs:
|
||||
CGO_ENABLED: "0"
|
||||
GOOS: ${{ matrix.os }}
|
||||
GOARCH: ${{ matrix.arch }}
|
||||
GO386: ${{ matrix.go386 }}
|
||||
GOARM: ${{ matrix.goarm }}
|
||||
GOMIPS: ${{ matrix.gomips }}
|
||||
GOMIPS64: ${{ matrix.gomips }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build Android
|
||||
if: matrix.os == 'android'
|
||||
@@ -159,12 +169,17 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set name
|
||||
run: |-
|
||||
ARM_VERSION=$([ -n '${{ matrix.goarm}}' ] && echo 'v${{ matrix.goarm}}' || true)
|
||||
LEGACY=$([ '${{ matrix.legacy_go }}' = 'true' ] && echo "-legacy" || true)
|
||||
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}${ARM_VERSION}${LEGACY}"
|
||||
PKG_NAME="sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.arch }}${ARM_VERSION}"
|
||||
DIR_NAME="sing-box-${{ needs.calculate_version.outputs.version }}-${{ matrix.os }}-${{ matrix.arch }}"
|
||||
if [[ -n "${{ matrix.goarm }}" ]]; then
|
||||
DIR_NAME="${DIR_NAME}v${{ matrix.goarm }}"
|
||||
elif [[ -n "${{ matrix.go386 }}" && "${{ matrix.go386 }}" != 'sse2' ]]; then
|
||||
DIR_NAME="${DIR_NAME}-${{ matrix.go386 }}"
|
||||
elif [[ -n "${{ matrix.gomips }}" && "${{ matrix.gomips }}" != 'hardfloat' ]]; then
|
||||
DIR_NAME="${DIR_NAME}-${{ matrix.gomips }}"
|
||||
elif [[ "${{ matrix.legacy_go }}" == 'true' ]]; then
|
||||
DIR_NAME="${DIR_NAME}-legacy"
|
||||
fi
|
||||
echo "DIR_NAME=${DIR_NAME}" >> "${GITHUB_ENV}"
|
||||
echo "PKG_NAME=${PKG_NAME}" >> "${GITHUB_ENV}"
|
||||
PKG_VERSION="${{ needs.calculate_version.outputs.version }}"
|
||||
PKG_VERSION="${PKG_VERSION//-/\~}"
|
||||
echo "PKG_VERSION=${PKG_VERSION}" >> "${GITHUB_ENV}"
|
||||
@@ -173,10 +188,12 @@ jobs:
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y debsigs
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t deb \
|
||||
-v "$PKG_VERSION" \
|
||||
-p "dist/${PKG_NAME}.deb" \
|
||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.debian }}.deb" \
|
||||
--architecture ${{ matrix.debian }} \
|
||||
dist/sing-box=/usr/bin/sing-box
|
||||
curl -Lo '/tmp/debsigs.diff' 'https://gitlab.com/debsigs/debsigs/-/commit/160138f5de1ec110376d3c807b60a37388bc7c90.diff'
|
||||
@@ -191,9 +208,10 @@ jobs:
|
||||
run: |-
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t rpm \
|
||||
-v "$PKG_VERSION" \
|
||||
-p "dist/${PKG_NAME}.rpm" \
|
||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.rpm }}.rpm" \
|
||||
--architecture ${{ matrix.rpm }} \
|
||||
dist/sing-box=/usr/bin/sing-box
|
||||
cat > $HOME/.rpmmacros <<EOF
|
||||
@@ -209,12 +227,29 @@ jobs:
|
||||
run: |-
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libarchive-tools
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t pacman \
|
||||
-v "$PKG_VERSION" \
|
||||
-p "dist/${PKG_NAME}.pkg.tar.zst" \
|
||||
-p "dist/sing-box_${{ needs.calculate_version.outputs.version }}_${{ matrix.os }}_${{ matrix.pacman }}.pkg.tar.zst" \
|
||||
--architecture ${{ matrix.pacman }} \
|
||||
dist/sing-box=/usr/bin/sing-box
|
||||
- name: Package OpenWrt
|
||||
if: matrix.openwrt != ''
|
||||
run: |-
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
cp .fpm_openwrt .fpm
|
||||
fpm -t deb \
|
||||
-v "$PKG_VERSION" \
|
||||
-p "dist/openwrt.deb" \
|
||||
--architecture all \
|
||||
dist/sing-box=/usr/bin/sing-box
|
||||
for architecture in ${{ matrix.openwrt }}; do
|
||||
.github/deb2ipk.sh "$architecture" "dist/openwrt.deb" "dist/sing-box_${{ needs.calculate_version.outputs.version }}_openwrt_${architecture}.ipk"
|
||||
done
|
||||
rm "dist/openwrt.deb"
|
||||
- name: Archive
|
||||
run: |
|
||||
set -xeuo pipefail
|
||||
@@ -234,7 +269,7 @@ jobs:
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||
name: binary-${{ matrix.os }}_${{ matrix.arch }}${{ matrix.goarm && format('v{0}', matrix.goarm) }}${{ matrix.go386 && format('_{0}', matrix.go386) }}${{ matrix.gomips && format('_{0}', matrix.gomips) }}${{ matrix.legacy_go && '-legacy' || '' }}
|
||||
path: "dist"
|
||||
build_android:
|
||||
name: Build Android
|
||||
|
||||
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -120,6 +120,7 @@ jobs:
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
sudo apt-get install -y debsigs
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t deb \
|
||||
--name "${NAME}" \
|
||||
-v "$PKG_VERSION" \
|
||||
@@ -138,6 +139,7 @@ jobs:
|
||||
run: |-
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t rpm \
|
||||
--name "${NAME}" \
|
||||
-v "$PKG_VERSION" \
|
||||
|
||||
@@ -35,7 +35,6 @@ func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Ma
|
||||
|
||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if m.started && m.stage >= stage {
|
||||
panic("already started")
|
||||
}
|
||||
@@ -43,9 +42,12 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
||||
m.stage = stage
|
||||
if stage == adapter.StartStateStart {
|
||||
// started with outbound manager
|
||||
m.access.Unlock()
|
||||
return nil
|
||||
}
|
||||
for _, endpoint := range m.endpoints {
|
||||
endpoints := m.endpoints
|
||||
m.access.Unlock()
|
||||
for _, endpoint := range endpoints {
|
||||
err := adapter.LegacyStart(endpoint, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||
|
||||
@@ -33,6 +33,8 @@ type CacheFile interface {
|
||||
StoreRDRC() bool
|
||||
dns.RDRCStore
|
||||
|
||||
StoreWARPConfig() bool
|
||||
|
||||
LoadMode() string
|
||||
StoreMode(mode string) error
|
||||
LoadSelected(group string) string
|
||||
@@ -41,6 +43,8 @@ type CacheFile interface {
|
||||
StoreGroupExpand(group string, expand bool) error
|
||||
LoadRuleSet(tag string) *SavedBinary
|
||||
SaveRuleSet(tag string, set *SavedBinary) error
|
||||
LoadWARPConfig(tag string) *SavedBinary
|
||||
SaveWARPConfig(tag string, set *SavedBinary) error
|
||||
}
|
||||
|
||||
type SavedBinary struct {
|
||||
|
||||
4
box.go
4
box.go
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental"
|
||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||
@@ -114,6 +115,9 @@ func New(options Options) (*Box, error) {
|
||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||
needV2RayAPI = true
|
||||
}
|
||||
if experimentalOptions.UnifiedDelay != nil && experimentalOptions.UnifiedDelay.Enabled {
|
||||
ctx = urltest.ContextWithIsUnifiedDelay(ctx)
|
||||
}
|
||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||
var defaultLogWriter io.Writer
|
||||
if platformInterface != nil {
|
||||
|
||||
Submodule clients/android updated: 8354b78e5d...320170a107
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/srs"
|
||||
@@ -56,6 +57,14 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "read rule-set")
|
||||
}
|
||||
if flagRuleSetMatchFormat == "" {
|
||||
switch filepath.Ext(sourcePath) {
|
||||
case ".json":
|
||||
flagRuleSetMatchFormat = C.RuleSetFormatSource
|
||||
case ".srs":
|
||||
flagRuleSetMatchFormat = C.RuleSetFormatBinary
|
||||
}
|
||||
}
|
||||
var ruleSet option.PlainRuleSetCompat
|
||||
switch flagRuleSetMatchFormat {
|
||||
case C.RuleSetFormatSource:
|
||||
|
||||
74
common/cloudflare/api.go
Normal file
74
common/cloudflare/api.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
type CloudflareApi struct {
|
||||
client http.Client
|
||||
}
|
||||
|
||||
func NewCloudflareApi(opts ...CloudflareApiOption) *CloudflareApi {
|
||||
api := &CloudflareApi{http.Client{Timeout: 30 * time.Second}}
|
||||
for _, opt := range opts {
|
||||
opt(api)
|
||||
}
|
||||
return api
|
||||
}
|
||||
|
||||
func (api *CloudflareApi) CreateProfile(ctx context.Context, publicKey string) (*CloudflareProfile, error) {
|
||||
request, err := http.NewRequest("POST", "https://api.cloudflareclient.com/v0i1909051800/reg", strings.NewReader(
|
||||
fmt.Sprintf(
|
||||
"{\"install_id\":\"\",\"tos\":\"%s\",\"key\":\"%s\",\"fcm_token\":\"\",\"type\":\"ios\",\"locale\":\"en_US\"}",
|
||||
time.Now().Format("2006-01-02T15:04:05.000Z"),
|
||||
publicKey,
|
||||
),
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response, err := api.client.Do(request.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("status code is not 200")
|
||||
}
|
||||
content, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
profile := new(CloudflareProfile)
|
||||
return profile, json.NewDecoder(strings.NewReader(gjson.Get(string(content), "result").Raw)).Decode(profile)
|
||||
}
|
||||
|
||||
func (api *CloudflareApi) GetProfile(ctx context.Context, authToken string, id string) (*CloudflareProfile, error) {
|
||||
request, err := http.NewRequest("GET", "https://api.cloudflareclient.com/v0i1909051800/reg/"+id, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Header.Set("Authorization", "Bearer "+authToken)
|
||||
response, err := api.client.Do(request.WithContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != 200 {
|
||||
return nil, fmt.Errorf("status code is not 200")
|
||||
}
|
||||
content, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
profile := new(CloudflareProfile)
|
||||
return profile, json.NewDecoder(strings.NewReader(gjson.Get(string(content), "result").Raw)).Decode(profile)
|
||||
}
|
||||
17
common/cloudflare/option.go
Normal file
17
common/cloudflare/option.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package cloudflare
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type CloudflareApiOption func(api *CloudflareApi)
|
||||
|
||||
func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) CloudflareApiOption {
|
||||
return func(api *CloudflareApi) {
|
||||
api.client.Transport = &http.Transport{
|
||||
DialContext: dialContext,
|
||||
}
|
||||
}
|
||||
}
|
||||
64
common/cloudflare/profile.go
Normal file
64
common/cloudflare/profile.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package cloudflare
|
||||
|
||||
import "time"
|
||||
|
||||
type CloudflareProfile struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
Account struct {
|
||||
ID string `json:"id"`
|
||||
AccountType string `json:"account_type"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
PremiumData int `json:"premium_data"`
|
||||
Quota int `json:"quota"`
|
||||
Usage int `json:"usage"`
|
||||
WARPPlus bool `json:"warp_plus"`
|
||||
ReferralCount int `json:"referral_count"`
|
||||
ReferralRenewalCountdown int `json:"referral_renewal_countdown"`
|
||||
Role string `json:"role"`
|
||||
License string `json:"license"`
|
||||
TTL time.Time `json:"ttl"`
|
||||
} `json:"account"`
|
||||
Config struct {
|
||||
ClientID string `json:"client_id"`
|
||||
Interface struct {
|
||||
Addresses struct {
|
||||
V4 string `json:"v4"`
|
||||
V6 string `json:"v6"`
|
||||
} `json:"addresses"`
|
||||
} `json:"interface"`
|
||||
Peers []struct {
|
||||
PublicKey string `json:"public_key"`
|
||||
Endpoint struct {
|
||||
V4 string `json:"v4"`
|
||||
V6 string `json:"v6"`
|
||||
Host string `json:"host"`
|
||||
Ports []int `json:"ports"`
|
||||
} `json:"endpoint"`
|
||||
} `json:"peers"`
|
||||
Services struct {
|
||||
HTTPProxy string `json:"http_proxy"`
|
||||
} `json:"services"`
|
||||
Metrics struct {
|
||||
Ping int `json:"ping"`
|
||||
Report int `json:"report"`
|
||||
} `json:"metrics"`
|
||||
} `json:"config"`
|
||||
Token string `json:"token"`
|
||||
WARPEnabled bool `json:"warp_enabled"`
|
||||
WaitlistEnabled bool `json:"waitlist_enabled"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
Tos time.Time `json:"tos"`
|
||||
Place int `json:"place"`
|
||||
Locale string `json:"locale"`
|
||||
Enabled bool `json:"enabled"`
|
||||
InstallID string `json:"install_id"`
|
||||
FcmToken string `json:"fcm_token"`
|
||||
Policy struct {
|
||||
TunnelProtocol string `json:"tunnel_protocol"`
|
||||
} `json:"policy"`
|
||||
}
|
||||
@@ -333,7 +333,17 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
||||
return d.udpListener.ListenPacket(context.Background(), network, address)
|
||||
udpListener := d.udpListener
|
||||
udpListener.Control = control.Append(udpListener.Control, func(network, address string, conn syscall.RawConn) error {
|
||||
for _, wgControlFn := range WgControlFns {
|
||||
err := wgControlFn(network, address, conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return udpListener.ListenPacket(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
package humanize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// IEC Sizes.
|
||||
// kibis of bits
|
||||
const (
|
||||
Byte = 1 << (iota * 10)
|
||||
KiByte
|
||||
MiByte
|
||||
GiByte
|
||||
TiByte
|
||||
PiByte
|
||||
EiByte
|
||||
)
|
||||
|
||||
// SI Sizes.
|
||||
const (
|
||||
IByte = 1
|
||||
KByte = IByte * 1000
|
||||
MByte = KByte * 1000
|
||||
GByte = MByte * 1000
|
||||
TByte = GByte * 1000
|
||||
PByte = TByte * 1000
|
||||
EByte = PByte * 1000
|
||||
)
|
||||
|
||||
var defaultSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kib": KiByte,
|
||||
"kb": KByte,
|
||||
"mib": MiByte,
|
||||
"mb": MByte,
|
||||
"gib": GiByte,
|
||||
"gb": GByte,
|
||||
"tib": TiByte,
|
||||
"tb": TByte,
|
||||
"pib": PiByte,
|
||||
"pb": PByte,
|
||||
"eib": EiByte,
|
||||
"eb": EByte,
|
||||
// Without suffix
|
||||
"": Byte,
|
||||
"ki": KiByte,
|
||||
"k": KByte,
|
||||
"mi": MiByte,
|
||||
"m": MByte,
|
||||
"gi": GiByte,
|
||||
"g": GByte,
|
||||
"ti": TiByte,
|
||||
"t": TByte,
|
||||
"pi": PiByte,
|
||||
"p": PByte,
|
||||
"ei": EiByte,
|
||||
"e": EByte,
|
||||
}
|
||||
|
||||
var memorysSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kb": KiByte,
|
||||
"mb": MiByte,
|
||||
"gb": GiByte,
|
||||
"tb": TiByte,
|
||||
"pb": PiByte,
|
||||
"eb": EiByte,
|
||||
"": Byte,
|
||||
"k": KiByte,
|
||||
"m": MiByte,
|
||||
"g": GiByte,
|
||||
"t": TiByte,
|
||||
"p": PiByte,
|
||||
"e": EiByte,
|
||||
}
|
||||
|
||||
var (
|
||||
defaultSizes = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
||||
iSizes = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
||||
)
|
||||
|
||||
func Bytes(s uint64) string {
|
||||
return humanateBytes(s, 1000, defaultSizes)
|
||||
}
|
||||
|
||||
func MemoryBytes(s uint64) string {
|
||||
return humanateBytes(s, 1024, defaultSizes)
|
||||
}
|
||||
|
||||
func IBytes(s uint64) string {
|
||||
return humanateBytes(s, 1024, iSizes)
|
||||
}
|
||||
|
||||
func logn(n, b float64) float64 {
|
||||
return math.Log(n) / math.Log(b)
|
||||
}
|
||||
|
||||
func humanateBytes(s uint64, base float64, sizes []string) string {
|
||||
if s < 10 {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
e := math.Floor(logn(float64(s), base))
|
||||
suffix := sizes[int(e)]
|
||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
||||
f := "%.0f %s"
|
||||
if val < 10 {
|
||||
f = "%.1f %s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f, val, suffix)
|
||||
}
|
||||
|
||||
func ParseBytes(s string) (uint64, error) {
|
||||
return parseBytes0(s, defaultSizeTable)
|
||||
}
|
||||
|
||||
func ParseMemoryBytes(s string) (uint64, error) {
|
||||
return parseBytes0(s, memorysSizeTable)
|
||||
}
|
||||
|
||||
func parseBytes0(s string, sizeTable map[string]uint64) (uint64, error) {
|
||||
lastDigit := 0
|
||||
hasComma := false
|
||||
for _, r := range s {
|
||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||
break
|
||||
}
|
||||
if r == ',' {
|
||||
hasComma = true
|
||||
}
|
||||
lastDigit++
|
||||
}
|
||||
|
||||
num := s[:lastDigit]
|
||||
if hasComma {
|
||||
num = strings.Replace(num, ",", "", -1)
|
||||
}
|
||||
|
||||
f, err := strconv.ParseFloat(num, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||
if m, ok := sizeTable[extra]; ok {
|
||||
f *= float64(m)
|
||||
if f >= math.MaxUint64 {
|
||||
return 0, fmt.Errorf("too large: %v", s)
|
||||
}
|
||||
return uint64(f), nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package interrupt
|
||||
import (
|
||||
"net"
|
||||
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
@@ -73,3 +74,32 @@ func (c *PacketConn) WriterReplaceable() bool {
|
||||
func (c *PacketConn) Upstream() any {
|
||||
return c.PacketConn
|
||||
}
|
||||
|
||||
type SingPacketConn struct {
|
||||
N.PacketConn
|
||||
group *Group
|
||||
element *list.Element[*groupConnItem]
|
||||
}
|
||||
|
||||
/*func (c *SingPacketConn) MarkAsInternal() {
|
||||
c.element.Value.internal = true
|
||||
}*/
|
||||
|
||||
func (c *SingPacketConn) Close() error {
|
||||
c.group.access.Lock()
|
||||
defer c.group.access.Unlock()
|
||||
c.group.connections.Remove(c.element)
|
||||
return c.PacketConn.Close()
|
||||
}
|
||||
|
||||
func (c *SingPacketConn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *SingPacketConn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *SingPacketConn) Upstream() any {
|
||||
return c.PacketConn
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
@@ -36,6 +37,13 @@ func (g *Group) NewPacketConn(conn net.PacketConn, isExternal bool) net.PacketCo
|
||||
return &PacketConn{PacketConn: conn, group: g, element: item}
|
||||
}
|
||||
|
||||
func (g *Group) NewSingPacketConn(conn N.PacketConn, isExternal bool) N.PacketConn {
|
||||
g.access.Lock()
|
||||
defer g.access.Unlock()
|
||||
item := g.connections.PushBack(&groupConnItem{conn, isExternal})
|
||||
return &SingPacketConn{PacketConn: conn, group: g, element: item}
|
||||
}
|
||||
|
||||
func (g *Group) Interrupt(interruptExternalConnections bool) {
|
||||
g.access.Lock()
|
||||
defer g.access.Unlock()
|
||||
|
||||
@@ -31,13 +31,18 @@ func BitTorrent(_ context.Context, metadata *adapter.InboundContext, reader io.R
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
const header = "BitTorrent protocol"
|
||||
var protocol [19]byte
|
||||
_, err = reader.Read(protocol[:])
|
||||
var n int
|
||||
n, err = reader.Read(protocol[:])
|
||||
if string(protocol[:n]) != header[:n] {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if err != nil {
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
if string(protocol[:]) != "BitTorrent protocol" {
|
||||
return os.ErrInvalid
|
||||
if n < 19 {
|
||||
return ErrNeedMoreData
|
||||
}
|
||||
|
||||
metadata.Protocol = C.ProtocolBitTorrent
|
||||
|
||||
@@ -32,6 +32,27 @@ func TestSniffBittorrent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSniffIncompleteBittorrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pkt, err := hex.DecodeString("13426974546f7272656e74")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
func TestSniffNotBittorrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pkt, err := hex.DecodeString("13426974546f7272656e75")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.BitTorrent(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||
require.NotEmpty(t, err)
|
||||
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
func TestSniffUTP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -20,22 +20,36 @@ func StreamDomainNameQuery(readCtx context.Context, metadata *adapter.InboundCon
|
||||
if err != nil {
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
if length == 0 {
|
||||
if length < 12 {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
buffer := buf.NewSize(int(length))
|
||||
defer buffer.Release()
|
||||
_, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
|
||||
var n int
|
||||
n, err = buffer.ReadFullFrom(reader, buffer.FreeLen())
|
||||
packet := buffer.Bytes()
|
||||
if n > 2 && packet[2]&0x80 != 0 { // QR
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if n > 5 && packet[4] == 0 && packet[5] == 0 { // QDCOUNT
|
||||
return os.ErrInvalid
|
||||
}
|
||||
for i := 6; i < 10; i++ {
|
||||
// ANCOUNT, NSCOUNT
|
||||
if n > i && packet[i] != 0 {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
}
|
||||
return DomainNameQuery(readCtx, metadata, buffer.Bytes())
|
||||
return DomainNameQuery(readCtx, metadata, packet)
|
||||
}
|
||||
|
||||
func DomainNameQuery(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
||||
var msg mDNS.Msg
|
||||
err := msg.Unpack(packet)
|
||||
if err != nil {
|
||||
if err != nil || msg.Response || len(msg.Question) == 0 || len(msg.Answer) > 0 || len(msg.Ns) > 0 {
|
||||
return err
|
||||
}
|
||||
metadata.Protocol = C.ProtocolDNS
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sniff_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
@@ -21,3 +22,32 @@ func TestSniffDNS(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
|
||||
}
|
||||
|
||||
func TestSniffStreamDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
query, err := hex.DecodeString("001e740701000001000000000000012a06676f6f676c6503636f6d0000010001")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolDNS, metadata.Protocol)
|
||||
}
|
||||
|
||||
func TestSniffIncompleteStreamDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
query, err := hex.DecodeString("001e740701000001000000000000")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
|
||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
func TestSniffNotStreamDNS(t *testing.T) {
|
||||
t.Parallel()
|
||||
query, err := hex.DecodeString("001e740701000000000000000000")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.StreamDomainNameQuery(context.TODO(), &metadata, bytes.NewReader(query))
|
||||
require.NotEmpty(t, err)
|
||||
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ func PeekStream(ctx context.Context, metadata *adapter.InboundContext, conn net.
|
||||
}
|
||||
sniffError = E.Errors(sniffError, err)
|
||||
}
|
||||
if !errors.Is(err, ErrNeedMoreData) {
|
||||
if !errors.Is(sniffError, ErrNeedMoreData) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,11 @@ func SSH(_ context.Context, metadata *adapter.InboundContext, reader io.Reader)
|
||||
const sshPrefix = "SSH-2.0-"
|
||||
bReader := bufio.NewReader(reader)
|
||||
prefix, err := bReader.Peek(len(sshPrefix))
|
||||
if string(prefix[:]) != sshPrefix[:len(prefix)] {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
if err != nil {
|
||||
return E.Cause1(ErrNeedMoreData, err)
|
||||
} else if string(prefix) != sshPrefix {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
fistLine, _, err := bReader.ReadLine()
|
||||
if err != nil {
|
||||
|
||||
@@ -24,3 +24,24 @@ func TestSniffSSH(t *testing.T) {
|
||||
require.Equal(t, C.ProtocolSSH, metadata.Protocol)
|
||||
require.Equal(t, "dropbear", metadata.Client)
|
||||
}
|
||||
|
||||
func TestSniffIncompleteSSH(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pkt, err := hex.DecodeString("5353482d322e30")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||
require.ErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
func TestSniffNotSSH(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pkt, err := hex.DecodeString("5353482d322e31")
|
||||
require.NoError(t, err)
|
||||
var metadata adapter.InboundContext
|
||||
err = sniff.SSH(context.TODO(), &metadata, bytes.NewReader(pkt))
|
||||
require.NotEmpty(t, err)
|
||||
require.NotErrorIs(t, err, sniff.ErrNeedMoreData)
|
||||
}
|
||||
|
||||
13
common/urltest/context.go
Normal file
13
common/urltest/context.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package urltest
|
||||
|
||||
import "context"
|
||||
|
||||
type contextKeyIsUnifiedDelay struct{}
|
||||
|
||||
func ContextWithIsUnifiedDelay(ctx context.Context) context.Context {
|
||||
return context.WithValue(ctx, contextKeyIsUnifiedDelay{}, true)
|
||||
}
|
||||
|
||||
func IsUnifiedDelayFromContext(ctx context.Context) bool {
|
||||
return ctx.Value(contextKeyIsUnifiedDelay{}) != nil
|
||||
}
|
||||
@@ -122,6 +122,15 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e
|
||||
return
|
||||
}
|
||||
resp.Body.Close()
|
||||
if IsUnifiedDelayFromContext(ctx) {
|
||||
second := time.Now()
|
||||
resp, err = client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp.Body.Close()
|
||||
start = second
|
||||
}
|
||||
t = uint16(time.Since(start) / time.Millisecond)
|
||||
return
|
||||
}
|
||||
|
||||
337
common/xray/buf/buffer.go
Normal file
337
common/xray/buf/buffer.go
Normal file
@@ -0,0 +1,337 @@
|
||||
package buf
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray/bytespool"
|
||||
"github.com/sagernet/sing-box/common/xray/net"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
const (
|
||||
// Size of a regular buffer.
|
||||
Size = 8192
|
||||
)
|
||||
|
||||
var zero = [Size * 10]byte{0}
|
||||
|
||||
var pool = bytespool.GetPool(Size)
|
||||
|
||||
// ownership represents the data owner of the buffer.
|
||||
type ownership uint8
|
||||
|
||||
const (
|
||||
managed ownership = iota
|
||||
unmanaged
|
||||
bytespools
|
||||
)
|
||||
|
||||
// Buffer is a recyclable allocation of a byte array. Buffer.Release() recycles
|
||||
// the buffer into an internal buffer pool, in order to recreate a buffer more
|
||||
// quickly.
|
||||
type Buffer struct {
|
||||
v []byte
|
||||
start int32
|
||||
end int32
|
||||
ownership ownership
|
||||
UDP *net.Destination
|
||||
}
|
||||
|
||||
// New creates a Buffer with 0 length and 8K capacity, managed.
|
||||
func New() *Buffer {
|
||||
buf := pool.Get().([]byte)
|
||||
if cap(buf) >= Size {
|
||||
buf = buf[:Size]
|
||||
} else {
|
||||
buf = make([]byte, Size)
|
||||
}
|
||||
|
||||
return &Buffer{
|
||||
v: buf,
|
||||
}
|
||||
}
|
||||
|
||||
// NewExisted creates a standard size Buffer with an existed bytearray, managed.
|
||||
func NewExisted(b []byte) *Buffer {
|
||||
if cap(b) < Size {
|
||||
panic("Invalid buffer")
|
||||
}
|
||||
|
||||
oLen := len(b)
|
||||
if oLen < Size {
|
||||
b = b[:Size]
|
||||
}
|
||||
|
||||
return &Buffer{
|
||||
v: b,
|
||||
end: int32(oLen),
|
||||
}
|
||||
}
|
||||
|
||||
// FromBytes creates a Buffer with an existed bytearray, unmanaged.
|
||||
func FromBytes(b []byte) *Buffer {
|
||||
return &Buffer{
|
||||
v: b,
|
||||
end: int32(len(b)),
|
||||
ownership: unmanaged,
|
||||
}
|
||||
}
|
||||
|
||||
// StackNew creates a new Buffer object on stack, managed.
|
||||
// This method is for buffers that is released in the same function.
|
||||
func StackNew() Buffer {
|
||||
buf := pool.Get().([]byte)
|
||||
if cap(buf) >= Size {
|
||||
buf = buf[:Size]
|
||||
} else {
|
||||
buf = make([]byte, Size)
|
||||
}
|
||||
|
||||
return Buffer{
|
||||
v: buf,
|
||||
}
|
||||
}
|
||||
|
||||
// NewWithSize creates a Buffer with 0 length and capacity with at least the given size, bytespool's.
|
||||
func NewWithSize(size int32) *Buffer {
|
||||
return &Buffer{
|
||||
v: bytespool.Alloc(size),
|
||||
ownership: bytespools,
|
||||
}
|
||||
}
|
||||
|
||||
// Release recycles the buffer into an internal buffer pool.
|
||||
func (b *Buffer) Release() {
|
||||
if b == nil || b.v == nil || b.ownership == unmanaged {
|
||||
return
|
||||
}
|
||||
|
||||
p := b.v
|
||||
b.v = nil
|
||||
b.Clear()
|
||||
|
||||
switch b.ownership {
|
||||
case managed:
|
||||
if cap(p) == Size {
|
||||
pool.Put(p)
|
||||
}
|
||||
case bytespools:
|
||||
bytespool.Free(p)
|
||||
}
|
||||
b.UDP = nil
|
||||
}
|
||||
|
||||
// Clear clears the content of the buffer, results an empty buffer with
|
||||
// Len() = 0.
|
||||
func (b *Buffer) Clear() {
|
||||
b.start = 0
|
||||
b.end = 0
|
||||
}
|
||||
|
||||
// Byte returns the bytes at index.
|
||||
func (b *Buffer) Byte(index int32) byte {
|
||||
return b.v[b.start+index]
|
||||
}
|
||||
|
||||
// SetByte sets the byte value at index.
|
||||
func (b *Buffer) SetByte(index int32, value byte) {
|
||||
b.v[b.start+index] = value
|
||||
}
|
||||
|
||||
// Bytes returns the content bytes of this Buffer.
|
||||
func (b *Buffer) Bytes() []byte {
|
||||
return b.v[b.start:b.end]
|
||||
}
|
||||
|
||||
// Extend increases the buffer size by n bytes, and returns the extended part.
|
||||
// It panics if result size is larger than buf.Size.
|
||||
func (b *Buffer) Extend(n int32) []byte {
|
||||
end := b.end + n
|
||||
if end > int32(len(b.v)) {
|
||||
panic("extending out of bound")
|
||||
}
|
||||
ext := b.v[b.end:end]
|
||||
b.end = end
|
||||
copy(ext, zero[:])
|
||||
return ext
|
||||
}
|
||||
|
||||
// BytesRange returns a slice of this buffer with given from and to boundary.
|
||||
func (b *Buffer) BytesRange(from, to int32) []byte {
|
||||
if from < 0 {
|
||||
from += b.Len()
|
||||
}
|
||||
if to < 0 {
|
||||
to += b.Len()
|
||||
}
|
||||
return b.v[b.start+from : b.start+to]
|
||||
}
|
||||
|
||||
// BytesFrom returns a slice of this Buffer starting from the given position.
|
||||
func (b *Buffer) BytesFrom(from int32) []byte {
|
||||
if from < 0 {
|
||||
from += b.Len()
|
||||
}
|
||||
return b.v[b.start+from : b.end]
|
||||
}
|
||||
|
||||
// BytesTo returns a slice of this Buffer from start to the given position.
|
||||
func (b *Buffer) BytesTo(to int32) []byte {
|
||||
if to < 0 {
|
||||
to += b.Len()
|
||||
}
|
||||
if to < 0 {
|
||||
to = 0
|
||||
}
|
||||
return b.v[b.start : b.start+to]
|
||||
}
|
||||
|
||||
// Check makes sure that 0 <= b.start <= b.end.
|
||||
func (b *Buffer) Check() {
|
||||
if b.start < 0 {
|
||||
b.start = 0
|
||||
}
|
||||
if b.end < 0 {
|
||||
b.end = 0
|
||||
}
|
||||
if b.start > b.end {
|
||||
b.start = b.end
|
||||
}
|
||||
}
|
||||
|
||||
// Resize cuts the buffer at the given position.
|
||||
func (b *Buffer) Resize(from, to int32) {
|
||||
oldEnd := b.end
|
||||
if from < 0 {
|
||||
from += b.Len()
|
||||
}
|
||||
if to < 0 {
|
||||
to += b.Len()
|
||||
}
|
||||
if to < from {
|
||||
panic("Invalid slice")
|
||||
}
|
||||
b.end = b.start + to
|
||||
b.start += from
|
||||
b.Check()
|
||||
if b.end > oldEnd {
|
||||
copy(b.v[oldEnd:b.end], zero[:])
|
||||
}
|
||||
}
|
||||
|
||||
// Advance cuts the buffer at the given position.
|
||||
func (b *Buffer) Advance(from int32) {
|
||||
if from < 0 {
|
||||
from += b.Len()
|
||||
}
|
||||
b.start += from
|
||||
b.Check()
|
||||
}
|
||||
|
||||
// Len returns the length of the buffer content.
|
||||
func (b *Buffer) Len() int32 {
|
||||
if b == nil {
|
||||
return 0
|
||||
}
|
||||
return b.end - b.start
|
||||
}
|
||||
|
||||
// Cap returns the capacity of the buffer content.
|
||||
func (b *Buffer) Cap() int32 {
|
||||
if b == nil {
|
||||
return 0
|
||||
}
|
||||
return int32(len(b.v))
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the buffer is empty.
|
||||
func (b *Buffer) IsEmpty() bool {
|
||||
return b.Len() == 0
|
||||
}
|
||||
|
||||
// IsFull returns true if the buffer has no more room to grow.
|
||||
func (b *Buffer) IsFull() bool {
|
||||
return b != nil && b.end == int32(len(b.v))
|
||||
}
|
||||
|
||||
// Write implements Write method in io.Writer.
|
||||
func (b *Buffer) Write(data []byte) (int, error) {
|
||||
nBytes := copy(b.v[b.end:], data)
|
||||
b.end += int32(nBytes)
|
||||
return nBytes, nil
|
||||
}
|
||||
|
||||
// WriteByte writes a single byte into the buffer.
|
||||
func (b *Buffer) WriteByte(v byte) error {
|
||||
if b.IsFull() {
|
||||
return E.New("buffer full")
|
||||
}
|
||||
b.v[b.end] = v
|
||||
b.end++
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteString implements io.StringWriter.
|
||||
func (b *Buffer) WriteString(s string) (int, error) {
|
||||
return b.Write([]byte(s))
|
||||
}
|
||||
|
||||
// ReadByte implements io.ByteReader
|
||||
func (b *Buffer) ReadByte() (byte, error) {
|
||||
if b.start == b.end {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
nb := b.v[b.start]
|
||||
b.start++
|
||||
return nb, nil
|
||||
}
|
||||
|
||||
// ReadBytes implements bufio.Reader.ReadBytes
|
||||
func (b *Buffer) ReadBytes(length int32) ([]byte, error) {
|
||||
if b.end-b.start < length {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
nb := b.v[b.start : b.start+length]
|
||||
b.start += length
|
||||
return nb, nil
|
||||
}
|
||||
|
||||
// Read implements io.Reader.Read().
|
||||
func (b *Buffer) Read(data []byte) (int, error) {
|
||||
if b.Len() == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
nBytes := copy(data, b.v[b.start:b.end])
|
||||
if int32(nBytes) == b.Len() {
|
||||
b.Clear()
|
||||
} else {
|
||||
b.start += int32(nBytes)
|
||||
}
|
||||
return nBytes, nil
|
||||
}
|
||||
|
||||
// ReadFrom implements io.ReaderFrom.
|
||||
func (b *Buffer) ReadFrom(reader io.Reader) (int64, error) {
|
||||
n, err := reader.Read(b.v[b.end:])
|
||||
b.end += int32(n)
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// ReadFullFrom reads exact size of bytes from given reader, or until error occurs.
|
||||
func (b *Buffer) ReadFullFrom(reader io.Reader, size int32) (int64, error) {
|
||||
end := b.end + size
|
||||
if end > int32(len(b.v)) {
|
||||
v := end
|
||||
return 0, E.New("out of bound: ", v)
|
||||
}
|
||||
n, err := io.ReadFull(reader, b.v[b.end:end])
|
||||
b.end += int32(n)
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
// String returns the string form of this Buffer.
|
||||
func (b *Buffer) String() string {
|
||||
return string(b.Bytes())
|
||||
}
|
||||
124
common/xray/buf/copy.go
Normal file
124
common/xray/buf/copy.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package buf
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray/errors"
|
||||
"github.com/sagernet/sing-box/common/xray/signal"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type dataHandler func(MultiBuffer)
|
||||
|
||||
type copyHandler struct {
|
||||
onData []dataHandler
|
||||
}
|
||||
|
||||
// SizeCounter is for counting bytes copied by Copy().
|
||||
type SizeCounter struct {
|
||||
Size int64
|
||||
}
|
||||
|
||||
// CopyOption is an option for copying data.
|
||||
type CopyOption func(*copyHandler)
|
||||
|
||||
// UpdateActivity is a CopyOption to update activity on each data copy operation.
|
||||
func UpdateActivity(timer signal.ActivityUpdater) CopyOption {
|
||||
return func(handler *copyHandler) {
|
||||
handler.onData = append(handler.onData, func(MultiBuffer) {
|
||||
timer.Update()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// CountSize is a CopyOption that sums the total size of data copied into the given SizeCounter.
|
||||
func CountSize(sc *SizeCounter) CopyOption {
|
||||
return func(handler *copyHandler) {
|
||||
handler.onData = append(handler.onData, func(b MultiBuffer) {
|
||||
sc.Size += int64(b.Len())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type readError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (e readError) Error() string {
|
||||
return e.error.Error()
|
||||
}
|
||||
|
||||
func (e readError) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
// IsReadError returns true if the error in Copy() comes from reading.
|
||||
func IsReadError(err error) bool {
|
||||
_, ok := err.(readError)
|
||||
return ok
|
||||
}
|
||||
|
||||
type writeError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (e writeError) Error() string {
|
||||
return e.error.Error()
|
||||
}
|
||||
|
||||
func (e writeError) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
// IsWriteError returns true if the error in Copy() comes from writing.
|
||||
func IsWriteError(err error) bool {
|
||||
_, ok := err.(writeError)
|
||||
return ok
|
||||
}
|
||||
|
||||
func copyInternal(reader Reader, writer Writer, handler *copyHandler) error {
|
||||
for {
|
||||
buffer, err := reader.ReadMultiBuffer()
|
||||
if !buffer.IsEmpty() {
|
||||
for _, handler := range handler.onData {
|
||||
handler(buffer)
|
||||
}
|
||||
|
||||
if werr := writer.WriteMultiBuffer(buffer); werr != nil {
|
||||
return writeError{werr}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return readError{err}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy dumps all payload from reader to writer or stops when an error occurs. It returns nil when EOF.
|
||||
func Copy(reader Reader, writer Writer, options ...CopyOption) error {
|
||||
var handler copyHandler
|
||||
for _, option := range options {
|
||||
option(&handler)
|
||||
}
|
||||
err := copyInternal(reader, writer, &handler)
|
||||
if err != nil && errors.Cause(err) != io.EOF {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var ErrNotTimeoutReader = E.New("not a TimeoutReader")
|
||||
|
||||
func CopyOnceTimeout(reader Reader, writer Writer, timeout time.Duration) error {
|
||||
timeoutReader, ok := reader.(TimeoutReader)
|
||||
if !ok {
|
||||
return ErrNotTimeoutReader
|
||||
}
|
||||
mb, err := timeoutReader.ReadMultiBufferTimeout(timeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return writer.WriteMultiBuffer(mb)
|
||||
}
|
||||
126
common/xray/buf/io.go
Normal file
126
common/xray/buf/io.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package buf
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray/stat"
|
||||
"github.com/sagernet/sing-box/common/xray/stats"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
// Reader extends io.Reader with MultiBuffer.
|
||||
type Reader interface {
|
||||
// ReadMultiBuffer reads content from underlying reader, and put it into a MultiBuffer.
|
||||
ReadMultiBuffer() (MultiBuffer, error)
|
||||
}
|
||||
|
||||
// ErrReadTimeout is an error that happens with IO timeout.
|
||||
var ErrReadTimeout = E.New("IO timeout")
|
||||
|
||||
// TimeoutReader is a reader that returns error if Read() operation takes longer than the given timeout.
|
||||
type TimeoutReader interface {
|
||||
ReadMultiBufferTimeout(time.Duration) (MultiBuffer, error)
|
||||
}
|
||||
|
||||
// Writer extends io.Writer with MultiBuffer.
|
||||
type Writer interface {
|
||||
// WriteMultiBuffer writes a MultiBuffer into underlying writer.
|
||||
WriteMultiBuffer(MultiBuffer) error
|
||||
}
|
||||
|
||||
// WriteAllBytes ensures all bytes are written into the given writer.
|
||||
func WriteAllBytes(writer io.Writer, payload []byte, c stats.Counter) error {
|
||||
wc := 0
|
||||
defer func() {
|
||||
if c != nil {
|
||||
c.Add(int64(wc))
|
||||
}
|
||||
}()
|
||||
|
||||
for len(payload) > 0 {
|
||||
n, err := writer.Write(payload)
|
||||
wc += n
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payload = payload[n:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isPacketReader(reader io.Reader) bool {
|
||||
_, ok := reader.(net.PacketConn)
|
||||
return ok
|
||||
}
|
||||
|
||||
// NewReader creates a new Reader.
|
||||
// The Reader instance doesn't take the ownership of reader.
|
||||
func NewReader(reader io.Reader) Reader {
|
||||
if mr, ok := reader.(Reader); ok {
|
||||
return mr
|
||||
}
|
||||
|
||||
if isPacketReader(reader) {
|
||||
return &PacketReader{
|
||||
Reader: reader,
|
||||
}
|
||||
}
|
||||
|
||||
return &SingleReader{
|
||||
Reader: reader,
|
||||
}
|
||||
}
|
||||
|
||||
// NewPacketReader creates a new PacketReader based on the given reader.
|
||||
func NewPacketReader(reader io.Reader) Reader {
|
||||
if mr, ok := reader.(Reader); ok {
|
||||
return mr
|
||||
}
|
||||
|
||||
return &PacketReader{
|
||||
Reader: reader,
|
||||
}
|
||||
}
|
||||
|
||||
func isPacketWriter(writer io.Writer) bool {
|
||||
if _, ok := writer.(net.PacketConn); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the writer doesn't implement syscall.Conn, it is probably not a TCP connection.
|
||||
if _, ok := writer.(syscall.Conn); !ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NewWriter creates a new Writer.
|
||||
func NewWriter(writer io.Writer) Writer {
|
||||
if mw, ok := writer.(Writer); ok {
|
||||
return mw
|
||||
}
|
||||
|
||||
iConn := writer
|
||||
if statConn, ok := writer.(*stat.CounterConnection); ok {
|
||||
iConn = statConn.Connection
|
||||
}
|
||||
|
||||
if isPacketWriter(iConn) {
|
||||
return &SequentialWriter{
|
||||
Writer: writer,
|
||||
}
|
||||
}
|
||||
|
||||
var counter stats.Counter
|
||||
|
||||
if statConn, ok := writer.(*stat.CounterConnection); ok {
|
||||
counter = statConn.WriteCounter
|
||||
}
|
||||
return &BufferToBytesWriter{
|
||||
Writer: iConn,
|
||||
counter: counter,
|
||||
}
|
||||
}
|
||||
310
common/xray/buf/multi_buffer.go
Normal file
310
common/xray/buf/multi_buffer.go
Normal file
@@ -0,0 +1,310 @@
|
||||
package buf
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray"
|
||||
"github.com/sagernet/sing-box/common/xray/errors"
|
||||
"github.com/sagernet/sing-box/common/xray/serial"
|
||||
)
|
||||
|
||||
// ReadAllToBytes reads all content from the reader into a byte array, until EOF.
|
||||
func ReadAllToBytes(reader io.Reader) ([]byte, error) {
|
||||
mb, err := ReadFrom(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if mb.Len() == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
b := make([]byte, mb.Len())
|
||||
mb, _ = SplitBytes(mb, b)
|
||||
ReleaseMulti(mb)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// MultiBuffer is a list of Buffers. The order of Buffer matters.
|
||||
type MultiBuffer []*Buffer
|
||||
|
||||
// MergeMulti merges content from src to dest, and returns the new address of dest and src
|
||||
func MergeMulti(dest MultiBuffer, src MultiBuffer) (MultiBuffer, MultiBuffer) {
|
||||
dest = append(dest, src...)
|
||||
for idx := range src {
|
||||
src[idx] = nil
|
||||
}
|
||||
return dest, src[:0]
|
||||
}
|
||||
|
||||
// MergeBytes merges the given bytes into MultiBuffer and return the new address of the merged MultiBuffer.
|
||||
func MergeBytes(dest MultiBuffer, src []byte) MultiBuffer {
|
||||
n := len(dest)
|
||||
if n > 0 && !(dest)[n-1].IsFull() {
|
||||
nBytes, _ := (dest)[n-1].Write(src)
|
||||
src = src[nBytes:]
|
||||
}
|
||||
|
||||
for len(src) > 0 {
|
||||
b := New()
|
||||
nBytes, _ := b.Write(src)
|
||||
src = src[nBytes:]
|
||||
dest = append(dest, b)
|
||||
}
|
||||
|
||||
return dest
|
||||
}
|
||||
|
||||
// ReleaseMulti releases all content of the MultiBuffer, and returns an empty MultiBuffer.
|
||||
func ReleaseMulti(mb MultiBuffer) MultiBuffer {
|
||||
for i := range mb {
|
||||
mb[i].Release()
|
||||
mb[i] = nil
|
||||
}
|
||||
return mb[:0]
|
||||
}
|
||||
|
||||
// Copy copied the beginning part of the MultiBuffer into the given byte array.
|
||||
func (mb MultiBuffer) Copy(b []byte) int {
|
||||
total := 0
|
||||
for _, bb := range mb {
|
||||
nBytes := copy(b[total:], bb.Bytes())
|
||||
total += nBytes
|
||||
if int32(nBytes) < bb.Len() {
|
||||
break
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// ReadFrom reads all content from reader until EOF.
|
||||
func ReadFrom(reader io.Reader) (MultiBuffer, error) {
|
||||
mb := make(MultiBuffer, 0, 16)
|
||||
for {
|
||||
b := New()
|
||||
_, err := b.ReadFullFrom(reader, Size)
|
||||
if b.IsEmpty() {
|
||||
b.Release()
|
||||
} else {
|
||||
mb = append(mb, b)
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Cause(err) == io.EOF || errors.Cause(err) == io.ErrUnexpectedEOF {
|
||||
return mb, nil
|
||||
}
|
||||
return mb, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SplitBytes splits the given amount of bytes from the beginning of the MultiBuffer.
|
||||
// It returns the new address of MultiBuffer leftover, and number of bytes written into the input byte slice.
|
||||
func SplitBytes(mb MultiBuffer, b []byte) (MultiBuffer, int) {
|
||||
totalBytes := 0
|
||||
endIndex := -1
|
||||
for i := range mb {
|
||||
pBuffer := mb[i]
|
||||
nBytes, _ := pBuffer.Read(b)
|
||||
totalBytes += nBytes
|
||||
b = b[nBytes:]
|
||||
if !pBuffer.IsEmpty() {
|
||||
endIndex = i
|
||||
break
|
||||
}
|
||||
pBuffer.Release()
|
||||
mb[i] = nil
|
||||
}
|
||||
|
||||
if endIndex == -1 {
|
||||
mb = mb[:0]
|
||||
} else {
|
||||
mb = mb[endIndex:]
|
||||
}
|
||||
|
||||
return mb, totalBytes
|
||||
}
|
||||
|
||||
// SplitFirstBytes splits the first buffer from MultiBuffer, and then copy its content into the given slice.
|
||||
func SplitFirstBytes(mb MultiBuffer, p []byte) (MultiBuffer, int) {
|
||||
mb, b := SplitFirst(mb)
|
||||
if b == nil {
|
||||
return mb, 0
|
||||
}
|
||||
n := copy(p, b.Bytes())
|
||||
b.Release()
|
||||
return mb, n
|
||||
}
|
||||
|
||||
// Compact returns another MultiBuffer by merging all content of the given one together.
|
||||
func Compact(mb MultiBuffer) MultiBuffer {
|
||||
if len(mb) == 0 {
|
||||
return mb
|
||||
}
|
||||
|
||||
mb2 := make(MultiBuffer, 0, len(mb))
|
||||
last := mb[0]
|
||||
|
||||
for i := 1; i < len(mb); i++ {
|
||||
curr := mb[i]
|
||||
if last.Len()+curr.Len() > Size {
|
||||
mb2 = append(mb2, last)
|
||||
last = curr
|
||||
} else {
|
||||
common.Must2(last.ReadFrom(curr))
|
||||
curr.Release()
|
||||
}
|
||||
}
|
||||
|
||||
mb2 = append(mb2, last)
|
||||
return mb2
|
||||
}
|
||||
|
||||
// SplitFirst splits the first Buffer from the beginning of the MultiBuffer.
|
||||
func SplitFirst(mb MultiBuffer) (MultiBuffer, *Buffer) {
|
||||
if len(mb) == 0 {
|
||||
return mb, nil
|
||||
}
|
||||
|
||||
b := mb[0]
|
||||
mb[0] = nil
|
||||
mb = mb[1:]
|
||||
return mb, b
|
||||
}
|
||||
|
||||
// SplitSize splits the beginning of the MultiBuffer into another one, for at most size bytes.
|
||||
func SplitSize(mb MultiBuffer, size int32) (MultiBuffer, MultiBuffer) {
|
||||
if len(mb) == 0 {
|
||||
return mb, nil
|
||||
}
|
||||
|
||||
if mb[0].Len() > size {
|
||||
b := New()
|
||||
copy(b.Extend(size), mb[0].BytesTo(size))
|
||||
mb[0].Advance(size)
|
||||
return mb, MultiBuffer{b}
|
||||
}
|
||||
|
||||
totalBytes := int32(0)
|
||||
var r MultiBuffer
|
||||
endIndex := -1
|
||||
for i := range mb {
|
||||
if totalBytes+mb[i].Len() > size {
|
||||
endIndex = i
|
||||
break
|
||||
}
|
||||
totalBytes += mb[i].Len()
|
||||
r = append(r, mb[i])
|
||||
mb[i] = nil
|
||||
}
|
||||
if endIndex == -1 {
|
||||
// To reuse mb array
|
||||
mb = mb[:0]
|
||||
} else {
|
||||
mb = mb[endIndex:]
|
||||
}
|
||||
return mb, r
|
||||
}
|
||||
|
||||
// SplitMulti splits the beginning of the MultiBuffer into first one, the index i and after into second one
|
||||
func SplitMulti(mb MultiBuffer, i int) (MultiBuffer, MultiBuffer) {
|
||||
mb2 := make(MultiBuffer, 0, len(mb))
|
||||
if i < len(mb) && i >= 0 {
|
||||
mb2 = append(mb2, mb[i:]...)
|
||||
for j := i; j < len(mb); j++ {
|
||||
mb[j] = nil
|
||||
}
|
||||
mb = mb[:i]
|
||||
}
|
||||
return mb, mb2
|
||||
}
|
||||
|
||||
// WriteMultiBuffer writes all buffers from the MultiBuffer to the Writer one by one, and return error if any, with leftover MultiBuffer.
|
||||
func WriteMultiBuffer(writer io.Writer, mb MultiBuffer) (MultiBuffer, error) {
|
||||
for {
|
||||
mb2, b := SplitFirst(mb)
|
||||
mb = mb2
|
||||
if b == nil {
|
||||
break
|
||||
}
|
||||
|
||||
_, err := writer.Write(b.Bytes())
|
||||
b.Release()
|
||||
if err != nil {
|
||||
return mb, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Len returns the total number of bytes in the MultiBuffer.
|
||||
func (mb MultiBuffer) Len() int32 {
|
||||
if mb == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
size := int32(0)
|
||||
for _, b := range mb {
|
||||
size += b.Len()
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// IsEmpty returns true if the MultiBuffer has no content.
|
||||
func (mb MultiBuffer) IsEmpty() bool {
|
||||
for _, b := range mb {
|
||||
if !b.IsEmpty() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// String returns the content of the MultiBuffer in string.
|
||||
func (mb MultiBuffer) String() string {
|
||||
v := make([]interface{}, len(mb))
|
||||
for i, b := range mb {
|
||||
v[i] = b
|
||||
}
|
||||
return serial.Concat(v...)
|
||||
}
|
||||
|
||||
// MultiBufferContainer is a ReadWriteCloser wrapper over MultiBuffer.
|
||||
type MultiBufferContainer struct {
|
||||
MultiBuffer
|
||||
}
|
||||
|
||||
// Read implements io.Reader.
|
||||
func (c *MultiBufferContainer) Read(b []byte) (int, error) {
|
||||
if c.MultiBuffer.IsEmpty() {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
mb, nBytes := SplitBytes(c.MultiBuffer, b)
|
||||
c.MultiBuffer = mb
|
||||
return nBytes, nil
|
||||
}
|
||||
|
||||
// ReadMultiBuffer implements Reader.
|
||||
func (c *MultiBufferContainer) ReadMultiBuffer() (MultiBuffer, error) {
|
||||
mb := c.MultiBuffer
|
||||
c.MultiBuffer = nil
|
||||
return mb, nil
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (c *MultiBufferContainer) Write(b []byte) (int, error) {
|
||||
c.MultiBuffer = MergeBytes(c.MultiBuffer, b)
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// WriteMultiBuffer implements Writer.
|
||||
func (c *MultiBufferContainer) WriteMultiBuffer(b MultiBuffer) error {
|
||||
mb, _ := MergeMulti(c.MultiBuffer, b)
|
||||
c.MultiBuffer = mb
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements io.Closer.
|
||||
func (c *MultiBufferContainer) Close() error {
|
||||
c.MultiBuffer = ReleaseMulti(c.MultiBuffer)
|
||||
return nil
|
||||
}
|
||||
38
common/xray/buf/override.go
Normal file
38
common/xray/buf/override.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package buf
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/common/xray/net"
|
||||
)
|
||||
|
||||
type EndpointOverrideReader struct {
|
||||
Reader
|
||||
Dest net.Address
|
||||
OriginalDest net.Address
|
||||
}
|
||||
|
||||
func (r *EndpointOverrideReader) ReadMultiBuffer() (MultiBuffer, error) {
|
||||
mb, err := r.Reader.ReadMultiBuffer()
|
||||
if err == nil {
|
||||
for _, b := range mb {
|
||||
if b.UDP != nil && b.UDP.Address == r.OriginalDest {
|
||||
b.UDP.Address = r.Dest
|
||||
}
|
||||
}
|
||||
}
|
||||
return mb, err
|
||||
}
|
||||
|
||||
type EndpointOverrideWriter struct {
|
||||
Writer
|
||||
Dest net.Address
|
||||
OriginalDest net.Address
|
||||
}
|
||||
|
||||
func (w *EndpointOverrideWriter) WriteMultiBuffer(mb MultiBuffer) error {
|
||||
for _, b := range mb {
|
||||
if b.UDP != nil && b.UDP.Address == w.Dest {
|
||||
b.UDP.Address = w.OriginalDest
|
||||
}
|
||||
}
|
||||
return w.Writer.WriteMultiBuffer(mb)
|
||||
}
|
||||
175
common/xray/buf/reader.go
Normal file
175
common/xray/buf/reader.go
Normal file
@@ -0,0 +1,175 @@
|
||||
package buf
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray"
|
||||
"github.com/sagernet/sing-box/common/xray/errors"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func readOneUDP(r io.Reader) (*Buffer, error) {
|
||||
b := New()
|
||||
for i := 0; i < 64; i++ {
|
||||
_, err := b.ReadFrom(r)
|
||||
if !b.IsEmpty() {
|
||||
return b, nil
|
||||
}
|
||||
if err != nil {
|
||||
b.Release()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
b.Release()
|
||||
return nil, E.New("Reader returns too many empty payloads.")
|
||||
}
|
||||
|
||||
// ReadBuffer reads a Buffer from the given reader.
|
||||
func ReadBuffer(r io.Reader) (*Buffer, error) {
|
||||
b := New()
|
||||
n, err := b.ReadFrom(r)
|
||||
if n > 0 {
|
||||
return b, err
|
||||
}
|
||||
b.Release()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// BufferedReader is a Reader that keeps its internal buffer.
|
||||
type BufferedReader struct {
|
||||
// Reader is the underlying reader to be read from
|
||||
Reader Reader
|
||||
// Buffer is the internal buffer to be read from first
|
||||
Buffer MultiBuffer
|
||||
// Splitter is a function to read bytes from MultiBuffer
|
||||
Splitter func(MultiBuffer, []byte) (MultiBuffer, int)
|
||||
}
|
||||
|
||||
// BufferedBytes returns the number of bytes that is cached in this reader.
|
||||
func (r *BufferedReader) BufferedBytes() int32 {
|
||||
return r.Buffer.Len()
|
||||
}
|
||||
|
||||
// ReadByte implements io.ByteReader.
|
||||
func (r *BufferedReader) ReadByte() (byte, error) {
|
||||
var b [1]byte
|
||||
_, err := r.Read(b[:])
|
||||
return b[0], err
|
||||
}
|
||||
|
||||
// Read implements io.Reader. It reads from internal buffer first (if available) and then reads from the underlying reader.
|
||||
func (r *BufferedReader) Read(b []byte) (int, error) {
|
||||
spliter := r.Splitter
|
||||
if spliter == nil {
|
||||
spliter = SplitBytes
|
||||
}
|
||||
|
||||
if !r.Buffer.IsEmpty() {
|
||||
buffer, nBytes := spliter(r.Buffer, b)
|
||||
r.Buffer = buffer
|
||||
if r.Buffer.IsEmpty() {
|
||||
r.Buffer = nil
|
||||
}
|
||||
return nBytes, nil
|
||||
}
|
||||
|
||||
mb, err := r.Reader.ReadMultiBuffer()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
mb, nBytes := spliter(mb, b)
|
||||
if !mb.IsEmpty() {
|
||||
r.Buffer = mb
|
||||
}
|
||||
return nBytes, nil
|
||||
}
|
||||
|
||||
// ReadMultiBuffer implements Reader.
|
||||
func (r *BufferedReader) ReadMultiBuffer() (MultiBuffer, error) {
|
||||
if !r.Buffer.IsEmpty() {
|
||||
mb := r.Buffer
|
||||
r.Buffer = nil
|
||||
return mb, nil
|
||||
}
|
||||
|
||||
return r.Reader.ReadMultiBuffer()
|
||||
}
|
||||
|
||||
// ReadAtMost returns a MultiBuffer with at most size.
|
||||
func (r *BufferedReader) ReadAtMost(size int32) (MultiBuffer, error) {
|
||||
if r.Buffer.IsEmpty() {
|
||||
mb, err := r.Reader.ReadMultiBuffer()
|
||||
if mb.IsEmpty() && err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Buffer = mb
|
||||
}
|
||||
|
||||
rb, mb := SplitSize(r.Buffer, size)
|
||||
r.Buffer = rb
|
||||
if r.Buffer.IsEmpty() {
|
||||
r.Buffer = nil
|
||||
}
|
||||
return mb, nil
|
||||
}
|
||||
|
||||
func (r *BufferedReader) writeToInternal(writer io.Writer) (int64, error) {
|
||||
mbWriter := NewWriter(writer)
|
||||
var sc SizeCounter
|
||||
if r.Buffer != nil {
|
||||
sc.Size = int64(r.Buffer.Len())
|
||||
if err := mbWriter.WriteMultiBuffer(r.Buffer); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
r.Buffer = nil
|
||||
}
|
||||
|
||||
err := Copy(r.Reader, mbWriter, CountSize(&sc))
|
||||
return sc.Size, err
|
||||
}
|
||||
|
||||
// WriteTo implements io.WriterTo.
|
||||
func (r *BufferedReader) WriteTo(writer io.Writer) (int64, error) {
|
||||
nBytes, err := r.writeToInternal(writer)
|
||||
if errors.Cause(err) == io.EOF {
|
||||
return nBytes, nil
|
||||
}
|
||||
return nBytes, err
|
||||
}
|
||||
|
||||
// Interrupt implements common.Interruptible.
|
||||
func (r *BufferedReader) Interrupt() {
|
||||
common.Interrupt(r.Reader)
|
||||
}
|
||||
|
||||
// Close implements io.Closer.
|
||||
func (r *BufferedReader) Close() error {
|
||||
return common.Close(r.Reader)
|
||||
}
|
||||
|
||||
// SingleReader is a Reader that read one Buffer every time.
|
||||
type SingleReader struct {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
// ReadMultiBuffer implements Reader.
|
||||
func (r *SingleReader) ReadMultiBuffer() (MultiBuffer, error) {
|
||||
b, err := ReadBuffer(r.Reader)
|
||||
return MultiBuffer{b}, err
|
||||
}
|
||||
|
||||
// PacketReader is a Reader that read one Buffer every time.
|
||||
type PacketReader struct {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
// ReadMultiBuffer implements Reader.
|
||||
func (r *PacketReader) ReadMultiBuffer() (MultiBuffer, error) {
|
||||
b, err := readOneUDP(r.Reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return MultiBuffer{b}, nil
|
||||
}
|
||||
270
common/xray/buf/writer.go
Normal file
270
common/xray/buf/writer.go
Normal file
@@ -0,0 +1,270 @@
|
||||
package buf
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray"
|
||||
"github.com/sagernet/sing-box/common/xray/errors"
|
||||
"github.com/sagernet/sing-box/common/xray/stats"
|
||||
)
|
||||
|
||||
// BufferToBytesWriter is a Writer that writes alloc.Buffer into underlying writer.
|
||||
type BufferToBytesWriter struct {
|
||||
io.Writer
|
||||
|
||||
counter stats.Counter
|
||||
cache [][]byte
|
||||
}
|
||||
|
||||
// WriteMultiBuffer implements Writer. This method takes ownership of the given buffer.
|
||||
func (w *BufferToBytesWriter) WriteMultiBuffer(mb MultiBuffer) error {
|
||||
defer ReleaseMulti(mb)
|
||||
|
||||
size := mb.Len()
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(mb) == 1 {
|
||||
return WriteAllBytes(w.Writer, mb[0].Bytes(), w.counter)
|
||||
}
|
||||
|
||||
if cap(w.cache) < len(mb) {
|
||||
w.cache = make([][]byte, 0, len(mb))
|
||||
}
|
||||
|
||||
bs := w.cache
|
||||
for _, b := range mb {
|
||||
bs = append(bs, b.Bytes())
|
||||
}
|
||||
|
||||
defer func() {
|
||||
for idx := range bs {
|
||||
bs[idx] = nil
|
||||
}
|
||||
}()
|
||||
|
||||
nb := net.Buffers(bs)
|
||||
wc := int64(0)
|
||||
defer func() {
|
||||
if w.counter != nil {
|
||||
w.counter.Add(wc)
|
||||
}
|
||||
}()
|
||||
for size > 0 {
|
||||
n, err := nb.WriteTo(w.Writer)
|
||||
wc += n
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
size -= int32(n)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadFrom implements io.ReaderFrom.
|
||||
func (w *BufferToBytesWriter) ReadFrom(reader io.Reader) (int64, error) {
|
||||
var sc SizeCounter
|
||||
err := Copy(NewReader(reader), w, CountSize(&sc))
|
||||
return sc.Size, err
|
||||
}
|
||||
|
||||
// BufferedWriter is a Writer with internal buffer.
|
||||
type BufferedWriter struct {
|
||||
sync.Mutex
|
||||
writer Writer
|
||||
buffer *Buffer
|
||||
buffered bool
|
||||
}
|
||||
|
||||
// NewBufferedWriter creates a new BufferedWriter.
|
||||
func NewBufferedWriter(writer Writer) *BufferedWriter {
|
||||
return &BufferedWriter{
|
||||
writer: writer,
|
||||
buffer: New(),
|
||||
buffered: true,
|
||||
}
|
||||
}
|
||||
|
||||
// WriteByte implements io.ByteWriter.
|
||||
func (w *BufferedWriter) WriteByte(c byte) error {
|
||||
return common.Error2(w.Write([]byte{c}))
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (w *BufferedWriter) Write(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
if !w.buffered {
|
||||
if writer, ok := w.writer.(io.Writer); ok {
|
||||
return writer.Write(b)
|
||||
}
|
||||
}
|
||||
|
||||
totalBytes := 0
|
||||
for len(b) > 0 {
|
||||
if w.buffer == nil {
|
||||
w.buffer = New()
|
||||
}
|
||||
|
||||
nBytes, err := w.buffer.Write(b)
|
||||
totalBytes += nBytes
|
||||
if err != nil {
|
||||
return totalBytes, err
|
||||
}
|
||||
if !w.buffered || w.buffer.IsFull() {
|
||||
if err := w.flushInternal(); err != nil {
|
||||
return totalBytes, err
|
||||
}
|
||||
}
|
||||
b = b[nBytes:]
|
||||
}
|
||||
|
||||
return totalBytes, nil
|
||||
}
|
||||
|
||||
// WriteMultiBuffer implements Writer. It takes ownership of the given MultiBuffer.
|
||||
func (w *BufferedWriter) WriteMultiBuffer(b MultiBuffer) error {
|
||||
if b.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
if !w.buffered {
|
||||
return w.writer.WriteMultiBuffer(b)
|
||||
}
|
||||
|
||||
reader := MultiBufferContainer{
|
||||
MultiBuffer: b,
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
for !reader.MultiBuffer.IsEmpty() {
|
||||
if w.buffer == nil {
|
||||
w.buffer = New()
|
||||
}
|
||||
common.Must2(w.buffer.ReadFrom(&reader))
|
||||
if w.buffer.IsFull() {
|
||||
if err := w.flushInternal(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush flushes buffered content into underlying writer.
|
||||
func (w *BufferedWriter) Flush() error {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
return w.flushInternal()
|
||||
}
|
||||
|
||||
func (w *BufferedWriter) flushInternal() error {
|
||||
if w.buffer.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
b := w.buffer
|
||||
w.buffer = nil
|
||||
|
||||
if writer, ok := w.writer.(io.Writer); ok {
|
||||
err := WriteAllBytes(writer, b.Bytes(), nil)
|
||||
b.Release()
|
||||
return err
|
||||
}
|
||||
|
||||
return w.writer.WriteMultiBuffer(MultiBuffer{b})
|
||||
}
|
||||
|
||||
// SetBuffered sets whether the internal buffer is used. If set to false, Flush() will be called to clear the buffer.
|
||||
func (w *BufferedWriter) SetBuffered(f bool) error {
|
||||
w.Lock()
|
||||
defer w.Unlock()
|
||||
|
||||
w.buffered = f
|
||||
if !f {
|
||||
return w.flushInternal()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadFrom implements io.ReaderFrom.
|
||||
func (w *BufferedWriter) ReadFrom(reader io.Reader) (int64, error) {
|
||||
if err := w.SetBuffered(false); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var sc SizeCounter
|
||||
err := Copy(NewReader(reader), w, CountSize(&sc))
|
||||
return sc.Size, err
|
||||
}
|
||||
|
||||
// Close implements io.Closable.
|
||||
func (w *BufferedWriter) Close() error {
|
||||
if err := w.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
return common.Close(w.writer)
|
||||
}
|
||||
|
||||
// SequentialWriter is a Writer that writes MultiBuffer sequentially into the underlying io.Writer.
|
||||
type SequentialWriter struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// WriteMultiBuffer implements Writer.
|
||||
func (w *SequentialWriter) WriteMultiBuffer(mb MultiBuffer) error {
|
||||
mb, err := WriteMultiBuffer(w.Writer, mb)
|
||||
ReleaseMulti(mb)
|
||||
return err
|
||||
}
|
||||
|
||||
type noOpWriter byte
|
||||
|
||||
func (noOpWriter) WriteMultiBuffer(b MultiBuffer) error {
|
||||
ReleaseMulti(b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (noOpWriter) Write(b []byte) (int, error) {
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func (noOpWriter) ReadFrom(reader io.Reader) (int64, error) {
|
||||
b := New()
|
||||
defer b.Release()
|
||||
|
||||
totalBytes := int64(0)
|
||||
for {
|
||||
b.Clear()
|
||||
_, err := b.ReadFrom(reader)
|
||||
totalBytes += int64(b.Len())
|
||||
if err != nil {
|
||||
if errors.Cause(err) == io.EOF {
|
||||
return totalBytes, nil
|
||||
}
|
||||
return totalBytes, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// Discard is a Writer that swallows all contents written in.
|
||||
Discard Writer = noOpWriter(0)
|
||||
|
||||
// DiscardBytes is an io.Writer that swallows all contents written in.
|
||||
DiscardBytes io.Writer = noOpWriter(0)
|
||||
)
|
||||
72
common/xray/bytespool/pool.go
Normal file
72
common/xray/bytespool/pool.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package bytespool
|
||||
|
||||
import "sync"
|
||||
|
||||
func createAllocFunc(size int32) func() interface{} {
|
||||
return func() interface{} {
|
||||
return make([]byte, size)
|
||||
}
|
||||
}
|
||||
|
||||
// The following parameters controls the size of buffer pools.
|
||||
// There are numPools pools. Starting from 2k size, the size of each pool is sizeMulti of the previous one.
|
||||
// Package buf is guaranteed to not use buffers larger than the largest pool.
|
||||
// Other packets may use larger buffers.
|
||||
const (
|
||||
numPools = 4
|
||||
sizeMulti = 4
|
||||
)
|
||||
|
||||
var (
|
||||
pool [numPools]sync.Pool
|
||||
poolSize [numPools]int32
|
||||
)
|
||||
|
||||
func init() {
|
||||
size := int32(2048)
|
||||
for i := 0; i < numPools; i++ {
|
||||
pool[i] = sync.Pool{
|
||||
New: createAllocFunc(size),
|
||||
}
|
||||
poolSize[i] = size
|
||||
size *= sizeMulti
|
||||
}
|
||||
}
|
||||
|
||||
// GetPool returns a sync.Pool that generates bytes array with at least the given size.
|
||||
// It may return nil if no such pool exists.
|
||||
//
|
||||
// xray:api:stable
|
||||
func GetPool(size int32) *sync.Pool {
|
||||
for idx, ps := range poolSize {
|
||||
if size <= ps {
|
||||
return &pool[idx]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Alloc returns a byte slice with at least the given size. Minimum size of returned slice is 2048.
|
||||
//
|
||||
// xray:api:stable
|
||||
func Alloc(size int32) []byte {
|
||||
pool := GetPool(size)
|
||||
if pool != nil {
|
||||
return pool.Get().([]byte)
|
||||
}
|
||||
return make([]byte, size)
|
||||
}
|
||||
|
||||
// Free puts a byte slice into the internal pool.
|
||||
//
|
||||
// xray:api:stable
|
||||
func Free(b []byte) {
|
||||
size := int32(cap(b))
|
||||
b = b[0:cap(b)]
|
||||
for i := numPools - 1; i >= 0; i-- {
|
||||
if size >= poolSize[i] {
|
||||
pool[i].Put(b)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
19
common/xray/common.go
Normal file
19
common/xray/common.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package common
|
||||
|
||||
// Must panics if err is not nil.
|
||||
func Must(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Must2 panics if the second parameter is not nil, otherwise returns the first parameter.
|
||||
func Must2(v interface{}, err error) interface{} {
|
||||
Must(err)
|
||||
return v
|
||||
}
|
||||
|
||||
// Error2 returns the err from the 2nd parameter.
|
||||
func Error2(v interface{}, err error) error {
|
||||
return err
|
||||
}
|
||||
14
common/xray/crypto/crypto.go
Normal file
14
common/xray/crypto/crypto.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
func RandBetween(from int64, to int64) int64 {
|
||||
if from == to {
|
||||
return from
|
||||
}
|
||||
bigInt, _ := rand.Int(rand.Reader, big.NewInt(to-from))
|
||||
return from + bigInt.Int64()
|
||||
}
|
||||
25
common/xray/errors/errors.go
Normal file
25
common/xray/errors/errors.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package errors
|
||||
|
||||
type hasInnerError interface {
|
||||
// Unwrap returns the underlying error of this one.
|
||||
Unwrap() error
|
||||
}
|
||||
|
||||
func Cause(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
L:
|
||||
for {
|
||||
switch inner := err.(type) {
|
||||
case hasInnerError:
|
||||
if inner.Unwrap() == nil {
|
||||
break L
|
||||
}
|
||||
err = inner.Unwrap()
|
||||
default:
|
||||
break L
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
52
common/xray/interfaces.go
Normal file
52
common/xray/interfaces.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package common
|
||||
|
||||
// Closable is the interface for objects that can release its resources.
|
||||
//
|
||||
// xray:api:beta
|
||||
type Closable interface {
|
||||
// Close release all resources used by this object, including goroutines.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Interruptible is an interface for objects that can be stopped before its completion.
|
||||
//
|
||||
// xray:api:beta
|
||||
type Interruptible interface {
|
||||
Interrupt()
|
||||
}
|
||||
|
||||
// Close closes the obj if it is a Closable.
|
||||
//
|
||||
// xray:api:beta
|
||||
func Close(obj interface{}) error {
|
||||
if c, ok := obj.(Closable); ok {
|
||||
return c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Interrupt calls Interrupt() if object implements Interruptible interface, or Close() if the object implements Closable interface.
|
||||
//
|
||||
// xray:api:beta
|
||||
func Interrupt(obj interface{}) error {
|
||||
if c, ok := obj.(Interruptible); ok {
|
||||
c.Interrupt()
|
||||
return nil
|
||||
}
|
||||
return Close(obj)
|
||||
}
|
||||
|
||||
// Runnable is the interface for objects that can start to work and stop on demand.
|
||||
type Runnable interface {
|
||||
// Start starts the runnable object. Upon the method returning nil, the object begins to function properly.
|
||||
Start() error
|
||||
|
||||
Closable
|
||||
}
|
||||
|
||||
// HasType is the interface for objects that knows its type.
|
||||
type HasType interface {
|
||||
// Type returns the type of the object.
|
||||
// Usually it returns (*Type)(nil) of the object.
|
||||
Type() interface{}
|
||||
}
|
||||
62
common/xray/json/badoption/range.go
Normal file
62
common/xray/json/badoption/range.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package badoption
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray/crypto"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type Range struct {
|
||||
From int32 `json:"from"`
|
||||
To int32 `json:"to"`
|
||||
}
|
||||
|
||||
func (c *Range) Build() *Range {
|
||||
return (*Range)(c)
|
||||
}
|
||||
|
||||
func (c *Range) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(fmt.Sprintf("%d-%d", c.From, c.To))
|
||||
}
|
||||
|
||||
func (c *Range) UnmarshalJSON(content []byte) error {
|
||||
var rangeValue struct {
|
||||
From int32 `json:"from"`
|
||||
To int32 `json:"to"`
|
||||
}
|
||||
var stringValue string
|
||||
err := json.Unmarshal(content, &stringValue)
|
||||
if err == nil {
|
||||
parts := strings.Split(stringValue, "-")
|
||||
if len(parts) != 2 {
|
||||
return E.New("invalid length of range parts")
|
||||
}
|
||||
from, err := strconv.ParseInt(parts[0], 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
to, err := strconv.ParseInt(parts[1], 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rangeValue.From, rangeValue.To = int32(from), int32(to)
|
||||
} else {
|
||||
err := json.Unmarshal(content, &rangeValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rangeValue.From > rangeValue.To {
|
||||
return E.New("invalid range")
|
||||
}
|
||||
*c = Range{rangeValue.From, rangeValue.To}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Range) Rand() int32 {
|
||||
return int32(crypto.RandBetween(int64(c.From), int64(c.To)))
|
||||
}
|
||||
181
common/xray/net/address.go
Normal file
181
common/xray/net/address.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// LocalHostIP is a constant value for localhost IP in IPv4.
|
||||
LocalHostIP = IPAddress([]byte{127, 0, 0, 1})
|
||||
|
||||
// AnyIP is a constant value for any IP in IPv4.
|
||||
AnyIP = IPAddress([]byte{0, 0, 0, 0})
|
||||
|
||||
// LocalHostDomain is a constant value for localhost domain.
|
||||
LocalHostDomain = DomainAddress("localhost")
|
||||
|
||||
// LocalHostIPv6 is a constant value for localhost IP in IPv6.
|
||||
LocalHostIPv6 = IPAddress([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})
|
||||
|
||||
// AnyIPv6 is a constant value for any IP in IPv6.
|
||||
AnyIPv6 = IPAddress([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||
)
|
||||
|
||||
// AddressFamily is the type of address.
|
||||
type AddressFamily byte
|
||||
|
||||
const (
|
||||
// AddressFamilyIPv4 represents address as IPv4
|
||||
AddressFamilyIPv4 = AddressFamily(0)
|
||||
|
||||
// AddressFamilyIPv6 represents address as IPv6
|
||||
AddressFamilyIPv6 = AddressFamily(1)
|
||||
|
||||
// AddressFamilyDomain represents address as Domain
|
||||
AddressFamilyDomain = AddressFamily(2)
|
||||
)
|
||||
|
||||
// IsIPv4 returns true if current AddressFamily is IPv4.
|
||||
func (af AddressFamily) IsIPv4() bool {
|
||||
return af == AddressFamilyIPv4
|
||||
}
|
||||
|
||||
// IsIPv6 returns true if current AddressFamily is IPv6.
|
||||
func (af AddressFamily) IsIPv6() bool {
|
||||
return af == AddressFamilyIPv6
|
||||
}
|
||||
|
||||
// IsIP returns true if current AddressFamily is IPv6 or IPv4.
|
||||
func (af AddressFamily) IsIP() bool {
|
||||
return af == AddressFamilyIPv4 || af == AddressFamilyIPv6
|
||||
}
|
||||
|
||||
// IsDomain returns true if current AddressFamily is Domain.
|
||||
func (af AddressFamily) IsDomain() bool {
|
||||
return af == AddressFamilyDomain
|
||||
}
|
||||
|
||||
// Address represents a network address to be communicated with. It may be an IP address or domain
|
||||
// address, not both. This interface doesn't resolve IP address for a given domain.
|
||||
type Address interface {
|
||||
IP() net.IP // IP of this Address
|
||||
Domain() string // Domain of this Address
|
||||
Family() AddressFamily
|
||||
|
||||
String() string // String representation of this Address
|
||||
}
|
||||
|
||||
func isAlphaNum(c byte) bool {
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||
}
|
||||
|
||||
// ParseAddress parses a string into an Address. The return value will be an IPAddress when
|
||||
// the string is in the form of IPv4 or IPv6 address, or a DomainAddress otherwise.
|
||||
func ParseAddress(addr string) Address {
|
||||
// Handle IPv6 address in form as "[2001:4860:0:2001::68]"
|
||||
lenAddr := len(addr)
|
||||
if lenAddr > 0 && addr[0] == '[' && addr[lenAddr-1] == ']' {
|
||||
addr = addr[1 : lenAddr-1]
|
||||
lenAddr -= 2
|
||||
}
|
||||
|
||||
if lenAddr > 0 && (!isAlphaNum(addr[0]) || !isAlphaNum(addr[len(addr)-1])) {
|
||||
addr = strings.TrimSpace(addr)
|
||||
}
|
||||
|
||||
ip := net.ParseIP(addr)
|
||||
if ip != nil {
|
||||
return IPAddress(ip)
|
||||
}
|
||||
return DomainAddress(addr)
|
||||
}
|
||||
|
||||
var bytes0 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
|
||||
// IPAddress creates an Address with given IP.
|
||||
func IPAddress(ip []byte) Address {
|
||||
switch len(ip) {
|
||||
case net.IPv4len:
|
||||
var addr ipv4Address = [4]byte{ip[0], ip[1], ip[2], ip[3]}
|
||||
return addr
|
||||
case net.IPv6len:
|
||||
if bytes.Equal(ip[:10], bytes0) && ip[10] == 0xff && ip[11] == 0xff {
|
||||
return IPAddress(ip[12:16])
|
||||
}
|
||||
var addr ipv6Address = [16]byte{
|
||||
ip[0], ip[1], ip[2], ip[3],
|
||||
ip[4], ip[5], ip[6], ip[7],
|
||||
ip[8], ip[9], ip[10], ip[11],
|
||||
ip[12], ip[13], ip[14], ip[15],
|
||||
}
|
||||
return addr
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DomainAddress creates an Address with given domain.
|
||||
// This is an internal function that forcibly converts a string to domain.
|
||||
// It's mainly used in test files and mux.
|
||||
// Unless you have a specific reason, use net.ParseAddress instead,
|
||||
// as this function does not check whether the input is an IP address.
|
||||
// Otherwise, you will get strange results like domain: 1.1.1.1
|
||||
func DomainAddress(domain string) Address {
|
||||
return domainAddress(domain)
|
||||
}
|
||||
|
||||
type ipv4Address [4]byte
|
||||
|
||||
func (a ipv4Address) IP() net.IP {
|
||||
return net.IP(a[:])
|
||||
}
|
||||
|
||||
func (ipv4Address) Domain() string {
|
||||
panic("Calling Domain() on an IPv4Address.")
|
||||
}
|
||||
|
||||
func (ipv4Address) Family() AddressFamily {
|
||||
return AddressFamilyIPv4
|
||||
}
|
||||
|
||||
func (a ipv4Address) String() string {
|
||||
return a.IP().String()
|
||||
}
|
||||
|
||||
type ipv6Address [16]byte
|
||||
|
||||
func (a ipv6Address) IP() net.IP {
|
||||
return net.IP(a[:])
|
||||
}
|
||||
|
||||
func (ipv6Address) Domain() string {
|
||||
panic("Calling Domain() on an IPv6Address.")
|
||||
}
|
||||
|
||||
func (ipv6Address) Family() AddressFamily {
|
||||
return AddressFamilyIPv6
|
||||
}
|
||||
|
||||
func (a ipv6Address) String() string {
|
||||
return "[" + a.IP().String() + "]"
|
||||
}
|
||||
|
||||
type domainAddress string
|
||||
|
||||
func (domainAddress) IP() net.IP {
|
||||
panic("Calling IP() on a DomainAddress.")
|
||||
}
|
||||
|
||||
func (a domainAddress) Domain() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
func (domainAddress) Family() AddressFamily {
|
||||
return AddressFamilyDomain
|
||||
}
|
||||
|
||||
func (a domainAddress) String() string {
|
||||
return a.Domain()
|
||||
}
|
||||
146
common/xray/net/destination.go
Normal file
146
common/xray/net/destination.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Destination represents a network destination including address and protocol (tcp / udp).
|
||||
type Destination struct {
|
||||
Address Address
|
||||
Port Port
|
||||
Network Network
|
||||
}
|
||||
|
||||
// DestinationFromAddr generates a Destination from a net address.
|
||||
func DestinationFromAddr(addr net.Addr) Destination {
|
||||
switch addr := addr.(type) {
|
||||
case *net.TCPAddr:
|
||||
return TCPDestination(IPAddress(addr.IP), Port(addr.Port))
|
||||
case *net.UDPAddr:
|
||||
return UDPDestination(IPAddress(addr.IP), Port(addr.Port))
|
||||
case *net.UnixAddr:
|
||||
return UnixDestination(DomainAddress(addr.Name))
|
||||
default:
|
||||
panic("Net: Unknown address type.")
|
||||
}
|
||||
}
|
||||
|
||||
// ParseDestination converts a destination from its string presentation.
|
||||
func ParseDestination(dest string) (Destination, error) {
|
||||
d := Destination{
|
||||
Address: AnyIP,
|
||||
Port: Port(0),
|
||||
}
|
||||
if strings.HasPrefix(dest, "tcp:") {
|
||||
d.Network = Network_TCP
|
||||
dest = dest[4:]
|
||||
} else if strings.HasPrefix(dest, "udp:") {
|
||||
d.Network = Network_UDP
|
||||
dest = dest[4:]
|
||||
} else if strings.HasPrefix(dest, "unix:") {
|
||||
d = UnixDestination(DomainAddress(dest[5:]))
|
||||
return d, nil
|
||||
}
|
||||
|
||||
hstr, pstr, err := SplitHostPort(dest)
|
||||
if err != nil {
|
||||
return d, err
|
||||
}
|
||||
if len(hstr) > 0 {
|
||||
d.Address = ParseAddress(hstr)
|
||||
}
|
||||
if len(pstr) > 0 {
|
||||
port, err := PortFromString(pstr)
|
||||
if err != nil {
|
||||
return d, err
|
||||
}
|
||||
d.Port = port
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// TCPDestination creates a TCP destination with given address
|
||||
func TCPDestination(address Address, port Port) Destination {
|
||||
return Destination{
|
||||
Network: Network_TCP,
|
||||
Address: address,
|
||||
Port: port,
|
||||
}
|
||||
}
|
||||
|
||||
// UDPDestination creates a UDP destination with given address
|
||||
func UDPDestination(address Address, port Port) Destination {
|
||||
return Destination{
|
||||
Network: Network_UDP,
|
||||
Address: address,
|
||||
Port: port,
|
||||
}
|
||||
}
|
||||
|
||||
// UnixDestination creates a Unix destination with given address
|
||||
func UnixDestination(address Address) Destination {
|
||||
return Destination{
|
||||
Network: Network_UNIX,
|
||||
Address: address,
|
||||
}
|
||||
}
|
||||
|
||||
// NetAddr returns the network address in this Destination in string form.
|
||||
func (d Destination) NetAddr() string {
|
||||
addr := ""
|
||||
if d.Network == Network_TCP || d.Network == Network_UDP {
|
||||
addr = d.Address.String() + ":" + d.Port.String()
|
||||
} else if d.Network == Network_UNIX {
|
||||
addr = d.Address.String()
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// RawNetAddr converts a net.Addr from its Destination presentation.
|
||||
func (d Destination) RawNetAddr() net.Addr {
|
||||
var addr net.Addr
|
||||
switch d.Network {
|
||||
case Network_TCP:
|
||||
if d.Address.Family().IsIP() {
|
||||
addr = &net.TCPAddr{
|
||||
IP: d.Address.IP(),
|
||||
Port: int(d.Port),
|
||||
}
|
||||
}
|
||||
case Network_UDP:
|
||||
if d.Address.Family().IsIP() {
|
||||
addr = &net.UDPAddr{
|
||||
IP: d.Address.IP(),
|
||||
Port: int(d.Port),
|
||||
}
|
||||
}
|
||||
case Network_UNIX:
|
||||
if d.Address.Family().IsDomain() {
|
||||
addr = &net.UnixAddr{
|
||||
Name: d.Address.String(),
|
||||
Net: d.Network.SystemString(),
|
||||
}
|
||||
}
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// String returns the strings form of this Destination.
|
||||
func (d Destination) String() string {
|
||||
prefix := "unknown:"
|
||||
switch d.Network {
|
||||
case Network_TCP:
|
||||
prefix = "tcp:"
|
||||
case Network_UDP:
|
||||
prefix = "udp:"
|
||||
case Network_UNIX:
|
||||
prefix = "unix:"
|
||||
}
|
||||
return prefix + d.NetAddr()
|
||||
}
|
||||
|
||||
// IsValid returns true if this Destination is valid.
|
||||
func (d Destination) IsValid() bool {
|
||||
return d.Network != Network_Unknown
|
||||
}
|
||||
13
common/xray/net/net.go
Normal file
13
common/xray/net/net.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package net
|
||||
|
||||
import "time"
|
||||
|
||||
// defines the maximum time an idle TCP session can survive in the tunnel, so
|
||||
// it should be consistent across HTTP versions and with other transports.
|
||||
const ConnIdleTimeout = 300 * time.Second
|
||||
|
||||
// consistent with quic-go
|
||||
const QuicgoH3KeepAlivePeriod = 10 * time.Second
|
||||
|
||||
// consistent with chrome
|
||||
const ChromeH2KeepAlivePeriod = 45 * time.Second
|
||||
33
common/xray/net/network.go
Normal file
33
common/xray/net/network.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package net
|
||||
|
||||
type Network int32
|
||||
|
||||
const (
|
||||
Network_Unknown Network = 0
|
||||
Network_TCP Network = 2
|
||||
Network_UDP Network = 3
|
||||
Network_UNIX Network = 4
|
||||
)
|
||||
|
||||
func (n Network) SystemString() string {
|
||||
switch n {
|
||||
case Network_TCP:
|
||||
return "tcp"
|
||||
case Network_UDP:
|
||||
return "udp"
|
||||
case Network_UNIX:
|
||||
return "unix"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// HasNetwork returns true if the network list has a certain network.
|
||||
func HasNetwork(list []Network, network Network) bool {
|
||||
for _, value := range list {
|
||||
if value == network {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
55
common/xray/net/port.go
Normal file
55
common/xray/net/port.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
// Port represents a network port in TCP and UDP protocol.
|
||||
type Port uint16
|
||||
|
||||
// PortFromBytes converts a byte array to a Port, assuming bytes are in big endian order.
|
||||
// @unsafe Caller must ensure that the byte array has at least 2 elements.
|
||||
func PortFromBytes(port []byte) Port {
|
||||
return Port(binary.BigEndian.Uint16(port))
|
||||
}
|
||||
|
||||
// PortFromInt converts an integer to a Port.
|
||||
// @error when the integer is not positive or larger then 65535
|
||||
func PortFromInt(val uint32) (Port, error) {
|
||||
if val > 65535 {
|
||||
return Port(0), E.New("invalid port range: ", val)
|
||||
}
|
||||
return Port(val), nil
|
||||
}
|
||||
|
||||
// PortFromString converts a string to a Port.
|
||||
// @error when the string is not an integer or the integral value is a not a valid Port.
|
||||
func PortFromString(s string) (Port, error) {
|
||||
val, err := strconv.ParseUint(s, 10, 32)
|
||||
if err != nil {
|
||||
return Port(0), E.New("invalid port range: ", s)
|
||||
}
|
||||
return PortFromInt(uint32(val))
|
||||
}
|
||||
|
||||
// Value return the corresponding uint16 value of a Port.
|
||||
func (p Port) Value() uint16 {
|
||||
return uint16(p)
|
||||
}
|
||||
|
||||
// String returns the string presentation of a Port.
|
||||
func (p Port) String() string {
|
||||
return strconv.Itoa(int(p))
|
||||
}
|
||||
|
||||
type MemoryPortRange struct {
|
||||
From Port
|
||||
To Port
|
||||
}
|
||||
|
||||
func (r MemoryPortRange) Contains(port Port) bool {
|
||||
return r.From <= port && port <= r.To
|
||||
}
|
||||
84
common/xray/net/system.go
Normal file
84
common/xray/net/system.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package net
|
||||
|
||||
import "net"
|
||||
|
||||
// DialTCP is an alias of net.DialTCP.
|
||||
var (
|
||||
DialTCP = net.DialTCP
|
||||
DialUDP = net.DialUDP
|
||||
DialUnix = net.DialUnix
|
||||
Dial = net.Dial
|
||||
)
|
||||
|
||||
type ListenConfig = net.ListenConfig
|
||||
|
||||
var (
|
||||
Listen = net.Listen
|
||||
ListenTCP = net.ListenTCP
|
||||
ListenUDP = net.ListenUDP
|
||||
ListenUnix = net.ListenUnix
|
||||
)
|
||||
|
||||
var LookupIP = net.LookupIP
|
||||
|
||||
var FileConn = net.FileConn
|
||||
|
||||
// ParseIP is an alias of net.ParseIP
|
||||
var ParseIP = net.ParseIP
|
||||
|
||||
var SplitHostPort = net.SplitHostPort
|
||||
|
||||
var CIDRMask = net.CIDRMask
|
||||
|
||||
type (
|
||||
Addr = net.Addr
|
||||
Conn = net.Conn
|
||||
PacketConn = net.PacketConn
|
||||
)
|
||||
|
||||
type (
|
||||
TCPAddr = net.TCPAddr
|
||||
TCPConn = net.TCPConn
|
||||
)
|
||||
|
||||
type (
|
||||
UDPAddr = net.UDPAddr
|
||||
UDPConn = net.UDPConn
|
||||
)
|
||||
|
||||
type (
|
||||
UnixAddr = net.UnixAddr
|
||||
UnixConn = net.UnixConn
|
||||
)
|
||||
|
||||
// IP is an alias for net.IP.
|
||||
type (
|
||||
IP = net.IP
|
||||
IPMask = net.IPMask
|
||||
IPNet = net.IPNet
|
||||
)
|
||||
|
||||
const (
|
||||
IPv4len = net.IPv4len
|
||||
IPv6len = net.IPv6len
|
||||
)
|
||||
|
||||
type (
|
||||
Error = net.Error
|
||||
AddrError = net.AddrError
|
||||
)
|
||||
|
||||
type (
|
||||
Dialer = net.Dialer
|
||||
Listener = net.Listener
|
||||
TCPListener = net.TCPListener
|
||||
UnixListener = net.UnixListener
|
||||
)
|
||||
|
||||
var (
|
||||
ResolveTCPAddr = net.ResolveTCPAddr
|
||||
ResolveUDPAddr = net.ResolveUDPAddr
|
||||
ResolveUnixAddr = net.ResolveUnixAddr
|
||||
)
|
||||
|
||||
type Resolver = net.Resolver
|
||||
215
common/xray/pipe/impl.go
Normal file
215
common/xray/pipe/impl.go
Normal file
@@ -0,0 +1,215 @@
|
||||
package pipe
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray"
|
||||
"github.com/sagernet/sing-box/common/xray/buf"
|
||||
"github.com/sagernet/sing-box/common/xray/signal"
|
||||
"github.com/sagernet/sing-box/common/xray/signal/done"
|
||||
)
|
||||
|
||||
type state byte
|
||||
|
||||
const (
|
||||
open state = iota
|
||||
closed
|
||||
errord
|
||||
)
|
||||
|
||||
type pipeOption struct {
|
||||
limit int32 // maximum buffer size in bytes
|
||||
discardOverflow bool
|
||||
}
|
||||
|
||||
func (o *pipeOption) isFull(curSize int32) bool {
|
||||
return o.limit >= 0 && curSize > o.limit
|
||||
}
|
||||
|
||||
type pipe struct {
|
||||
sync.Mutex
|
||||
data buf.MultiBuffer
|
||||
readSignal *signal.Notifier
|
||||
writeSignal *signal.Notifier
|
||||
done *done.Instance
|
||||
errChan chan error
|
||||
option pipeOption
|
||||
state state
|
||||
}
|
||||
|
||||
var (
|
||||
errBufferFull = errors.New("buffer full")
|
||||
errSlowDown = errors.New("slow down")
|
||||
)
|
||||
|
||||
func (p *pipe) Len() int32 {
|
||||
data := p.data
|
||||
if data == nil {
|
||||
return 0
|
||||
}
|
||||
return data.Len()
|
||||
}
|
||||
|
||||
func (p *pipe) getState(forRead bool) error {
|
||||
switch p.state {
|
||||
case open:
|
||||
if !forRead && p.option.isFull(p.data.Len()) {
|
||||
return errBufferFull
|
||||
}
|
||||
return nil
|
||||
case closed:
|
||||
if !forRead {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
if !p.data.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
return io.EOF
|
||||
case errord:
|
||||
return io.ErrClosedPipe
|
||||
default:
|
||||
panic("impossible case")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipe) readMultiBufferInternal() (buf.MultiBuffer, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
if err := p.getState(true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := p.data
|
||||
p.data = nil
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (p *pipe) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
for {
|
||||
data, err := p.readMultiBufferInternal()
|
||||
if data != nil || err != nil {
|
||||
p.writeSignal.Signal()
|
||||
return data, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-p.readSignal.Wait():
|
||||
case <-p.done.Wait():
|
||||
case err = <-p.errChan:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipe) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) {
|
||||
timer := time.NewTimer(d)
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
data, err := p.readMultiBufferInternal()
|
||||
if data != nil || err != nil {
|
||||
p.writeSignal.Signal()
|
||||
return data, err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-p.readSignal.Wait():
|
||||
case <-p.done.Wait():
|
||||
case <-timer.C:
|
||||
return nil, buf.ErrReadTimeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipe) writeMultiBufferInternal(mb buf.MultiBuffer) error {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
if err := p.getState(false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.data == nil {
|
||||
p.data = mb
|
||||
return nil
|
||||
}
|
||||
|
||||
p.data, _ = buf.MergeMulti(p.data, mb)
|
||||
return errSlowDown
|
||||
}
|
||||
|
||||
func (p *pipe) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
if mb.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
err := p.writeMultiBufferInternal(mb)
|
||||
if err == nil {
|
||||
p.readSignal.Signal()
|
||||
return nil
|
||||
}
|
||||
|
||||
if err == errSlowDown {
|
||||
p.readSignal.Signal()
|
||||
|
||||
// Yield current goroutine. Hopefully the reading counterpart can pick up the payload.
|
||||
runtime.Gosched()
|
||||
return nil
|
||||
}
|
||||
|
||||
if err == errBufferFull && p.option.discardOverflow {
|
||||
buf.ReleaseMulti(mb)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != errBufferFull {
|
||||
buf.ReleaseMulti(mb)
|
||||
p.readSignal.Signal()
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case <-p.writeSignal.Wait():
|
||||
case <-p.done.Wait():
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipe) Close() error {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
if p.state == closed || p.state == errord {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.state = closed
|
||||
common.Must(p.done.Close())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Interrupt implements common.Interruptible.
|
||||
func (p *pipe) Interrupt() {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
if p.state == closed || p.state == errord {
|
||||
return
|
||||
}
|
||||
|
||||
p.state = errord
|
||||
|
||||
if !p.data.IsEmpty() {
|
||||
buf.ReleaseMulti(p.data)
|
||||
p.data = nil
|
||||
}
|
||||
|
||||
common.Must(p.done.Close())
|
||||
}
|
||||
53
common/xray/pipe/pipe.go
Normal file
53
common/xray/pipe/pipe.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package pipe
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/common/xray/signal"
|
||||
"github.com/sagernet/sing-box/common/xray/signal/done"
|
||||
)
|
||||
|
||||
// Option for creating new Pipes.
|
||||
type Option func(*pipeOption)
|
||||
|
||||
// WithoutSizeLimit returns an Option for Pipe to have no size limit.
|
||||
func WithoutSizeLimit() Option {
|
||||
return func(opt *pipeOption) {
|
||||
opt.limit = -1
|
||||
}
|
||||
}
|
||||
|
||||
// WithSizeLimit returns an Option for Pipe to have the given size limit.
|
||||
func WithSizeLimit(limit int32) Option {
|
||||
return func(opt *pipeOption) {
|
||||
opt.limit = limit
|
||||
}
|
||||
}
|
||||
|
||||
// DiscardOverflow returns an Option for Pipe to discard writes if full.
|
||||
func DiscardOverflow() Option {
|
||||
return func(opt *pipeOption) {
|
||||
opt.discardOverflow = true
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new Reader and Writer that connects to each other.
|
||||
func New(opts ...Option) (*Reader, *Writer) {
|
||||
p := &pipe{
|
||||
readSignal: signal.NewNotifier(),
|
||||
writeSignal: signal.NewNotifier(),
|
||||
done: done.New(),
|
||||
errChan: make(chan error, 1),
|
||||
option: pipeOption{
|
||||
limit: -1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(&(p.option))
|
||||
}
|
||||
|
||||
return &Reader{
|
||||
pipe: p,
|
||||
}, &Writer{
|
||||
pipe: p,
|
||||
}
|
||||
}
|
||||
41
common/xray/pipe/reader.go
Normal file
41
common/xray/pipe/reader.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package pipe
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray/buf"
|
||||
)
|
||||
|
||||
// Reader is a buf.Reader that reads content from a pipe.
|
||||
type Reader struct {
|
||||
pipe *pipe
|
||||
}
|
||||
|
||||
// ReadMultiBuffer implements buf.Reader.
|
||||
func (r *Reader) ReadMultiBuffer() (buf.MultiBuffer, error) {
|
||||
return r.pipe.ReadMultiBuffer()
|
||||
}
|
||||
|
||||
// ReadMultiBufferTimeout reads content from a pipe within the given duration, or returns buf.ErrTimeout otherwise.
|
||||
func (r *Reader) ReadMultiBufferTimeout(d time.Duration) (buf.MultiBuffer, error) {
|
||||
return r.pipe.ReadMultiBufferTimeout(d)
|
||||
}
|
||||
|
||||
// Interrupt implements common.Interruptible.
|
||||
func (r *Reader) Interrupt() {
|
||||
r.pipe.Interrupt()
|
||||
}
|
||||
|
||||
// ReturnAnError makes ReadMultiBuffer return an error, only once.
|
||||
func (r *Reader) ReturnAnError(err error) {
|
||||
r.pipe.errChan <- err
|
||||
}
|
||||
|
||||
// Recover catches an error set by ReturnAnError, if exists.
|
||||
func (r *Reader) Recover() (err error) {
|
||||
select {
|
||||
case err = <-r.pipe.errChan:
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
29
common/xray/pipe/writer.go
Normal file
29
common/xray/pipe/writer.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package pipe
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/common/xray/buf"
|
||||
)
|
||||
|
||||
// Writer is a buf.Writer that writes data into a pipe.
|
||||
type Writer struct {
|
||||
pipe *pipe
|
||||
}
|
||||
|
||||
// WriteMultiBuffer implements buf.Writer.
|
||||
func (w *Writer) WriteMultiBuffer(mb buf.MultiBuffer) error {
|
||||
return w.pipe.WriteMultiBuffer(mb)
|
||||
}
|
||||
|
||||
// Close implements io.Closer. After the pipe is closed, writing to the pipe will return io.ErrClosedPipe, while reading will return io.EOF.
|
||||
func (w *Writer) Close() error {
|
||||
return w.pipe.Close()
|
||||
}
|
||||
|
||||
func (w *Writer) Len() int32 {
|
||||
return w.pipe.Len()
|
||||
}
|
||||
|
||||
// Interrupt implements common.Interruptible.
|
||||
func (w *Writer) Interrupt() {
|
||||
w.pipe.Interrupt()
|
||||
}
|
||||
29
common/xray/serial/serial.go
Normal file
29
common/xray/serial/serial.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package serial
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ReadUint16 reads first two bytes from the reader, and then converts them to an uint16 value.
|
||||
func ReadUint16(reader io.Reader) (uint16, error) {
|
||||
var b [2]byte
|
||||
if _, err := io.ReadFull(reader, b[:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return binary.BigEndian.Uint16(b[:]), nil
|
||||
}
|
||||
|
||||
// WriteUint16 writes an uint16 value into writer.
|
||||
func WriteUint16(writer io.Writer, value uint16) (int, error) {
|
||||
var b [2]byte
|
||||
binary.BigEndian.PutUint16(b[:], value)
|
||||
return writer.Write(b[:])
|
||||
}
|
||||
|
||||
// WriteUint64 writes an uint64 value into writer.
|
||||
func WriteUint64(writer io.Writer, value uint64) (int, error) {
|
||||
var b [8]byte
|
||||
binary.BigEndian.PutUint64(b[:], value)
|
||||
return writer.Write(b[:])
|
||||
}
|
||||
35
common/xray/serial/string.go
Normal file
35
common/xray/serial/string.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package serial
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ToString serializes an arbitrary value into string.
|
||||
func ToString(v interface{}) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch value := v.(type) {
|
||||
case string:
|
||||
return value
|
||||
case *string:
|
||||
return *value
|
||||
case fmt.Stringer:
|
||||
return value.String()
|
||||
case error:
|
||||
return value.Error()
|
||||
default:
|
||||
return fmt.Sprintf("%+v", value)
|
||||
}
|
||||
}
|
||||
|
||||
// Concat concatenates all input into a single string.
|
||||
func Concat(v ...interface{}) string {
|
||||
builder := strings.Builder{}
|
||||
for _, value := range v {
|
||||
builder.WriteString(ToString(value))
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
49
common/xray/signal/done/done.go
Normal file
49
common/xray/signal/done/done.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package done
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Instance is a utility for notifications of something being done.
|
||||
type Instance struct {
|
||||
access sync.Mutex
|
||||
c chan struct{}
|
||||
closed bool
|
||||
}
|
||||
|
||||
// New returns a new Done.
|
||||
func New() *Instance {
|
||||
return &Instance{
|
||||
c: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Done returns true if Close() is called.
|
||||
func (d *Instance) Done() bool {
|
||||
select {
|
||||
case <-d.Wait():
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Wait returns a channel for waiting for done.
|
||||
func (d *Instance) Wait() <-chan struct{} {
|
||||
return d.c
|
||||
}
|
||||
|
||||
// Close marks this Done 'done'. This method may be called multiple times. All calls after first call will have no effect on its status.
|
||||
func (d *Instance) Close() error {
|
||||
d.access.Lock()
|
||||
defer d.access.Unlock()
|
||||
|
||||
if d.closed {
|
||||
return nil
|
||||
}
|
||||
|
||||
d.closed = true
|
||||
close(d.c)
|
||||
|
||||
return nil
|
||||
}
|
||||
26
common/xray/signal/notifier.go
Normal file
26
common/xray/signal/notifier.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package signal
|
||||
|
||||
// Notifier is a utility for notifying changes. The change producer may notify changes multiple time, and the consumer may get notified asynchronously.
|
||||
type Notifier struct {
|
||||
c chan struct{}
|
||||
}
|
||||
|
||||
// NewNotifier creates a new Notifier.
|
||||
func NewNotifier() *Notifier {
|
||||
return &Notifier{
|
||||
c: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
||||
// Signal signals a change, usually by producer. This method never blocks.
|
||||
func (n *Notifier) Signal() {
|
||||
select {
|
||||
case n.c <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Wait returns a channel for waiting for changes. The returned channel never gets closed.
|
||||
func (n *Notifier) Wait() <-chan struct{} {
|
||||
return n.c
|
||||
}
|
||||
105
common/xray/signal/pubsub/pubsub.go
Normal file
105
common/xray/signal/pubsub/pubsub.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package pubsub
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray"
|
||||
"github.com/sagernet/sing-box/common/xray/signal/done"
|
||||
"github.com/sagernet/sing-box/common/xray/task"
|
||||
)
|
||||
|
||||
type Subscriber struct {
|
||||
buffer chan interface{}
|
||||
done *done.Instance
|
||||
}
|
||||
|
||||
func (s *Subscriber) push(msg interface{}) {
|
||||
select {
|
||||
case s.buffer <- msg:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Subscriber) Wait() <-chan interface{} {
|
||||
return s.buffer
|
||||
}
|
||||
|
||||
func (s *Subscriber) Close() error {
|
||||
return s.done.Close()
|
||||
}
|
||||
|
||||
func (s *Subscriber) IsClosed() bool {
|
||||
return s.done.Done()
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
sync.RWMutex
|
||||
subs map[string][]*Subscriber
|
||||
ctask *task.Periodic
|
||||
}
|
||||
|
||||
func NewService() *Service {
|
||||
s := &Service{
|
||||
subs: make(map[string][]*Subscriber),
|
||||
}
|
||||
s.ctask = &task.Periodic{
|
||||
Execute: s.Cleanup,
|
||||
Interval: time.Second * 30,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Cleanup cleans up internal caches of subscribers.
|
||||
// Visible for testing only.
|
||||
func (s *Service) Cleanup() error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
if len(s.subs) == 0 {
|
||||
return errors.New("nothing to do")
|
||||
}
|
||||
|
||||
for name, subs := range s.subs {
|
||||
newSub := make([]*Subscriber, 0, len(s.subs))
|
||||
for _, sub := range subs {
|
||||
if !sub.IsClosed() {
|
||||
newSub = append(newSub, sub)
|
||||
}
|
||||
}
|
||||
if len(newSub) == 0 {
|
||||
delete(s.subs, name)
|
||||
} else {
|
||||
s.subs[name] = newSub
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.subs) == 0 {
|
||||
s.subs = make(map[string][]*Subscriber)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Subscribe(name string) *Subscriber {
|
||||
sub := &Subscriber{
|
||||
buffer: make(chan interface{}, 16),
|
||||
done: done.New(),
|
||||
}
|
||||
s.Lock()
|
||||
s.subs[name] = append(s.subs[name], sub)
|
||||
s.Unlock()
|
||||
common.Must(s.ctask.Start())
|
||||
return sub
|
||||
}
|
||||
|
||||
func (s *Service) Publish(name string, message interface{}) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
for _, sub := range s.subs[name] {
|
||||
if !sub.IsClosed() {
|
||||
sub.push(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
27
common/xray/signal/semaphore/semaphore.go
Normal file
27
common/xray/signal/semaphore/semaphore.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package semaphore
|
||||
|
||||
// Instance is an implementation of semaphore.
|
||||
type Instance struct {
|
||||
token chan struct{}
|
||||
}
|
||||
|
||||
// New create a new Semaphore with n permits.
|
||||
func New(n int) *Instance {
|
||||
s := &Instance{
|
||||
token: make(chan struct{}, n),
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
s.token <- struct{}{}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Wait returns a channel for acquiring a permit.
|
||||
func (s *Instance) Wait() <-chan struct{} {
|
||||
return s.token
|
||||
}
|
||||
|
||||
// Signal releases a permit into the semaphore.
|
||||
func (s *Instance) Signal() {
|
||||
s.token <- struct{}{}
|
||||
}
|
||||
82
common/xray/signal/timer.go
Normal file
82
common/xray/signal/timer.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package signal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray"
|
||||
"github.com/sagernet/sing-box/common/xray/task"
|
||||
)
|
||||
|
||||
type ActivityUpdater interface {
|
||||
Update()
|
||||
}
|
||||
|
||||
type ActivityTimer struct {
|
||||
sync.RWMutex
|
||||
updated chan struct{}
|
||||
checkTask *task.Periodic
|
||||
onTimeout func()
|
||||
}
|
||||
|
||||
func (t *ActivityTimer) Update() {
|
||||
select {
|
||||
case t.updated <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ActivityTimer) check() error {
|
||||
select {
|
||||
case <-t.updated:
|
||||
default:
|
||||
t.finish()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ActivityTimer) finish() {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if t.onTimeout != nil {
|
||||
t.onTimeout()
|
||||
t.onTimeout = nil
|
||||
}
|
||||
if t.checkTask != nil {
|
||||
t.checkTask.Close()
|
||||
t.checkTask = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ActivityTimer) SetTimeout(timeout time.Duration) {
|
||||
if timeout == 0 {
|
||||
t.finish()
|
||||
return
|
||||
}
|
||||
|
||||
checkTask := &task.Periodic{
|
||||
Interval: timeout,
|
||||
Execute: t.check,
|
||||
}
|
||||
|
||||
t.Lock()
|
||||
|
||||
if t.checkTask != nil {
|
||||
t.checkTask.Close()
|
||||
}
|
||||
t.checkTask = checkTask
|
||||
t.Unlock()
|
||||
t.Update()
|
||||
common.Must(checkTask.Start())
|
||||
}
|
||||
|
||||
func CancelAfterInactivity(ctx context.Context, cancel context.CancelFunc, timeout time.Duration) *ActivityTimer {
|
||||
timer := &ActivityTimer{
|
||||
updated: make(chan struct{}, 1),
|
||||
onTimeout: cancel,
|
||||
}
|
||||
timer.SetTimeout(timeout)
|
||||
return timer
|
||||
}
|
||||
34
common/xray/stat/connection.go
Normal file
34
common/xray/stat/connection.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package stat
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray/stats"
|
||||
)
|
||||
|
||||
type Connection interface {
|
||||
net.Conn
|
||||
}
|
||||
|
||||
type CounterConnection struct {
|
||||
Connection
|
||||
ReadCounter stats.Counter
|
||||
WriteCounter stats.Counter
|
||||
}
|
||||
|
||||
func (c *CounterConnection) Read(b []byte) (int, error) {
|
||||
nBytes, err := c.Connection.Read(b)
|
||||
if c.ReadCounter != nil {
|
||||
c.ReadCounter.Add(int64(nBytes))
|
||||
}
|
||||
|
||||
return nBytes, err
|
||||
}
|
||||
|
||||
func (c *CounterConnection) Write(b []byte) (int, error) {
|
||||
nBytes, err := c.Connection.Write(b)
|
||||
if c.WriteCounter != nil {
|
||||
c.WriteCounter.Add(int64(nBytes))
|
||||
}
|
||||
return nBytes, err
|
||||
}
|
||||
13
common/xray/stats/stats.go
Normal file
13
common/xray/stats/stats.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package stats
|
||||
|
||||
// Counter is the interface for stats counters.
|
||||
//
|
||||
// xray:api:stable
|
||||
type Counter interface {
|
||||
// Value is the current value of the counter.
|
||||
Value() int64
|
||||
// Set sets a new value to the counter, and returns the previous one.
|
||||
Set(int64) int64
|
||||
// Add adds a value to the current counter value, and returns the previous value.
|
||||
Add(int64) int64
|
||||
}
|
||||
10
common/xray/task/common.go
Normal file
10
common/xray/task/common.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package task
|
||||
|
||||
import "github.com/sagernet/sing-box/common/xray"
|
||||
|
||||
// Close returns a func() that closes v.
|
||||
func Close(v interface{}) func() error {
|
||||
return func() error {
|
||||
return common.Close(v)
|
||||
}
|
||||
}
|
||||
85
common/xray/task/periodic.go
Normal file
85
common/xray/task/periodic.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Periodic is a task that runs periodically.
|
||||
type Periodic struct {
|
||||
// Interval of the task being run
|
||||
Interval time.Duration
|
||||
// Execute is the task function
|
||||
Execute func() error
|
||||
|
||||
access sync.Mutex
|
||||
timer *time.Timer
|
||||
running bool
|
||||
}
|
||||
|
||||
func (t *Periodic) hasClosed() bool {
|
||||
t.access.Lock()
|
||||
defer t.access.Unlock()
|
||||
|
||||
return !t.running
|
||||
}
|
||||
|
||||
func (t *Periodic) checkedExecute() error {
|
||||
if t.hasClosed() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := t.Execute(); err != nil {
|
||||
t.access.Lock()
|
||||
t.running = false
|
||||
t.access.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
t.access.Lock()
|
||||
defer t.access.Unlock()
|
||||
|
||||
if !t.running {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.timer = time.AfterFunc(t.Interval, func() {
|
||||
t.checkedExecute()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start implements common.Runnable.
|
||||
func (t *Periodic) Start() error {
|
||||
t.access.Lock()
|
||||
if t.running {
|
||||
t.access.Unlock()
|
||||
return nil
|
||||
}
|
||||
t.running = true
|
||||
t.access.Unlock()
|
||||
|
||||
if err := t.checkedExecute(); err != nil {
|
||||
t.access.Lock()
|
||||
t.running = false
|
||||
t.access.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements common.Closable.
|
||||
func (t *Periodic) Close() error {
|
||||
t.access.Lock()
|
||||
defer t.access.Unlock()
|
||||
|
||||
t.running = false
|
||||
if t.timer != nil {
|
||||
t.timer.Stop()
|
||||
t.timer = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
64
common/xray/task/task.go
Normal file
64
common/xray/task/task.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray/signal/semaphore"
|
||||
)
|
||||
|
||||
// OnSuccess executes g() after f() returns nil.
|
||||
func OnSuccess(f func() error, g func() error) func() error {
|
||||
return func() error {
|
||||
if err := f(); err != nil {
|
||||
return err
|
||||
}
|
||||
return g()
|
||||
}
|
||||
}
|
||||
|
||||
// Run executes a list of tasks in parallel, returns the first error encountered or nil if all tasks pass.
|
||||
func Run(ctx context.Context, tasks ...func() error) error {
|
||||
n := len(tasks)
|
||||
s := semaphore.New(n)
|
||||
done := make(chan error, 1)
|
||||
|
||||
for _, task := range tasks {
|
||||
<-s.Wait()
|
||||
go func(f func() error) {
|
||||
err := f()
|
||||
if err == nil {
|
||||
s.Signal()
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case done <- err:
|
||||
default:
|
||||
}
|
||||
}(task)
|
||||
}
|
||||
|
||||
/*
|
||||
if altctx := ctx.Value("altctx"); altctx != nil {
|
||||
ctx = altctx.(context.Context)
|
||||
}
|
||||
*/
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
select {
|
||||
case err := <-done:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-s.Wait():
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if cancel := ctx.Value("cancel"); cancel != nil {
|
||||
cancel.(context.CancelFunc)()
|
||||
}
|
||||
*/
|
||||
|
||||
return nil
|
||||
}
|
||||
101
common/xray/uuid/uuid.go
Normal file
101
common/xray/uuid/uuid.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
var byteGroups = []int{8, 4, 4, 4, 12}
|
||||
|
||||
type UUID [16]byte
|
||||
|
||||
// String returns the string representation of this UUID.
|
||||
func (u *UUID) String() string {
|
||||
bytes := u.Bytes()
|
||||
result := hex.EncodeToString(bytes[0 : byteGroups[0]/2])
|
||||
start := byteGroups[0] / 2
|
||||
for i := 1; i < len(byteGroups); i++ {
|
||||
nBytes := byteGroups[i] / 2
|
||||
result += "-"
|
||||
result += hex.EncodeToString(bytes[start : start+nBytes])
|
||||
start += nBytes
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Bytes returns the bytes representation of this UUID.
|
||||
func (u *UUID) Bytes() []byte {
|
||||
return u[:]
|
||||
}
|
||||
|
||||
// Equals returns true if this UUID equals another UUID by value.
|
||||
func (u *UUID) Equals(another *UUID) bool {
|
||||
if u == nil && another == nil {
|
||||
return true
|
||||
}
|
||||
if u == nil || another == nil {
|
||||
return false
|
||||
}
|
||||
return bytes.Equal(u.Bytes(), another.Bytes())
|
||||
}
|
||||
|
||||
// New creates a UUID with random value.
|
||||
func New() UUID {
|
||||
var uuid UUID
|
||||
common.Must2(rand.Read(uuid.Bytes()))
|
||||
uuid[6] = (uuid[6] & 0x0f) | (4 << 4)
|
||||
uuid[8] = (uuid[8]&(0xff>>2) | (0x02 << 6))
|
||||
return uuid
|
||||
}
|
||||
|
||||
// ParseBytes converts a UUID in byte form to object.
|
||||
func ParseBytes(b []byte) (UUID, error) {
|
||||
var uuid UUID
|
||||
if len(b) != 16 {
|
||||
return uuid, E.New("invalid UUID: ", b)
|
||||
}
|
||||
copy(uuid[:], b)
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
// ParseString converts a UUID in string form to object.
|
||||
func ParseString(str string) (UUID, error) {
|
||||
var uuid UUID
|
||||
|
||||
text := []byte(str)
|
||||
if l := len(text); l < 32 || l > 36 {
|
||||
if l == 0 || l > 30 {
|
||||
return uuid, E.New("invalid UUID: ", str)
|
||||
}
|
||||
h := sha1.New()
|
||||
h.Write(uuid[:])
|
||||
h.Write(text)
|
||||
u := h.Sum(nil)[:16]
|
||||
u[6] = (u[6] & 0x0f) | (5 << 4)
|
||||
u[8] = (u[8]&(0xff>>2) | (0x02 << 6))
|
||||
copy(uuid[:], u)
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
b := uuid.Bytes()
|
||||
|
||||
for _, byteGroup := range byteGroups {
|
||||
if text[0] == '-' {
|
||||
text = text[1:]
|
||||
}
|
||||
|
||||
if _, err := hex.Decode(b[:byteGroup/2], text[:byteGroup]); err != nil {
|
||||
return uuid, err
|
||||
}
|
||||
|
||||
text = text[byteGroup:]
|
||||
b = b[byteGroup/2:]
|
||||
}
|
||||
|
||||
return uuid, nil
|
||||
}
|
||||
@@ -15,10 +15,12 @@ const (
|
||||
TypeTrojan = "trojan"
|
||||
TypeNaive = "naive"
|
||||
TypeWireGuard = "wireguard"
|
||||
TypeWARP = "warp"
|
||||
TypeHysteria = "hysteria"
|
||||
TypeTor = "tor"
|
||||
TypeSSH = "ssh"
|
||||
TypeShadowTLS = "shadowtls"
|
||||
TypeMieru = "mieru"
|
||||
TypeShadowsocksR = "shadowsocksr"
|
||||
TypeVLESS = "vless"
|
||||
TypeTUIC = "tuic"
|
||||
@@ -60,6 +62,8 @@ func ProxyDisplayName(proxyType string) string {
|
||||
return "Naive"
|
||||
case TypeWireGuard:
|
||||
return "WireGuard"
|
||||
case TypeWARP:
|
||||
return "WARP"
|
||||
case TypeHysteria:
|
||||
return "Hysteria"
|
||||
case TypeTor:
|
||||
@@ -76,6 +80,8 @@ func ProxyDisplayName(proxyType string) string {
|
||||
return "TUIC"
|
||||
case TypeHysteria2:
|
||||
return "Hysteria2"
|
||||
case TypeMieru:
|
||||
return "Mieru"
|
||||
case TypeSelector:
|
||||
return "Selector"
|
||||
case TypeURLTest:
|
||||
|
||||
@@ -6,4 +6,5 @@ const (
|
||||
V2RayTransportTypeQUIC = "quic"
|
||||
V2RayTransportTypeGRPC = "grpc"
|
||||
V2RayTransportTypeHTTPUpgrade = "httpupgrade"
|
||||
V2RayTransportTypeXHTTP = "xhttp"
|
||||
)
|
||||
|
||||
20
constant/warp.go
Normal file
20
constant/warp.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package constant
|
||||
|
||||
type WARPConfig struct {
|
||||
PrivateKey string `json:"private_key"`
|
||||
Interface struct {
|
||||
Addresses struct {
|
||||
V4 string `json:"v4"`
|
||||
V6 string `json:"v6"`
|
||||
} `json:"addresses"`
|
||||
} `json:"interface"`
|
||||
Peers []struct {
|
||||
PublicKey string `json:"public_key"`
|
||||
Endpoint struct {
|
||||
V4 string `json:"v4"`
|
||||
V6 string `json:"v6"`
|
||||
Host string `json:"host"`
|
||||
Ports []int `json:"ports"`
|
||||
} `json:"endpoint"`
|
||||
} `json:"peers"`
|
||||
}
|
||||
6
debug.go
6
debug.go
@@ -24,9 +24,9 @@ func applyDebugOptions(options option.DebugOptions) {
|
||||
if options.TraceBack != "" {
|
||||
debug.SetTraceback(options.TraceBack)
|
||||
}
|
||||
if options.MemoryLimit != 0 {
|
||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
|
||||
conntrack.MemoryLimit = uint64(options.MemoryLimit)
|
||||
if options.MemoryLimit.Value() != 0 {
|
||||
debug.SetMemoryLimit(int64(float64(options.MemoryLimit.Value()) / 1.5))
|
||||
conntrack.MemoryLimit = options.MemoryLimit.Value()
|
||||
}
|
||||
if options.OOMKiller != nil {
|
||||
conntrack.KillerEnabled = *options.OOMKiller
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/common/humanize"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/byteformats"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/json/badjson"
|
||||
@@ -38,9 +38,9 @@ func applyDebugListenOption(options option.DebugOptions) {
|
||||
runtime.ReadMemStats(&memStats)
|
||||
|
||||
var memObject badjson.JSONObject
|
||||
memObject.Put("heap", humanize.MemoryBytes(memStats.HeapInuse))
|
||||
memObject.Put("stack", humanize.MemoryBytes(memStats.StackInuse))
|
||||
memObject.Put("idle", humanize.MemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||
memObject.Put("heap", byteformats.FormatMemoryBytes(memStats.HeapInuse))
|
||||
memObject.Put("stack", byteformats.FormatMemoryBytes(memStats.StackInuse))
|
||||
memObject.Put("idle", byteformats.FormatMemoryBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||
memObject.Put("goroutines", runtime.NumGoroutine())
|
||||
memObject.Put("rss", rusageMaxRSS())
|
||||
|
||||
|
||||
@@ -2,23 +2,73 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
### 1.11.13
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
### 1.11.11
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
### 1.11.10
|
||||
|
||||
* Undeprecate the `block` outbound **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Since we don’t have a replacement for using the `block` outbound in selectors yet,
|
||||
we decided to temporarily undeprecate the `block` outbound until a replacement is available in the future.
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
### 1.11.9
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
### 1.11.8
|
||||
|
||||
* Improve `auto_redirect` **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Now `auto_redirect` fixes compatibility issues between TUN and Docker bridge networks,
|
||||
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
### 1.11.7
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
### 1.11.6
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
### 1.11.5
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected)._
|
||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
||||
violated the rules (TestFlight users are not affected)._
|
||||
|
||||
### 1.11.4
|
||||
|
||||
@@ -28,7 +78,8 @@ _We are temporarily unable to update sing-box apps on the App Store because the
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration process._
|
||||
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration
|
||||
process._
|
||||
|
||||
### 1.11.1
|
||||
|
||||
|
||||
@@ -398,11 +398,11 @@ UDP NAT 过期时间。
|
||||
|
||||
TCP/IP 栈。
|
||||
|
||||
| 栈 | 描述 |
|
||||
|--------|------------------------------------------------------------------|
|
||||
| system | 基于系统网络栈执行 L3 到 L4 转换 |
|
||||
| gVisor | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
|
||||
| mixed | 混合 `system` TCP 栈与 `gvisor` UDP 栈 |
|
||||
| 栈 | 描述 |
|
||||
|----------|-------------------------------------------------------------------------------------------------------|
|
||||
| `system` | 基于系统网络栈执行 L3 到 L4 转换 |
|
||||
| `gvisor` | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
|
||||
| `mixed` | 混合 `system` TCP 栈与 `gvisor` UDP 栈 |
|
||||
|
||||
默认使用 `mixed` 栈如果 gVisor 构建标记已启用,否则默认使用 `system` 栈。
|
||||
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
||||
Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
||||
|
||||
`block` 出站关闭所有传入请求。
|
||||
|
||||
### 结构
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
| `shadowtls` | [ShadowTLS](./shadowtls/) |
|
||||
| `tuic` | [TUIC](./tuic/) |
|
||||
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
||||
| `mieru` | [Mieru](./mieru/) |
|
||||
| `tor` | [Tor](./tor/) |
|
||||
| `ssh` | [SSH](./ssh/) |
|
||||
| `dns` | [DNS](./dns/) |
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
| `shadowtls` | [ShadowTLS](./shadowtls/) |
|
||||
| `tuic` | [TUIC](./tuic/) |
|
||||
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
||||
| `mieru` | [Mieru](./mieru/) |
|
||||
| `tor` | [Tor](./tor/) |
|
||||
| `ssh` | [SSH](./ssh/) |
|
||||
| `dns` | [DNS](./dns/) |
|
||||
|
||||
71
docs/configuration/outbound/mieru.md
Normal file
71
docs/configuration/outbound/mieru.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "mieru",
|
||||
"tag": "mieru-out",
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"server_ports": [
|
||||
"9000-9010",
|
||||
"9020-9030"
|
||||
],
|
||||
"transport": "TCP",
|
||||
"username": "asdf",
|
||||
"password": "hjkl",
|
||||
"multiplexing": "MULTIPLEXING_LOW",
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
#### server
|
||||
|
||||
==Required==
|
||||
|
||||
The server address.
|
||||
|
||||
#### server_port
|
||||
|
||||
The server port.
|
||||
|
||||
Must set at least one field between `server_port` and `server_ports`.
|
||||
|
||||
#### server_ports
|
||||
|
||||
Server port range list.
|
||||
|
||||
Must set at least one field between `server_port` and `server_ports`.
|
||||
|
||||
#### transport
|
||||
|
||||
==Required==
|
||||
|
||||
Transmission protocol. The only allowed value is `TCP`.
|
||||
|
||||
#### username
|
||||
|
||||
==Required==
|
||||
|
||||
mieru user name.
|
||||
|
||||
#### password
|
||||
|
||||
==Required==
|
||||
|
||||
mieru password.
|
||||
|
||||
#### multiplexing
|
||||
|
||||
Multiplexing level. Supported values are `MULTIPLEXING_OFF`, `MULTIPLEXING_LOW`, `MULTIPLEXING_MIDDLE`, `MULTIPLEXING_HIGH`. `MULTIPLEXING_OFF` disables multiplexing.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||
71
docs/configuration/outbound/mieru.zh.md
Normal file
71
docs/configuration/outbound/mieru.zh.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "mieru",
|
||||
"tag": "mieru-out",
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"server_ports": [
|
||||
"9000-9010",
|
||||
"9020-9030"
|
||||
],
|
||||
"transport": "TCP",
|
||||
"username": "asdf",
|
||||
"password": "hjkl",
|
||||
"multiplexing": "MULTIPLEXING_LOW",
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
```
|
||||
|
||||
### 字段
|
||||
|
||||
#### server
|
||||
|
||||
==必填==
|
||||
|
||||
服务器地址。
|
||||
|
||||
#### server_port
|
||||
|
||||
服务器端口。
|
||||
|
||||
必须填写 `server_port` 和 `server_ports` 中至少一项。
|
||||
|
||||
#### server_ports
|
||||
|
||||
服务器端口范围列表。
|
||||
|
||||
必须填写 `server_port` 和 `server_ports` 中至少一项。
|
||||
|
||||
#### transport
|
||||
|
||||
==必填==
|
||||
|
||||
通信协议。仅可设为 `TCP`。
|
||||
|
||||
#### username
|
||||
|
||||
==必填==
|
||||
|
||||
mieru 用户名。
|
||||
|
||||
#### password
|
||||
|
||||
==必填==
|
||||
|
||||
mieru 密码。
|
||||
|
||||
#### multiplexing
|
||||
|
||||
多路复用设置。可以设为 `MULTIPLEXING_OFF`,`MULTIPLEXING_LOW`,`MULTIPLEXING_MIDDLE`,`MULTIPLEXING_HIGH`。其中 `MULTIPLEXING_OFF` 会关闭多路复用功能。
|
||||
|
||||
### 拨号字段
|
||||
|
||||
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
||||
@@ -84,6 +84,8 @@ List of [Headless Rule](./headless-rule/).
|
||||
|
||||
Format of rule-set file, `source` or `binary`.
|
||||
|
||||
Optional when `path` or `url` uses `json` or `srs` as extension.
|
||||
|
||||
### Local Fields
|
||||
|
||||
#### path
|
||||
|
||||
@@ -84,6 +84,8 @@ icon: material/new-box
|
||||
|
||||
规则集格式, `source` 或 `binary`。
|
||||
|
||||
当 `path` 或 `url` 使用 `json` 或 `srs` 作为扩展名时可选。
|
||||
|
||||
### 本地字段
|
||||
|
||||
#### path
|
||||
|
||||
@@ -9,43 +9,56 @@ icon: material/package
|
||||
=== ":material-debian: Debian / APT"
|
||||
|
||||
```bash
|
||||
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc
|
||||
sudo chmod a+r /etc/apt/keyrings/sagernet.asc
|
||||
echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" | \
|
||||
sudo tee /etc/apt/sources.list.d/sagernet.list > /dev/null
|
||||
sudo apt-get update
|
||||
sudo apt-get install sing-box # or sing-box-beta
|
||||
sudo mkdir -p /etc/apt/keyrings &&
|
||||
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc &&
|
||||
sudo chmod a+r /etc/apt/keyrings/sagernet.asc &&
|
||||
echo '
|
||||
Types: deb
|
||||
URIs: https://deb.sagernet.org/
|
||||
Suites: *
|
||||
Components: *
|
||||
Enabled: yes
|
||||
Signed-By: /etc/apt/keyrings/sagernet.asc
|
||||
' | sudo tee /etc/apt/sources.list.d/sagernet.sources &&
|
||||
sudo apt-get update &&
|
||||
sudo apt-get install sing-box # or sing-box-beta
|
||||
```
|
||||
|
||||
=== ":material-redhat: Redhat / DNF"
|
||||
=== ":material-redhat: Redhat / DNF 5"
|
||||
|
||||
```bash
|
||||
sudo dnf -y install dnf-plugins-core
|
||||
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo
|
||||
sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo &&
|
||||
sudo dnf install sing-box # or sing-box-beta
|
||||
```
|
||||
|
||||
=== ":material-redhat: Redhat / DNF 4"
|
||||
|
||||
```bash
|
||||
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo &&
|
||||
sudo dnf -y install dnf-plugins-core &&
|
||||
sudo dnf install sing-box # or sing-box-beta
|
||||
```
|
||||
(This applies to any distribution that uses `dnf` as the package manager: Fedora, CentOS, even OpenSUSE with DNF installed.)
|
||||
|
||||
## :material-download-box: Manual Installation
|
||||
|
||||
=== ":material-debian: Debian / DEB"
|
||||
The script download and install the latest package from GitHub releases
|
||||
for deb or rpm based Linux distributions, ArchLinux and OpenWrt.
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://sing-box.app/deb-install.sh)
|
||||
```
|
||||
```shell
|
||||
curl -fsSL https://sing-box.app/install.sh | sh
|
||||
```
|
||||
|
||||
=== ":material-redhat: Redhat / RPM"
|
||||
or latest beta:
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://sing-box.app/rpm-install.sh)
|
||||
```
|
||||
(This applies to any distribution that uses `rpm` and `systemd`. Because of how `rpm` defines dependencies, if it installs, it probably works.)
|
||||
```shell
|
||||
curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta
|
||||
```
|
||||
|
||||
=== ":simple-archlinux: Archlinux / PKG"
|
||||
or specific version:
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://sing-box.app/arch-install.sh)
|
||||
```
|
||||
```shell
|
||||
curl -fsSL https://sing-box.app/install.sh | sh -s -- --version <version>
|
||||
```
|
||||
|
||||
## :material-book-lock-open: Managed Installation
|
||||
|
||||
|
||||
@@ -9,43 +9,55 @@ icon: material/package
|
||||
=== ":material-debian: Debian / APT"
|
||||
|
||||
```bash
|
||||
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc
|
||||
sudo chmod a+r /etc/apt/keyrings/sagernet.asc
|
||||
echo "deb [arch=`dpkg --print-architecture` signed-by=/etc/apt/keyrings/sagernet.asc] https://deb.sagernet.org/ * *" | \
|
||||
sudo tee /etc/apt/sources.list.d/sagernet.list > /dev/null
|
||||
sudo apt-get update
|
||||
sudo apt-get install sing-box # or sing-box-beta
|
||||
sudo mkdir -p /etc/apt/keyrings &&
|
||||
sudo curl -fsSL https://sing-box.app/gpg.key -o /etc/apt/keyrings/sagernet.asc &&
|
||||
sudo chmod a+r /etc/apt/keyrings/sagernet.asc &&
|
||||
echo '
|
||||
Types: deb
|
||||
URIs: https://deb.sagernet.org/
|
||||
Suites: *
|
||||
Components: *
|
||||
Enabled: yes
|
||||
Signed-By: /etc/apt/keyrings/sagernet.asc
|
||||
' | sudo tee /etc/apt/sources.list.d/sagernet.sources &&
|
||||
sudo apt-get update &&
|
||||
sudo apt-get install sing-box # or sing-box-beta
|
||||
```
|
||||
|
||||
=== ":material-redhat: Redhat / DNF"
|
||||
=== ":material-redhat: Redhat / DNF 5"
|
||||
|
||||
```bash
|
||||
sudo dnf -y install dnf-plugins-core
|
||||
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo
|
||||
sudo dnf config-manager addrepo --from-repofile=https://sing-box.app/sing-box.repo &&
|
||||
sudo dnf install sing-box # or sing-box-beta
|
||||
```
|
||||
|
||||
=== ":material-redhat: Redhat / DNF 4"
|
||||
|
||||
```bash
|
||||
sudo dnf config-manager --add-repo https://sing-box.app/sing-box.repo &&
|
||||
sudo dnf -y install dnf-plugins-core &&
|
||||
sudo dnf install sing-box # or sing-box-beta
|
||||
```
|
||||
(这适用于任何使用 `dnf` 作为包管理器的发行版:Fedora、CentOS,甚至安装了 DNF 的 OpenSUSE。)
|
||||
|
||||
## :material-download-box: 手动安装
|
||||
|
||||
=== ":material-debian: Debian / DEB"
|
||||
该脚本从 GitHub 发布中下载并安装最新的软件包,适用于基于 deb 或 rpm 的 Linux 发行版、ArchLinux 和 OpenWrt。
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://sing-box.app/deb-install.sh)
|
||||
```
|
||||
```shell
|
||||
curl -fsSL https://sing-box.app/install.sh | sh
|
||||
```
|
||||
|
||||
=== ":material-redhat: Redhat / RPM"
|
||||
或最新测试版:
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://sing-box.app/rpm-install.sh)
|
||||
```
|
||||
(这适用于任何使用 `rpm` 和 `systemd` 的发行版。由于 `rpm` 定义依赖关系的方式,如果安装成功,就多半能用。)
|
||||
```shell
|
||||
curl -fsSL https://sing-box.app/install.sh | sh -s -- --beta
|
||||
```
|
||||
|
||||
=== ":simple-archlinux: Archlinux / PKG"
|
||||
或指定版本:
|
||||
|
||||
```bash
|
||||
bash <(curl -fsSL https://sing-box.app/arch-install.sh)
|
||||
```
|
||||
```shell
|
||||
curl -fsSL https://sing-box.app/install.sh | sh -s -- --version <version>
|
||||
```
|
||||
|
||||
## :material-book-lock-open: 托管安装
|
||||
|
||||
|
||||
116
docs/installation/tools/install.sh
Executable file
116
docs/installation/tools/install.sh
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/bin/sh
|
||||
|
||||
download_beta=false
|
||||
download_version=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--beta)
|
||||
download_beta=true
|
||||
shift
|
||||
;;
|
||||
--version)
|
||||
shift
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "Missing argument for --version"
|
||||
echo "Usage: $0 [--beta] [--version <version>]"
|
||||
exit 1
|
||||
fi
|
||||
download_version="$1"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1"
|
||||
echo "Usage: $0 [--beta] [--version <version>]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if command -v pacman >/dev/null 2>&1; then
|
||||
os="linux"
|
||||
arch=$(uname -m)
|
||||
package_suffix=".pkg.tar.zst"
|
||||
package_install="pacman -U --noconfirm"
|
||||
elif command -v dpkg >/dev/null 2>&1; then
|
||||
os="linux"
|
||||
arch=$(dpkg --print-architecture)
|
||||
package_suffix=".deb"
|
||||
package_install="dpkg -i"
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
os="linux"
|
||||
arch=$(uname -m)
|
||||
package_suffix=".rpm"
|
||||
package_install="dnf install -y"
|
||||
elif command -v rpm >/dev/null 2>&1; then
|
||||
os="linux"
|
||||
arch=$(uname -m)
|
||||
package_suffix=".rpm"
|
||||
package_install="rpm -i"
|
||||
elif command -v opkg >/dev/null 2>&1; then
|
||||
os="openwrt"
|
||||
. /etc/os-release
|
||||
arch="$OPENWRT_ARCH"
|
||||
package_suffix=".ipk"
|
||||
package_install="opkg update && opkg install"
|
||||
else
|
||||
echo "Missing supported package manager."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$download_version" ]; then
|
||||
if [ "$download_beta" != "true" ]; then
|
||||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
latest_release=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases/latest)
|
||||
else
|
||||
latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases/latest)
|
||||
fi
|
||||
curl_exit_status=$?
|
||||
if [ $curl_exit_status -ne 0 ]; then
|
||||
exit $curl_exit_status
|
||||
fi
|
||||
if [ "$(echo "$latest_release" | grep tag_name | wc -l)" -eq 0 ]; then
|
||||
echo "$latest_release"
|
||||
exit 1
|
||||
fi
|
||||
download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[", v]//g')
|
||||
else
|
||||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
latest_release=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/SagerNet/sing-box/releases)
|
||||
else
|
||||
latest_release=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases)
|
||||
fi
|
||||
curl_exit_status=$?
|
||||
if [ $curl_exit_status -ne 0 ]; then
|
||||
exit $curl_exit_status
|
||||
fi
|
||||
if [ "$(echo "$latest_release" | grep tag_name | wc -l)" -eq 0 ]; then
|
||||
echo "$latest_release"
|
||||
exit 1
|
||||
fi
|
||||
download_version=$(echo "$latest_release" | grep tag_name | head -n 1 | awk -F: '{print $2}' | sed 's/[", v]//g')
|
||||
fi
|
||||
fi
|
||||
|
||||
package_name="sing-box_${download_version}_${os}_${arch}${package_suffix}"
|
||||
package_url="https://github.com/SagerNet/sing-box/releases/download/v${download_version}/${package_name}"
|
||||
|
||||
echo "Downloading $package_url"
|
||||
if [ -n "$GITHUB_TOKEN" ]; then
|
||||
curl --fail -Lo "$package_name" -H "Authorization: token ${GITHUB_TOKEN}" "$package_url"
|
||||
else
|
||||
curl --fail -Lo "$package_name" "$package_url"
|
||||
fi
|
||||
|
||||
curl_exit_status=$?
|
||||
if [ $curl_exit_status -ne 0 ]; then
|
||||
exit $curl_exit_status
|
||||
fi
|
||||
|
||||
if command -v sudo >/dev/null 2>&1; then
|
||||
package_install="sudo $package_install"
|
||||
fi
|
||||
|
||||
echo "$package_install $package_name"
|
||||
sh -c "$package_install \"$package_name\""
|
||||
rm -f "$package_name"
|
||||
71
examples/amnezia/client.json
Normal file
71
examples/amnezia/client.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "error"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"address": "local",
|
||||
"detour": "direct"
|
||||
}
|
||||
]
|
||||
},
|
||||
"endpoints": [
|
||||
{
|
||||
"type": "wireguard",
|
||||
"tag": "wireguard-out",
|
||||
"mtu": 1408,
|
||||
"address": null,
|
||||
"private_key": "",
|
||||
"listen_port": 10000,
|
||||
"peers": [
|
||||
{
|
||||
"address": "example.com",
|
||||
"port": 10001,
|
||||
"reserved": "AAAA"
|
||||
}
|
||||
],
|
||||
"udp_timeout": "5m0s",
|
||||
"amnezia": {
|
||||
"jc": 120,
|
||||
"jmin": 23,
|
||||
"jmax": 911,
|
||||
"h1": 1,
|
||||
"h2": 2,
|
||||
"h3": 3,
|
||||
"h4": 4
|
||||
}
|
||||
}
|
||||
],
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "mixed",
|
||||
"tag": "mixed-in",
|
||||
"listen_port": 7897
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"port": 53,
|
||||
"outbound": "dns-out"
|
||||
}
|
||||
],
|
||||
"final": "wireguard-out",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
56
examples/mieru/client.json
Normal file
56
examples/mieru/client.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "error"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"address": "local",
|
||||
"detour": "direct"
|
||||
}
|
||||
]
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "mixed",
|
||||
"tag": "mixed-in",
|
||||
"listen_port": 7897
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "mieru",
|
||||
"tag": "mieru-out",
|
||||
"server": "example.com",
|
||||
"server_port": 27017,
|
||||
"server_ports": "27017-27019",
|
||||
"transport": "TCP",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"multiplexing": "MULTIPLEXING_LOW"
|
||||
// Dial Fields
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"port": 53,
|
||||
"outbound": "dns-out"
|
||||
}
|
||||
],
|
||||
"final": "mieru-out",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
44
examples/sdns/client.json
Normal file
44
examples/sdns/client.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "error"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"address": "sdns://AQMAAAAAAAAAETk0LjE0MC4xNS4xNTo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
"detour": "direct"
|
||||
}
|
||||
]
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "mixed",
|
||||
"tag": "mixed-in",
|
||||
"listen_port": 7897
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"port": 53,
|
||||
"outbound": "dns-out"
|
||||
}
|
||||
],
|
||||
"final": "direct",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
79
examples/unified_delay/client.json
Normal file
79
examples/unified_delay/client.json
Normal file
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "error"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"address": "local",
|
||||
"detour": "direct"
|
||||
}
|
||||
]
|
||||
},
|
||||
"endpoints": [
|
||||
{
|
||||
"type": "warp",
|
||||
"tag": "warp-out",
|
||||
"listen_port": 10000,
|
||||
"udp_timeout": "5m0s",
|
||||
"amnezia": {
|
||||
"jc": 120,
|
||||
"jmin": 23,
|
||||
"jmax": 911,
|
||||
"h1": 1,
|
||||
"h2": 2,
|
||||
"h3": 3,
|
||||
"h4": 4
|
||||
},
|
||||
"profile": {
|
||||
"detour": "direct"
|
||||
}
|
||||
}
|
||||
],
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "mixed",
|
||||
"tag": "mixed-in",
|
||||
"listen_port": 7897
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "selector",
|
||||
"tag": "select-out",
|
||||
"outbounds": [
|
||||
"direct",
|
||||
"warp-out"
|
||||
],
|
||||
"default": "direct",
|
||||
"interrupt_exist_connections": true
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"port": 53,
|
||||
"outbound": "dns-out"
|
||||
}
|
||||
],
|
||||
"final": "direct",
|
||||
"auto_detect_interface": true
|
||||
},
|
||||
"experimental": {
|
||||
"unified_delay": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
75
examples/warp/client.json
Normal file
75
examples/warp/client.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "error"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"address": "local",
|
||||
"detour": "direct"
|
||||
}
|
||||
]
|
||||
},
|
||||
"endpoints": [
|
||||
{
|
||||
"type": "warp",
|
||||
"tag": "warp-out",
|
||||
"listen_port": 10000,
|
||||
"udp_timeout": "5m0s",
|
||||
"amnezia": {
|
||||
"jc": 120,
|
||||
"jmin": 23,
|
||||
"jmax": 911,
|
||||
"h1": 1,
|
||||
"h2": 2,
|
||||
"h3": 3,
|
||||
"h4": 4
|
||||
},
|
||||
"profile": {
|
||||
"detour": "direct",
|
||||
// for getting existing WARP device profile
|
||||
"id": "",
|
||||
"private_key": "",
|
||||
"auth_token": ""
|
||||
}
|
||||
// Dial Fields
|
||||
}
|
||||
],
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "mixed",
|
||||
"tag": "mixed-in",
|
||||
"listen_port": 7897
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"port": 53,
|
||||
"outbound": "dns-out"
|
||||
}
|
||||
],
|
||||
"final": "warp-out",
|
||||
"auto_detect_interface": true
|
||||
},
|
||||
"experimental": {
|
||||
"cache_file": {
|
||||
"enabled": true,
|
||||
"store_warp_config": true
|
||||
}
|
||||
}
|
||||
}
|
||||
98
examples/xhttp/client.json
Normal file
98
examples/xhttp/client.json
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "error"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"address": "local",
|
||||
"detour": "direct"
|
||||
}
|
||||
]
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "mixed",
|
||||
"tag": "mixed-in",
|
||||
"listen_port": 7897
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "vless",
|
||||
"tag": "vless-out",
|
||||
"server": "example.com",
|
||||
"server_port": 443,
|
||||
"uuid": "3179dce2-2ff9-413c-85b4-c1d53ed41668",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.com",
|
||||
"alpn": "h2" // h3 for QUIC
|
||||
},
|
||||
"transport": {
|
||||
"type": "xhttp",
|
||||
"mode": "stream-up",
|
||||
"host": "example.com",
|
||||
"path": "/xhttp",
|
||||
"domain_strategy": "prefer_ipv4",
|
||||
"xmux": {
|
||||
"max_concurrency": "0-1",
|
||||
"max_connections": "0-1",
|
||||
"c_max_reuse_times": "0-1",
|
||||
"h_max_request_times": "0-1",
|
||||
"h_max_reusable_secs": "0-1",
|
||||
"h_keep_alive_period": 60
|
||||
},
|
||||
"download": {
|
||||
"mode": "",
|
||||
"host": "example.com",
|
||||
"path": "/xhttp",
|
||||
"domain_strategy": "prefer_ipv4",
|
||||
"x_padding_bytes": "0-0",
|
||||
"sc_max_each_post_bytes": "0-0",
|
||||
"sc_min_posts_interval_ms": "0-0",
|
||||
"sc_stream_up_server_secs": "0-0",
|
||||
"xmux": {
|
||||
"max_concurrency": "0-1",
|
||||
"max_connections": "0-1",
|
||||
"c_max_reuse_times": "0-1",
|
||||
"h_max_request_times": "0-1",
|
||||
"h_max_reusable_secs": "0-1",
|
||||
"h_keep_alive_period": 60
|
||||
},
|
||||
"server": "example.com",
|
||||
"server_port": 443,
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.com",
|
||||
"alpn": "h2" // h3 for QUIC
|
||||
},
|
||||
"detour": "direct"
|
||||
}
|
||||
},
|
||||
"packet_encoding": ""
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"port": 53,
|
||||
"outbound": "dns-out"
|
||||
}
|
||||
],
|
||||
"final": "vless-out",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
63
examples/xhttp/server.json
Normal file
63
examples/xhttp/server.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "error"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"address": "local",
|
||||
"detour": "direct"
|
||||
}
|
||||
]
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "vless",
|
||||
"tag": "vless-out",
|
||||
"listen": "0.0.0.0",
|
||||
"listen_port": 443,
|
||||
"users": [
|
||||
{
|
||||
"name": "user",
|
||||
"uuid": "3179dce2-2ff9-413c-85b4-c1d53ed41668"
|
||||
}
|
||||
],
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.com",
|
||||
"alpn": "h2", // h3 for QUIC
|
||||
"certificate_path": "/path/to/fullchain.pem",
|
||||
"key_path": "/path/to/privkey.pem"
|
||||
},
|
||||
"transport": {
|
||||
"type": "xhttp",
|
||||
"mode": "stream-up",
|
||||
"path": "/xhttp",
|
||||
}
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"outbound": "dns-out"
|
||||
},
|
||||
{
|
||||
"port": 53,
|
||||
"outbound": "dns-out"
|
||||
}
|
||||
],
|
||||
"final": "direct",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ type CacheFile struct {
|
||||
cacheID []byte
|
||||
storeFakeIP bool
|
||||
storeRDRC bool
|
||||
storeWARPConfig bool
|
||||
rdrcTimeout time.Duration
|
||||
DB *bbolt.DB
|
||||
saveMetadataTimer *time.Timer
|
||||
@@ -80,16 +81,17 @@ func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
|
||||
}
|
||||
}
|
||||
return &CacheFile{
|
||||
ctx: ctx,
|
||||
path: filemanager.BasePath(ctx, path),
|
||||
cacheID: cacheIDBytes,
|
||||
storeFakeIP: options.StoreFakeIP,
|
||||
storeRDRC: options.StoreRDRC,
|
||||
rdrcTimeout: rdrcTimeout,
|
||||
saveDomain: make(map[netip.Addr]string),
|
||||
saveAddress4: make(map[string]netip.Addr),
|
||||
saveAddress6: make(map[string]netip.Addr),
|
||||
saveRDRC: make(map[saveRDRCCacheKey]bool),
|
||||
ctx: ctx,
|
||||
path: filemanager.BasePath(ctx, path),
|
||||
cacheID: cacheIDBytes,
|
||||
storeFakeIP: options.StoreFakeIP,
|
||||
storeRDRC: options.StoreRDRC,
|
||||
storeWARPConfig: options.StoreWARPConfig,
|
||||
rdrcTimeout: rdrcTimeout,
|
||||
saveDomain: make(map[netip.Addr]string),
|
||||
saveAddress4: make(map[string]netip.Addr),
|
||||
saveAddress6: make(map[string]netip.Addr),
|
||||
saveRDRC: make(map[saveRDRCCacheKey]bool),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,3 +318,40 @@ func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error {
|
||||
return bucket.Put([]byte(tag), setBinary)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *CacheFile) StoreWARPConfig() bool {
|
||||
return c.storeWARPConfig
|
||||
}
|
||||
|
||||
func (c *CacheFile) LoadWARPConfig(tag string) *adapter.SavedBinary {
|
||||
var savedConfig adapter.SavedBinary
|
||||
err := c.DB.View(func(t *bbolt.Tx) error {
|
||||
bucket := c.bucket(t, bucketRuleSet)
|
||||
if bucket == nil {
|
||||
return os.ErrNotExist
|
||||
}
|
||||
configBinary := bucket.Get([]byte(tag))
|
||||
if len(configBinary) == 0 {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
return savedConfig.UnmarshalBinary(configBinary)
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &savedConfig
|
||||
}
|
||||
|
||||
func (c *CacheFile) SaveWARPConfig(tag string, set *adapter.SavedBinary) error {
|
||||
return c.DB.Batch(func(t *bbolt.Tx) error {
|
||||
bucket, err := c.createBucket(t, bucketRuleSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configBinary, err := set.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bucket.Put([]byte(tag), configBinary)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -69,6 +69,10 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
|
||||
cancel()
|
||||
return nil, E.Cause(err, "create service")
|
||||
}
|
||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||
if experimentalOptions.UnifiedDelay != nil && experimentalOptions.UnifiedDelay.Enabled {
|
||||
ctx = urltest.ContextWithIsUnifiedDelay(ctx)
|
||||
}
|
||||
runtimeDebug.FreeOSMemory()
|
||||
return &BoxService{
|
||||
ctx: ctx,
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/humanize"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/locale"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common/byteformats"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -75,11 +75,11 @@ func Version() string {
|
||||
}
|
||||
|
||||
func FormatBytes(length int64) string {
|
||||
return humanize.Bytes(uint64(length))
|
||||
return byteformats.FormatBytes(uint64(length))
|
||||
}
|
||||
|
||||
func FormatMemoryBytes(length int64) string {
|
||||
return humanize.MemoryBytes(uint64(length))
|
||||
return byteformats.FormatMemoryBytes(uint64(length))
|
||||
}
|
||||
|
||||
func FormatDuration(duration int64) string {
|
||||
|
||||
91
go.mod
91
go.mod
@@ -1,11 +1,14 @@
|
||||
module github.com/sagernet/sing-box
|
||||
|
||||
go 1.20
|
||||
go 1.24.1
|
||||
|
||||
toolchain go1.24.3
|
||||
|
||||
require (
|
||||
github.com/caddyserver/certmagic v0.20.0
|
||||
github.com/cloudflare/circl v1.3.7
|
||||
github.com/cloudflare/circl v1.6.1
|
||||
github.com/cretz/bine v0.2.0
|
||||
github.com/enfein/mieru/v3 v3.13.0
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/gofrs/uuid/v5 v5.3.2
|
||||
@@ -15,8 +18,9 @@ require (
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
|
||||
github.com/mholt/acmez v1.2.0
|
||||
github.com/miekg/dns v1.1.63
|
||||
github.com/miekg/dns v1.1.66
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
github.com/quic-go/quic-go v0.52.0
|
||||
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
||||
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
|
||||
@@ -26,79 +30,96 @@ require (
|
||||
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
|
||||
github.com/sagernet/quic-go v0.49.0-beta.1
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||
github.com/sagernet/sing v0.6.6
|
||||
github.com/sagernet/sing-dns v0.4.1
|
||||
github.com/sagernet/sing-mux v0.3.1
|
||||
github.com/sagernet/sing-quic v0.4.1
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0
|
||||
github.com/sagernet/sing v0.6.10
|
||||
github.com/sagernet/sing-dns v0.4.5
|
||||
github.com/sagernet/sing-mux v0.3.2
|
||||
github.com/sagernet/sing-quic v0.4.3
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||
github.com/sagernet/sing-shadowtls v0.2.0
|
||||
github.com/sagernet/sing-tun v0.6.4
|
||||
github.com/sagernet/sing-vmess v0.2.0
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
||||
github.com/sagernet/sing-tun v0.6.5
|
||||
github.com/sagernet/sing-vmess v0.2.3
|
||||
github.com/sagernet/smux v1.5.34-mod.2
|
||||
github.com/sagernet/utls v1.6.7
|
||||
github.com/sagernet/wireguard-go v0.0.1-beta.5
|
||||
github.com/sagernet/wireguard-go v0.0.1-beta.7
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.uber.org/zap v1.27.0
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
golang.org/x/mod v0.20.0
|
||||
golang.org/x/net v0.34.0
|
||||
golang.org/x/sys v0.30.0
|
||||
golang.org/x/crypto v0.38.0
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394
|
||||
golang.org/x/mod v0.24.0
|
||||
golang.org/x/net v0.40.0
|
||||
golang.org/x/sys v0.33.0
|
||||
golang.org/x/time v0.11.0
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
google.golang.org/grpc v1.63.2
|
||||
google.golang.org/protobuf v1.33.0
|
||||
google.golang.org/grpc v1.72.1
|
||||
google.golang.org/protobuf v1.36.6
|
||||
howett.net/plist v1.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/golibs v0.32.7 // indirect
|
||||
github.com/ameshkov/dnscrypt/v2 v2.4.0 // indirect
|
||||
github.com/ameshkov/dnsstamps v1.0.3 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.7 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
golang.org/x/tools v0.32.0 // indirect
|
||||
)
|
||||
|
||||
//replace github.com/sagernet/sing => ../sing
|
||||
|
||||
require (
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/gobwas/httphead v0.1.0 // indirect
|
||||
github.com/gobwas/pool v0.2.1 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/klauspost/compress v1.17.8 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/libdns/libdns v0.2.2 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.7 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tevino/abool/v2 v2.1.0 // indirect
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/vishvananda/netns v0.0.5 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/blake3 v1.3.0 // indirect
|
||||
lukechampine.com/blake3 v1.4.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/sagernet/wireguard-go => github.com/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec
|
||||
|
||||
replace github.com/sagernet/sing-dns => github.com/shtorm-7/sing-dns v0.4.5-extended-1.0.0
|
||||
|
||||
replace github.com/ameshkov/dnscrypt/v2 => github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0
|
||||
|
||||
183
go.sum
183
go.sum
@@ -1,13 +1,17 @@
|
||||
github.com/AdguardTeam/golibs v0.32.7 h1:3dmGlAVgmvquCCwHsvEl58KKcRAK3z1UnjMnwSIeDH4=
|
||||
github.com/AdguardTeam/golibs v0.32.7/go.mod h1:bE8KV1zqTzgZjmjFyBJ9f9O5DEKO717r7e57j1HclJA=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
|
||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
|
||||
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
||||
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
||||
@@ -16,13 +20,20 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU=
|
||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
|
||||
github.com/enfein/mieru/v3 v3.13.0 h1:eGyxLGkb+lut9ebmx+BGwLJ5UMbEc/wGIYO0AXEKy98=
|
||||
github.com/enfein/mieru/v3 v3.13.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec h1:jXekDkSozctYj5JQlV1mCsIW+qHpIkgSFhOIpgRSB1I=
|
||||
github.com/getlantern/wireguard-go v0.0.1-beta.5.0.20250303165430-793006c422ec/go.mod h1:akc2Wh+rX9bFFNnHJGsQ8VIV3eJI1LXJYgx2Y+8lcW8=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
@@ -34,15 +45,18 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
|
||||
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
|
||||
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
@@ -53,14 +67,15 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS
|
||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ=
|
||||
github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE=
|
||||
github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054=
|
||||
@@ -78,23 +93,26 @@ github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVr
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||
github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
|
||||
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
|
||||
github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0=
|
||||
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
|
||||
github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/quic-go v0.52.0 h1:/SlHrCRElyaU6MaEPKqKr9z83sBg2v4FLLvWM+Z47pA=
|
||||
github.com/quic-go/quic-go v0.52.0/go.mod h1:MFlGGpcpJqRAfmYi6NC2cptDPSxRWTOGNuP4wqrWmzQ=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 h1:qi+ijeREa0yfAaO+NOcZ81gv4uzOfALUIdhkiIFvmG4=
|
||||
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1/go.mod h1:JULDuzTMn2gyZFcjpTVZP4/UuwAdbHJ0bum2RdjXojU=
|
||||
@@ -118,55 +136,83 @@ github.com/sagernet/quic-go v0.49.0-beta.1 h1:3LdoCzVVfYRibZns1tYWSIoB65fpTmrwy+
|
||||
github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8WHNsRs71b3Lt1+p/U=
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
|
||||
github.com/sagernet/sing v0.6.6-0.20250406121928-926a5a1e8bb7 h1:ZJauxLmH12Gzv3nucfjsSBQw9UA8t7Sxu8pYHBSP2TU=
|
||||
github.com/sagernet/sing v0.6.6-0.20250406121928-926a5a1e8bb7/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.6.6 h1:3JkvJ0vqDj/jJcx0a+ve/6lMOrSzZm30I3wrIuZtmRE=
|
||||
github.com/sagernet/sing v0.6.6/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-dns v0.4.1 h1:nozS7iqpxZ7aV73oHbkD/8haOvf3XXDCgT//8NdYirk=
|
||||
github.com/sagernet/sing-dns v0.4.1/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8=
|
||||
github.com/sagernet/sing-mux v0.3.1 h1:kvCc8HyGAskDHDQ0yQvoTi/7J4cZPB/VJMsAM3MmdQI=
|
||||
github.com/sagernet/sing-mux v0.3.1/go.mod h1:Mkdz8LnDstthz0HWuA/5foncnDIdcNN5KZ6AdJX+x78=
|
||||
github.com/sagernet/sing-quic v0.4.1 h1:pxlMa4efZu/M07RgGagNNDDyl6ZUwpmNUjRTpgHOWK4=
|
||||
github.com/sagernet/sing-quic v0.4.1/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.6.10 h1:Jey1tePgH9bjFuK1fQI3D9T+bPOQ4SdHMjuS4sYjDv4=
|
||||
github.com/sagernet/sing v0.6.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE=
|
||||
github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||
github.com/sagernet/sing-quic v0.4.3 h1:OZ/kGvSzjtYg+t0DY3F606hlT5LeiQQXDxfBopcRryQ=
|
||||
github.com/sagernet/sing-quic v0.4.3/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||
github.com/sagernet/sing-shadowtls v0.2.0 h1:cLKe4OAOFwuhmAIuPLj//CIL7Q9js+pIDardhJ+/osk=
|
||||
github.com/sagernet/sing-shadowtls v0.2.0/go.mod h1:agU+Fw5X+xnWVyRHyFthoZCX3MfWKCFPm4JUf+1oaxo=
|
||||
github.com/sagernet/sing-tun v0.6.4 h1:3Iew6UtAf1+mucVeHKNhAEQI5xmq3CUCbGptUbjebts=
|
||||
github.com/sagernet/sing-tun v0.6.4/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||
github.com/sagernet/sing-vmess v0.2.0 h1:pCMGUXN2k7RpikQV65/rtXtDHzb190foTfF9IGTMZrI=
|
||||
github.com/sagernet/sing-vmess v0.2.0/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA=
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
||||
github.com/sagernet/sing-tun v0.6.5 h1:nGfD6GNq/r0tEjdZHOV3BS6fydSmd4kBAokU5rffssg=
|
||||
github.com/sagernet/sing-tun v0.6.5/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||
github.com/sagernet/sing-vmess v0.2.3 h1:z6Ym8dnZG7k1fP3+54vz8G0tvRVJeOoTFFeUPwXTD44=
|
||||
github.com/sagernet/sing-vmess v0.2.3/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA=
|
||||
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
||||
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
|
||||
github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8=
|
||||
github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM=
|
||||
github.com/sagernet/wireguard-go v0.0.1-beta.5 h1:aBEsxJUMEONwOZqKPIkuAcv4zJV5p6XlzEN04CF0FXc=
|
||||
github.com/sagernet/wireguard-go v0.0.1-beta.5/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
||||
github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0 h1:e5s7RKBd2rIPR0StbvZ2vTVtJ5jDTsTk5wtIIapZTRg=
|
||||
github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0/go.mod h1:WpEFV2uhebXb8Jhes/5/fSdpmhGV8TL22RDaeWwV6hI=
|
||||
github.com/shtorm-7/sing-dns v0.4.5-extended-1.0.0 h1:iR0BTDEYwuYfQHwiySBhDOUHaipcizGb54OmC1C6Pz0=
|
||||
github.com/shtorm-7/sing-dns v0.4.5-extended-1.0.0/go.mod h1:HWuvH9L2ZwuffhzX5J5ZOjrhLQ8DB5qT9ie/YTXW1nU=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tevino/abool/v2 v2.1.0 h1:7w+Vf9f/5gmKT4m4qkayb33/92M+Um45F2BkHOR+L/c=
|
||||
github.com/tevino/abool/v2 v2.1.0/go.mod h1:+Lmlqk6bHDWHqN1cbxqhwEAwMPXgc8I1SDEamtseuXY=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
|
||||
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
@@ -174,50 +220,51 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
||||
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
|
||||
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf h1:dHDlF3CWxQkefK9IJx+O8ldY0gLygvrlYRBNbPqDWuY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
|
||||
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -227,5 +274,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
|
||||
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
||||
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/sagernet/sing-box/protocol/dns"
|
||||
"github.com/sagernet/sing-box/protocol/group"
|
||||
"github.com/sagernet/sing-box/protocol/http"
|
||||
"github.com/sagernet/sing-box/protocol/mieru"
|
||||
"github.com/sagernet/sing-box/protocol/mixed"
|
||||
"github.com/sagernet/sing-box/protocol/naive"
|
||||
"github.com/sagernet/sing-box/protocol/redirect"
|
||||
@@ -75,6 +76,7 @@ func OutboundRegistry() *outbound.Registry {
|
||||
ssh.RegisterOutbound(registry)
|
||||
shadowtls.RegisterOutbound(registry)
|
||||
vless.RegisterOutbound(registry)
|
||||
mieru.RegisterOutbound(registry)
|
||||
|
||||
registerQUICOutbounds(registry)
|
||||
registerWireGuardOutbound(registry)
|
||||
|
||||
@@ -14,4 +14,5 @@ func registerWireGuardOutbound(registry *outbound.Registry) {
|
||||
|
||||
func registerWireGuardEndpoint(registry *endpoint.Registry) {
|
||||
wireguard.RegisterEndpoint(registry)
|
||||
wireguard.RegisterWARPEndpoint(registry)
|
||||
}
|
||||
|
||||
@@ -1,43 +1,14 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/common/humanize"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
)
|
||||
import "github.com/sagernet/sing/common/byteformats"
|
||||
|
||||
type DebugOptions struct {
|
||||
Listen string `json:"listen,omitempty"`
|
||||
GCPercent *int `json:"gc_percent,omitempty"`
|
||||
MaxStack *int `json:"max_stack,omitempty"`
|
||||
MaxThreads *int `json:"max_threads,omitempty"`
|
||||
PanicOnFault *bool `json:"panic_on_fault,omitempty"`
|
||||
TraceBack string `json:"trace_back,omitempty"`
|
||||
MemoryLimit MemoryBytes `json:"memory_limit,omitempty"`
|
||||
OOMKiller *bool `json:"oom_killer,omitempty"`
|
||||
}
|
||||
|
||||
type MemoryBytes uint64
|
||||
|
||||
func (l MemoryBytes) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(humanize.MemoryBytes(uint64(l)))
|
||||
}
|
||||
|
||||
func (l *MemoryBytes) UnmarshalJSON(bytes []byte) error {
|
||||
var valueInteger int64
|
||||
err := json.Unmarshal(bytes, &valueInteger)
|
||||
if err == nil {
|
||||
*l = MemoryBytes(valueInteger)
|
||||
return nil
|
||||
}
|
||||
var valueString string
|
||||
err = json.Unmarshal(bytes, &valueString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parsedValue, err := humanize.ParseMemoryBytes(valueString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*l = MemoryBytes(parsedValue)
|
||||
return nil
|
||||
Listen string `json:"listen,omitempty"`
|
||||
GCPercent *int `json:"gc_percent,omitempty"`
|
||||
MaxStack *int `json:"max_stack,omitempty"`
|
||||
MaxThreads *int `json:"max_threads,omitempty"`
|
||||
PanicOnFault *bool `json:"panic_on_fault,omitempty"`
|
||||
TraceBack string `json:"trace_back,omitempty"`
|
||||
MemoryLimit *byteformats.MemoryBytes `json:"memory_limit,omitempty"`
|
||||
OOMKiller *bool `json:"oom_killer,omitempty"`
|
||||
}
|
||||
|
||||
@@ -3,19 +3,21 @@ package option
|
||||
import "github.com/sagernet/sing/common/json/badoption"
|
||||
|
||||
type ExperimentalOptions struct {
|
||||
CacheFile *CacheFileOptions `json:"cache_file,omitempty"`
|
||||
ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"`
|
||||
V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"`
|
||||
Debug *DebugOptions `json:"debug,omitempty"`
|
||||
CacheFile *CacheFileOptions `json:"cache_file,omitempty"`
|
||||
ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"`
|
||||
V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"`
|
||||
UnifiedDelay *UnifiedDelayOptions `json:"unified_delay,omitempty"`
|
||||
Debug *DebugOptions `json:"debug,omitempty"`
|
||||
}
|
||||
|
||||
type CacheFileOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
CacheID string `json:"cache_id,omitempty"`
|
||||
StoreFakeIP bool `json:"store_fakeip,omitempty"`
|
||||
StoreRDRC bool `json:"store_rdrc,omitempty"`
|
||||
RDRCTimeout badoption.Duration `json:"rdrc_timeout,omitempty"`
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
CacheID string `json:"cache_id,omitempty"`
|
||||
StoreFakeIP bool `json:"store_fakeip,omitempty"`
|
||||
StoreRDRC bool `json:"store_rdrc,omitempty"`
|
||||
StoreWARPConfig bool `json:"store_warp_config,omitempty"`
|
||||
RDRCTimeout badoption.Duration `json:"rdrc_timeout,omitempty"`
|
||||
}
|
||||
|
||||
type ClashAPIOptions struct {
|
||||
@@ -52,3 +54,7 @@ type V2RayStatsServiceOptions struct {
|
||||
Outbounds []string `json:"outbounds,omitempty"`
|
||||
Users []string `json:"users,omitempty"`
|
||||
}
|
||||
|
||||
type UnifiedDelayOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
package option
|
||||
|
||||
import "github.com/sagernet/sing/common/byteformats"
|
||||
|
||||
type HysteriaInboundOptions struct {
|
||||
ListenOptions
|
||||
Up string `json:"up,omitempty"`
|
||||
UpMbps int `json:"up_mbps,omitempty"`
|
||||
Down string `json:"down,omitempty"`
|
||||
DownMbps int `json:"down_mbps,omitempty"`
|
||||
Obfs string `json:"obfs,omitempty"`
|
||||
Users []HysteriaUser `json:"users,omitempty"`
|
||||
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
|
||||
ReceiveWindowClient uint64 `json:"recv_window_client,omitempty"`
|
||||
MaxConnClient int `json:"max_conn_client,omitempty"`
|
||||
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
|
||||
Up *byteformats.NetworkBytesCompat `json:"up,omitempty"`
|
||||
UpMbps int `json:"up_mbps,omitempty"`
|
||||
Down *byteformats.NetworkBytesCompat `json:"down,omitempty"`
|
||||
DownMbps int `json:"down_mbps,omitempty"`
|
||||
Obfs string `json:"obfs,omitempty"`
|
||||
Users []HysteriaUser `json:"users,omitempty"`
|
||||
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
|
||||
ReceiveWindowClient uint64 `json:"recv_window_client,omitempty"`
|
||||
MaxConnClient int `json:"max_conn_client,omitempty"`
|
||||
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
|
||||
InboundTLSOptionsContainer
|
||||
}
|
||||
|
||||
@@ -24,16 +26,16 @@ type HysteriaUser struct {
|
||||
type HysteriaOutboundOptions struct {
|
||||
DialerOptions
|
||||
ServerOptions
|
||||
Up string `json:"up,omitempty"`
|
||||
UpMbps int `json:"up_mbps,omitempty"`
|
||||
Down string `json:"down,omitempty"`
|
||||
DownMbps int `json:"down_mbps,omitempty"`
|
||||
Obfs string `json:"obfs,omitempty"`
|
||||
Auth []byte `json:"auth,omitempty"`
|
||||
AuthString string `json:"auth_str,omitempty"`
|
||||
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
|
||||
ReceiveWindow uint64 `json:"recv_window,omitempty"`
|
||||
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
Up *byteformats.NetworkBytesCompat `json:"up,omitempty"`
|
||||
UpMbps int `json:"up_mbps,omitempty"`
|
||||
Down *byteformats.NetworkBytesCompat `json:"down,omitempty"`
|
||||
DownMbps int `json:"down_mbps,omitempty"`
|
||||
Obfs string `json:"obfs,omitempty"`
|
||||
Auth []byte `json:"auth,omitempty"`
|
||||
AuthString string `json:"auth_str,omitempty"`
|
||||
ReceiveWindowConn uint64 `json:"recv_window_conn,omitempty"`
|
||||
ReceiveWindow uint64 `json:"recv_window,omitempty"`
|
||||
DisableMTUDiscovery bool `json:"disable_mtu_discovery,omitempty"`
|
||||
Network NetworkList `json:"network,omitempty"`
|
||||
OutboundTLSOptionsContainer
|
||||
}
|
||||
|
||||
13
option/mieru.go
Normal file
13
option/mieru.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package option
|
||||
|
||||
import "github.com/sagernet/sing/common/json/badoption"
|
||||
|
||||
type MieruOutboundOptions struct {
|
||||
DialerOptions
|
||||
ServerOptions
|
||||
ServerPortRanges badoption.Listable[string] `json:"server_ports,omitempty"`
|
||||
Transport string `json:"transport,omitempty"`
|
||||
UserName string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Multiplexing string `json:"multiplexing,omitempty"`
|
||||
}
|
||||
@@ -39,7 +39,7 @@ func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) err
|
||||
return E.New("missing outbound options registry in context")
|
||||
}
|
||||
switch h.Type {
|
||||
case C.TypeBlock, C.TypeDNS:
|
||||
case C.TypeDNS:
|
||||
deprecated.Report(ctx, deprecated.OptionSpecialOutbounds)
|
||||
}
|
||||
options, loaded := registry.CreateOptions(h.Type)
|
||||
|
||||
@@ -252,6 +252,14 @@ type _RejectActionOptions struct {
|
||||
|
||||
type RejectActionOptions _RejectActionOptions
|
||||
|
||||
func (r RejectActionOptions) MarshalJSON() ([]byte, error) {
|
||||
switch r.Method {
|
||||
case C.RuleActionRejectMethodDefault:
|
||||
r.Method = ""
|
||||
}
|
||||
return json.Marshal((_RejectActionOptions)(r))
|
||||
}
|
||||
|
||||
func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error {
|
||||
err := json.Unmarshal(bytes, (*_RejectActionOptions)(r))
|
||||
if err != nil {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user