mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-07 17:01:36 +03:00
Compare commits
96 Commits
v1.12.1-ex
...
renovate/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3190141613 | ||
|
|
0613431623 | ||
|
|
95aa5d05b7 | ||
|
|
0ccfb8ad96 | ||
|
|
86fe2aee98 | ||
|
|
1b2cce77f1 | ||
|
|
4b1a283bbf | ||
|
|
f91cbc0e42 | ||
|
|
7d1b88e1cd | ||
|
|
a4ad775fb7 | ||
|
|
88bfc6fd77 | ||
|
|
cd8501736d | ||
|
|
cc05b89b59 | ||
|
|
f8bfe56458 | ||
|
|
f9942b7c50 | ||
|
|
41915a6621 | ||
|
|
2d6894f28a | ||
|
|
93ceb33780 | ||
|
|
f643e35d00 | ||
|
|
cbcc4f4cfb | ||
|
|
feab546659 | ||
|
|
5a3be48d7b | ||
|
|
ef2f170364 | ||
|
|
5022d9a924 | ||
|
|
50d61c09cf | ||
|
|
bcc703310e | ||
|
|
a6c448e145 | ||
|
|
11105eb553 | ||
|
|
18e240e5b3 | ||
|
|
03bb284370 | ||
|
|
bc63aa2254 | ||
|
|
abbe2f7eaf | ||
|
|
33d576a018 | ||
|
|
38e34c7719 | ||
|
|
03d086c32e | ||
|
|
52de97edf1 | ||
|
|
0e68b1a29e | ||
|
|
6b43758d8e | ||
|
|
9957ab6e8b | ||
|
|
36aef228bf | ||
|
|
82ba228c39 | ||
|
|
7acc036c6e | ||
|
|
78439340cf | ||
|
|
7fd99aa515 | ||
|
|
5d30bfabae | ||
|
|
c29d56699f | ||
|
|
5c4518e6a4 | ||
|
|
58bf4c1e85 | ||
|
|
49d8408fdf | ||
|
|
01cbc4810a | ||
|
|
d9b5ca3e82 | ||
|
|
6c2b4dce4a | ||
|
|
daeb14bdd2 | ||
|
|
7d7aa4a328 | ||
|
|
f0f0b658f7 | ||
|
|
de2546f572 | ||
|
|
fcb8171ff5 | ||
|
|
a53c6a906f | ||
|
|
8637c536c8 | ||
|
|
db7a0c6092 | ||
|
|
87d287dd56 | ||
|
|
ee65df4dfc | ||
|
|
f9f5a0c724 | ||
|
|
96e81b6e76 | ||
|
|
883bea6628 | ||
|
|
62b98fcc4c | ||
|
|
c0d7198ac2 | ||
|
|
da1cdcb784 | ||
|
|
cd284a4368 | ||
|
|
33e9d719f8 | ||
|
|
f0a3ba5bc8 | ||
|
|
b96a6b595e | ||
|
|
be29171106 | ||
|
|
c66b32c859 | ||
|
|
984ebbb288 | ||
|
|
c5929cf948 | ||
|
|
be45dd2716 | ||
|
|
a20f525048 | ||
|
|
31ebe2f39f | ||
|
|
61b9da2f1a | ||
|
|
f5c738ea9d | ||
|
|
ebc783b580 | ||
|
|
a5b6dff812 | ||
|
|
cc4e90c861 | ||
|
|
2abce0959a | ||
|
|
a98cb0fd84 | ||
|
|
7b96406592 | ||
|
|
4fea063b6f | ||
|
|
1672d5a84a | ||
|
|
4a1b69c1d4 | ||
|
|
e2c1ff3a11 | ||
|
|
37dd8fa692 | ||
|
|
29b6a75f16 | ||
|
|
7575746f72 | ||
|
|
f35a58b7ee | ||
|
|
4aacdade73 |
@@ -8,7 +8,6 @@
|
|||||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||||
--no-deb-generate-changes
|
--no-deb-generate-changes
|
||||||
--config-files /etc/sing-box/config.json
|
--config-files /etc/sing-box/config.json
|
||||||
--after-install release/config/sing-box.postinst
|
|
||||||
|
|
||||||
release/config/config.json=/etc/sing-box/config.json
|
release/config/config.json=/etc/sing-box/config.json
|
||||||
|
|
||||||
|
|||||||
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.6
|
go-version: ^1.24.3
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -109,7 +109,7 @@ jobs:
|
|||||||
if: ${{ ! matrix.legacy_go }}
|
if: ${{ ! matrix.legacy_go }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.6
|
go-version: ^1.24.3
|
||||||
- name: Cache Legacy Go
|
- name: Cache Legacy Go
|
||||||
if: matrix.require_legacy_go
|
if: matrix.require_legacy_go
|
||||||
id: cache-legacy-go
|
id: cache-legacy-go
|
||||||
@@ -294,7 +294,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.6
|
go-version: ^1.24.3
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -374,7 +374,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.6
|
go-version: ^1.24.3
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -472,15 +472,15 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.6
|
go-version: ^1.24.3
|
||||||
- name: Setup Xcode stable
|
- name: Setup Xcode stable
|
||||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||||
run: |-
|
run: |-
|
||||||
sudo xcode-select -s /Applications/Xcode_16.4.app
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
- name: Setup Xcode beta
|
- name: Setup Xcode beta
|
||||||
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
sudo xcode-select -s /Applications/Xcode_16.4.app
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
@@ -615,7 +615,7 @@ jobs:
|
|||||||
path: 'dist'
|
path: 'dist'
|
||||||
upload:
|
upload:
|
||||||
name: Upload builds
|
name: Upload builds
|
||||||
if: "!failure() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')"
|
if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
|
|||||||
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@@ -28,9 +28,9 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.6
|
go-version: ^1.24.3
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v8
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
args: --timeout=30m
|
args: --timeout=30m
|
||||||
|
|||||||
6
.github/workflows/linux.yml
vendored
6
.github/workflows/linux.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.6
|
go-version: ^1.24.3
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24.6
|
go-version: ^1.24.3
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: matrix.os == 'android'
|
if: matrix.os == 'android'
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -80,7 +80,7 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api'
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
24
Makefile
24
Makefile
@@ -108,16 +108,6 @@ upload_ios_app_store:
|
|||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
export_ios_ipa:
|
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFI && \
|
|
||||||
cp build/SFI/sing-box.ipa dist/SFI.ipa
|
|
||||||
|
|
||||||
upload_ios_ipa:
|
|
||||||
cd dist && \
|
|
||||||
cp SFI.ipa "SFI-${VERSION}.ipa" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFI-${VERSION}.ipa"
|
|
||||||
|
|
||||||
release_ios: build_ios upload_ios_app_store
|
release_ios: build_ios upload_ios_app_store
|
||||||
|
|
||||||
build_macos:
|
build_macos:
|
||||||
@@ -185,16 +175,6 @@ upload_tvos_app_store:
|
|||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
export_tvos_ipa:
|
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFT && \
|
|
||||||
cp build/SFT/sing-box.ipa dist/SFT.ipa
|
|
||||||
|
|
||||||
upload_tvos_ipa:
|
|
||||||
cd dist && \
|
|
||||||
cp SFT.ipa "SFT-${VERSION}.ipa" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFT-${VERSION}.ipa"
|
|
||||||
|
|
||||||
release_tvos: build_tvos upload_tvos_app_store
|
release_tvos: build_tvos upload_tvos_app_store
|
||||||
|
|
||||||
update_apple_version:
|
update_apple_version:
|
||||||
@@ -245,8 +225,8 @@ lib:
|
|||||||
go run ./cmd/internal/build_libbox -target ios
|
go run ./cmd/internal/build_libbox -target ios
|
||||||
|
|
||||||
lib_install:
|
lib_install:
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.7
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.6
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.7
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.6
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
venv/bin/mkdocs serve
|
venv/bin/mkdocs serve
|
||||||
|
|||||||
20
README.md
20
README.md
@@ -1,20 +1,12 @@
|
|||||||
# sing-box-extended
|
# sing-box
|
||||||
|
|
||||||
Sing-box with extended features.
|
The universal proxy platform.
|
||||||
|
|
||||||
## Features
|
[](https://repology.org/project/sing-box/versions)
|
||||||
|
|
||||||
* Amnezia
|
## Documentation
|
||||||
* WARP
|
|
||||||
* Tunneling
|
|
||||||
* Mieru
|
|
||||||
* XHTTP
|
|
||||||
* SDNS (DNSCrypt)
|
|
||||||
* Unified delay
|
|
||||||
|
|
||||||
## Examples
|
https://sing-box.sagernet.org
|
||||||
|
|
||||||
https://github.com/shtorm-7/sing-box-extended/tree/extended/examples
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -36,4 +28,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
In addition, no derivative work may use the name or imply association
|
In addition, no derivative work may use the name or imply association
|
||||||
with this application without prior consent.
|
with this application without prior consent.
|
||||||
```
|
```
|
||||||
@@ -35,6 +35,7 @@ func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Ma
|
|||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
if m.started && m.stage >= stage {
|
if m.started && m.stage >= stage {
|
||||||
panic("already started")
|
panic("already started")
|
||||||
}
|
}
|
||||||
@@ -42,12 +43,9 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
|||||||
m.stage = stage
|
m.stage = stage
|
||||||
if stage == adapter.StartStateStart {
|
if stage == adapter.StartStateStart {
|
||||||
// started with outbound manager
|
// started with outbound manager
|
||||||
m.access.Unlock()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
endpoints := m.endpoints
|
for _, endpoint := range m.endpoints {
|
||||||
m.access.Unlock()
|
|
||||||
for _, endpoint := range endpoints {
|
|
||||||
err := adapter.LegacyStart(endpoint, stage)
|
err := adapter.LegacyStart(endpoint, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ type CacheFile interface {
|
|||||||
StoreRDRC() bool
|
StoreRDRC() bool
|
||||||
RDRCStore
|
RDRCStore
|
||||||
|
|
||||||
StoreWARPConfig() bool
|
|
||||||
|
|
||||||
LoadMode() string
|
LoadMode() string
|
||||||
StoreMode(mode string) error
|
StoreMode(mode string) error
|
||||||
LoadSelected(group string) string
|
LoadSelected(group string) string
|
||||||
@@ -54,8 +52,6 @@ type CacheFile interface {
|
|||||||
StoreGroupExpand(group string, expand bool) error
|
StoreGroupExpand(group string, expand bool) error
|
||||||
LoadRuleSet(tag string) *SavedBinary
|
LoadRuleSet(tag string) *SavedBinary
|
||||||
SaveRuleSet(tag string, set *SavedBinary) error
|
SaveRuleSet(tag string, set *SavedBinary) error
|
||||||
LoadWARPConfig(tag string) *SavedBinary
|
|
||||||
SaveWARPConfig(tag string, set *SavedBinary) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SavedBinary struct {
|
type SavedBinary struct {
|
||||||
|
|||||||
@@ -42,24 +42,22 @@ type InboundManager interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
Inbound string
|
Inbound string
|
||||||
InboundType string
|
InboundType string
|
||||||
IPVersion uint8
|
IPVersion uint8
|
||||||
Network string
|
Network string
|
||||||
Source M.Socksaddr
|
Source M.Socksaddr
|
||||||
Destination M.Socksaddr
|
Destination M.Socksaddr
|
||||||
TunnelSource string
|
User string
|
||||||
TunnelDestination string
|
Outbound string
|
||||||
User string
|
|
||||||
Outbound string
|
|
||||||
|
|
||||||
// sniffer
|
// sniffer
|
||||||
|
|
||||||
Protocol string
|
Protocol string
|
||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
SniffError error
|
PacketSniffError error
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
|
|||||||
6
box.go
6
box.go
@@ -17,7 +17,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/dns/transport/local"
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
@@ -140,9 +139,6 @@ func New(options Options) (*Box, error) {
|
|||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
if experimentalOptions.UnifiedDelay != nil && experimentalOptions.UnifiedDelay.Enabled {
|
|
||||||
ctx = urltest.ContextWithIsUnifiedDelay(ctx)
|
|
||||||
}
|
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
var defaultLogWriter io.Writer
|
var defaultLogWriter io.Writer
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
@@ -502,7 +498,7 @@ func (s *Box) Close() error {
|
|||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
err := common.Close(
|
err := common.Close(
|
||||||
s.service, s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||||
)
|
)
|
||||||
for _, lifecycleService := range s.internalService {
|
for _, lifecycleService := range s.internalService {
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
|
|||||||
Submodule clients/android updated: 6db8e06e8d...cec05bf693
Submodule clients/apple updated: c5734677bd...ae5818ee5a
@@ -105,7 +105,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tag := tagVersion.VersionString()
|
tag := tagVersion.VersionString()
|
||||||
client := createClient(20 * time.Minute)
|
client := createClient(10 * time.Minute)
|
||||||
|
|
||||||
log.Info(tag, " list build IDs")
|
log.Info(tag, " list build IDs")
|
||||||
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
||||||
@@ -145,7 +145,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
build := builds.Data[0]
|
build := builds.Data[0]
|
||||||
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute {
|
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
|
||||||
log.Info(string(platform), " ", tag, " waiting for process")
|
log.Info(string(platform), " ", tag, " waiting for process")
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(15 * time.Second)
|
||||||
continue
|
continue
|
||||||
@@ -177,7 +177,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
log.Info(string(platform), " ", tag, " publish")
|
log.Info(string(platform), " ", tag, " publish")
|
||||||
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
|
||||||
if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {
|
if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
|
||||||
log.Info("waiting for process")
|
log.Info("waiting for process")
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(15 * time.Second)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -16,17 +16,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
debugEnabled bool
|
debugEnabled bool
|
||||||
target string
|
target string
|
||||||
platform string
|
platform string
|
||||||
withTailscale bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
||||||
flag.StringVar(&target, "target", "android", "target platform")
|
flag.StringVar(&target, "target", "android", "target platform")
|
||||||
flag.StringVar(&platform, "platform", "", "specify platform")
|
flag.StringVar(&platform, "platform", "", "specify platform")
|
||||||
flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -46,9 +44,8 @@ var (
|
|||||||
sharedFlags []string
|
sharedFlags []string
|
||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
sharedTags []string
|
||||||
darwinTags []string
|
iosTags []string
|
||||||
memcTags []string
|
memcTags []string
|
||||||
notMemcTags []string
|
|
||||||
debugTags []string
|
debugTags []string
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -63,9 +60,8 @@ func init() {
|
|||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
|
||||||
darwinTags = append(darwinTags, "with_dhcp")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory")
|
||||||
memcTags = append(memcTags, "with_tailscale")
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
notMemcTags = append(notMemcTags, "with_low_memory")
|
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,8 +108,6 @@ func buildAndroid() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, "-ldflags", "-checklinkname=0")
|
|
||||||
|
|
||||||
tags := append(sharedTags, memcTags...)
|
tags := append(sharedTags, memcTags...)
|
||||||
if debugEnabled {
|
if debugEnabled {
|
||||||
tags = append(tags, debugTags...)
|
tags = append(tags, debugTags...)
|
||||||
@@ -157,10 +151,7 @@ func buildApple() {
|
|||||||
"-v",
|
"-v",
|
||||||
"-target", bindTarget,
|
"-target", bindTarget,
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
"-tags-not-macos=with_low_memory",
|
"-tags-macos=" + strings.Join(memcTags, ","),
|
||||||
}
|
|
||||||
if !withTailscale {
|
|
||||||
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
@@ -169,10 +160,7 @@ func buildApple() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, darwinTags...)
|
tags := append(sharedTags, iosTags...)
|
||||||
if withTailscale {
|
|
||||||
tags = append(tags, memcTags...)
|
|
||||||
}
|
|
||||||
if debugEnabled {
|
if debugEnabled {
|
||||||
tags = append(tags, debugTags...)
|
tags = append(tags, debugTags...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,286 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/include"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
|
||||||
"github.com/sagernet/sing/common/shell"
|
|
||||||
)
|
|
||||||
|
|
||||||
var iperf3Path string
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
err := main0()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main0() error {
|
|
||||||
err := shell.Exec("sudo", "ls").Run()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
results, err := runTests()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
encoder := json.NewEncoder(os.Stdout)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
return encoder.Encode(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runTests() ([]TestResult, error) {
|
|
||||||
boxPaths := []string{
|
|
||||||
os.ExpandEnv("$HOME/Downloads/sing-box-1.11.15-darwin-arm64/sing-box"),
|
|
||||||
//"/Users/sekai/Downloads/sing-box-1.11.15-linux-arm64/sing-box",
|
|
||||||
"./sing-box",
|
|
||||||
}
|
|
||||||
stacks := []string{
|
|
||||||
"gvisor",
|
|
||||||
"system",
|
|
||||||
}
|
|
||||||
mtus := []int{
|
|
||||||
1500,
|
|
||||||
4064,
|
|
||||||
// 16384,
|
|
||||||
// 32768,
|
|
||||||
// 49152,
|
|
||||||
65535,
|
|
||||||
}
|
|
||||||
flagList := [][]string{
|
|
||||||
{},
|
|
||||||
}
|
|
||||||
var results []TestResult
|
|
||||||
for _, boxPath := range boxPaths {
|
|
||||||
for _, stack := range stacks {
|
|
||||||
for _, mtu := range mtus {
|
|
||||||
if strings.HasPrefix(boxPath, ".") {
|
|
||||||
for _, flags := range flagList {
|
|
||||||
result, err := testOnce(boxPath, stack, mtu, false, flags)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, *result)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result, err := testOnce(boxPath, stack, mtu, false, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
results = append(results, *result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestResult struct {
|
|
||||||
BoxPath string `json:"box_path"`
|
|
||||||
Stack string `json:"stack"`
|
|
||||||
MTU int `json:"mtu"`
|
|
||||||
Flags []string `json:"flags"`
|
|
||||||
MultiThread bool `json:"multi_thread"`
|
|
||||||
UploadSpeed string `json:"upload_speed"`
|
|
||||||
DownloadSpeed string `json:"download_speed"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func testOnce(boxPath string, stackName string, mtu int, multiThread bool, flags []string) (result *TestResult, err error) {
|
|
||||||
testAddress := netip.MustParseAddr("1.1.1.1")
|
|
||||||
testConfig := option.Options{
|
|
||||||
Inbounds: []option.Inbound{
|
|
||||||
{
|
|
||||||
Type: C.TypeTun,
|
|
||||||
Options: &option.TunInboundOptions{
|
|
||||||
Address: []netip.Prefix{netip.MustParsePrefix("172.18.0.1/30")},
|
|
||||||
AutoRoute: true,
|
|
||||||
MTU: uint32(mtu),
|
|
||||||
Stack: stackName,
|
|
||||||
RouteAddress: []netip.Prefix{netip.PrefixFrom(testAddress, testAddress.BitLen())},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Route: &option.RouteOptions{
|
|
||||||
Rules: []option.Rule{
|
|
||||||
{
|
|
||||||
Type: C.RuleTypeDefault,
|
|
||||||
DefaultOptions: option.DefaultRule{
|
|
||||||
RawDefaultRule: option.RawDefaultRule{
|
|
||||||
IPCIDR: []string{testAddress.String()},
|
|
||||||
},
|
|
||||||
RuleAction: option.RuleAction{
|
|
||||||
Action: C.RuleActionTypeRouteOptions,
|
|
||||||
RouteOptionsOptions: option.RouteOptionsActionOptions{
|
|
||||||
OverrideAddress: "127.0.0.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
AutoDetectInterface: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
ctx := include.Context(context.Background())
|
|
||||||
tempConfig, err := os.CreateTemp("", "tun-bench-*.json")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.Remove(tempConfig.Name())
|
|
||||||
encoder := json.NewEncoderContext(ctx, tempConfig)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
err = encoder.Encode(testConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "encode test config")
|
|
||||||
}
|
|
||||||
tempConfig.Close()
|
|
||||||
var sudoArgs []string
|
|
||||||
if len(flags) > 0 {
|
|
||||||
sudoArgs = append(sudoArgs, "env")
|
|
||||||
for _, flag := range flags {
|
|
||||||
sudoArgs = append(sudoArgs, flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sudoArgs = append(sudoArgs, boxPath, "run", "-c", tempConfig.Name())
|
|
||||||
boxProcess := shell.Exec("sudo", sudoArgs...)
|
|
||||||
boxProcess.Stdout = &stderrWriter{}
|
|
||||||
boxProcess.Stderr = io.Discard
|
|
||||||
err = boxProcess.Start()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if C.IsDarwin {
|
|
||||||
iperf3Path, err = exec.LookPath("iperf3-darwin")
|
|
||||||
} else {
|
|
||||||
iperf3Path, err = exec.LookPath("iperf3")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
serverProcess := shell.Exec(iperf3Path, "-s")
|
|
||||||
serverProcess.Stdout = io.Discard
|
|
||||||
serverProcess.Stderr = io.Discard
|
|
||||||
err = serverProcess.Start()
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "start iperf3 server")
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
|
|
||||||
args := []string{"-c", testAddress.String()}
|
|
||||||
if multiThread {
|
|
||||||
args = append(args, "-P", "10")
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadProcess := shell.Exec(iperf3Path, args...)
|
|
||||||
output, err := uploadProcess.Read()
|
|
||||||
if err != nil {
|
|
||||||
boxProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
serverProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
println(output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadResult := common.SubstringBeforeLast(output, "iperf Done.")
|
|
||||||
uploadResult = common.SubstringBeforeLast(uploadResult, "sender")
|
|
||||||
uploadResult = common.SubstringBeforeLast(uploadResult, "bits/sec")
|
|
||||||
uploadResult = common.SubstringAfterLast(uploadResult, "Bytes")
|
|
||||||
uploadResult = strings.ReplaceAll(uploadResult, " ", "")
|
|
||||||
|
|
||||||
result = &TestResult{
|
|
||||||
BoxPath: boxPath,
|
|
||||||
Stack: stackName,
|
|
||||||
MTU: mtu,
|
|
||||||
Flags: flags,
|
|
||||||
MultiThread: multiThread,
|
|
||||||
UploadSpeed: uploadResult,
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadProcess := shell.Exec(iperf3Path, append(args, "-R")...)
|
|
||||||
output, err = downloadProcess.Read()
|
|
||||||
if err != nil {
|
|
||||||
boxProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
serverProcess.Process.Signal(syscall.SIGKILL)
|
|
||||||
println(output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadResult := common.SubstringBeforeLast(output, "iperf Done.")
|
|
||||||
downloadResult = common.SubstringBeforeLast(downloadResult, "receiver")
|
|
||||||
downloadResult = common.SubstringBeforeLast(downloadResult, "bits/sec")
|
|
||||||
downloadResult = common.SubstringAfterLast(downloadResult, "Bytes")
|
|
||||||
downloadResult = strings.ReplaceAll(downloadResult, " ", "")
|
|
||||||
|
|
||||||
result.DownloadSpeed = downloadResult
|
|
||||||
|
|
||||||
printArgs := []any{boxPath, stackName, mtu, "upload", uploadResult, "download", downloadResult}
|
|
||||||
if len(flags) > 0 {
|
|
||||||
printArgs = append(printArgs, "flags", strings.Join(flags, " "))
|
|
||||||
}
|
|
||||||
if multiThread {
|
|
||||||
printArgs = append(printArgs, "(-P 10)")
|
|
||||||
}
|
|
||||||
fmt.Println(printArgs...)
|
|
||||||
err = boxProcess.Process.Signal(syscall.SIGTERM)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = serverProcess.Process.Signal(syscall.SIGTERM)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
boxDone := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
boxProcess.Cmd.Wait()
|
|
||||||
close(boxDone)
|
|
||||||
}()
|
|
||||||
|
|
||||||
serverDone := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
serverProcess.Process.Wait()
|
|
||||||
close(serverDone)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-boxDone:
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
boxProcess.Process.Kill()
|
|
||||||
case <-time.After(4 * time.Second):
|
|
||||||
println("box process did not close!")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-serverDone:
|
|
||||||
case <-time.After(2 * time.Second):
|
|
||||||
serverProcess.Process.Kill()
|
|
||||||
case <-time.After(4 * time.Second):
|
|
||||||
println("server process did not close!")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type stderrWriter struct{}
|
|
||||||
|
|
||||||
func (w *stderrWriter) Write(p []byte) (n int, err error) {
|
|
||||||
return os.Stderr.Write(p)
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/convertor/adguard"
|
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -54,7 +54,7 @@ func convertRuleSet(sourcePath string) error {
|
|||||||
var rules []option.HeadlessRule
|
var rules []option.HeadlessRule
|
||||||
switch flagRuleSetConvertType {
|
switch flagRuleSetConvertType {
|
||||||
case "adguard":
|
case "adguard":
|
||||||
rules, err = adguard.ToOptions(reader, log.StdLogger())
|
rules, err = adguard.Convert(reader)
|
||||||
case "":
|
case "":
|
||||||
return E.New("source type is required")
|
return E.New("source type is required")
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -53,11 +50,6 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if hasRule(ruleSet.Options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return len(rule.AdGuardDomain) > 0
|
|
||||||
}) {
|
|
||||||
return E.New("unable to decompile binary AdGuard rules to rule-set.")
|
|
||||||
}
|
|
||||||
var outputPath string
|
var outputPath string
|
||||||
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||||
if strings.HasSuffix(sourcePath, ".srs") {
|
if strings.HasSuffix(sourcePath, ".srs") {
|
||||||
@@ -83,19 +75,3 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
if cond(rule.DefaultOptions) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
if hasRule(rule.LogicalOptions.Rules, cond) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package adguard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -10,10 +9,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ type agdguardRuleLine struct {
|
|||||||
isImportant bool
|
isImportant bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, error) {
|
func Convert(reader io.Reader) ([]option.HeadlessRule, error) {
|
||||||
scanner := bufio.NewScanner(reader)
|
scanner := bufio.NewScanner(reader)
|
||||||
var (
|
var (
|
||||||
ruleLines []agdguardRuleLine
|
ruleLines []agdguardRuleLine
|
||||||
@@ -37,10 +36,7 @@ func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, e
|
|||||||
parseLine:
|
parseLine:
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
ruleLine := scanner.Text()
|
ruleLine := scanner.Text()
|
||||||
if ruleLine == "" {
|
if ruleLine == "" || ruleLine[0] == '!' || ruleLine[0] == '#' {
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(ruleLine, "!") || strings.HasPrefix(ruleLine, "#") {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
originRuleLine := ruleLine
|
originRuleLine := ruleLine
|
||||||
@@ -96,7 +92,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if !ignored {
|
if !ignored {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", originRuleLine)
|
log.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", ruleLine)
|
||||||
continue parseLine
|
continue parseLine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,35 +120,27 @@ parseLine:
|
|||||||
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
||||||
if ignoreIPCIDRRegexp(ruleLine) {
|
if ignoreIPCIDRRegexp(ruleLine) {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with IPCIDR regexp: ", originRuleLine)
|
log.Debug("ignored unsupported rule with IPCIDR regexp: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isRegexp = true
|
isRegexp = true
|
||||||
} else {
|
} else {
|
||||||
if strings.Contains(ruleLine, "://") {
|
if strings.Contains(ruleLine, "://") {
|
||||||
ruleLine = common.SubstringAfter(ruleLine, "://")
|
ruleLine = common.SubstringAfter(ruleLine, "://")
|
||||||
isSuffix = true
|
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "/") {
|
if strings.Contains(ruleLine, "/") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with path: ", originRuleLine)
|
log.Debug("ignored unsupported rule with path: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "?") || strings.Contains(ruleLine, "&") {
|
if strings.Contains(ruleLine, "##") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with query: ", originRuleLine)
|
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "[") || strings.Contains(ruleLine, "]") ||
|
if strings.Contains(ruleLine, "#$#") {
|
||||||
strings.Contains(ruleLine, "(") || strings.Contains(ruleLine, ")") ||
|
|
||||||
strings.Contains(ruleLine, "!") || strings.Contains(ruleLine, "#") {
|
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported cosmetic filter: ", originRuleLine)
|
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(ruleLine, "~") {
|
|
||||||
ignoredLines++
|
|
||||||
logger.Debug("ignored unsupported rule modifier: ", originRuleLine)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var domainCheck string
|
var domainCheck string
|
||||||
@@ -163,7 +151,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if ruleLine == "" {
|
if ruleLine == "" {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
log.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
||||||
@@ -171,13 +159,13 @@ parseLine:
|
|||||||
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
||||||
if ipErr == nil {
|
if ipErr == nil {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with IPCIDR: ", originRuleLine)
|
log.Debug("ignored unsupported rule with IPCIDR: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
||||||
logger.Debug("ignored unsupported rule with port: ", originRuleLine)
|
log.Debug("ignored unsupported rule with port: ", ruleLine)
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("ignored unsupported rule with invalid domain: ", originRuleLine)
|
log.Debug("ignored unsupported rule with invalid domain: ", ruleLine)
|
||||||
}
|
}
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
continue
|
continue
|
||||||
@@ -295,112 +283,10 @@ parseLine:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ignoredLines > 0 {
|
log.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
||||||
logger.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
|
||||||
}
|
|
||||||
return []option.HeadlessRule{currentRule}, nil
|
return []option.HeadlessRule{currentRule}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrInvalid = E.New("invalid binary AdGuard rule-set")
|
|
||||||
|
|
||||||
func FromOptions(rules []option.HeadlessRule) ([]byte, error) {
|
|
||||||
if len(rules) != 1 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
rule := rules[0]
|
|
||||||
var (
|
|
||||||
importantDomain []string
|
|
||||||
importantDomainRegex []string
|
|
||||||
importantExcludeDomain []string
|
|
||||||
importantExcludeDomainRegex []string
|
|
||||||
domain []string
|
|
||||||
domainRegex []string
|
|
||||||
excludeDomain []string
|
|
||||||
excludeDomainRegex []string
|
|
||||||
)
|
|
||||||
parse:
|
|
||||||
for {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
if !(len(rule.LogicalOptions.Rules) == 2 && rule.LogicalOptions.Rules[0].Type == C.RuleTypeDefault) {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
if rule.LogicalOptions.Mode == C.LogicalTypeAnd && rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
|
||||||
if len(importantExcludeDomain) == 0 && len(importantExcludeDomainRegex) == 0 {
|
|
||||||
importantExcludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
importantExcludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(importantExcludeDomain)+len(importantExcludeDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
excludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
excludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(excludeDomain)+len(excludeDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if rule.LogicalOptions.Mode == C.LogicalTypeOr && !rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
|
||||||
importantDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
importantDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(importantDomain)+len(importantDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
rule = rule.LogicalOptions.Rules[1]
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
domain = rule.DefaultOptions.AdGuardDomain
|
|
||||||
domainRegex = rule.DefaultOptions.DomainRegex
|
|
||||||
if len(domain)+len(domainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
break parse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var output bytes.Buffer
|
|
||||||
for _, ruleLine := range importantDomain {
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantDomainRegex {
|
|
||||||
output.WriteString("/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/$important\n")
|
|
||||||
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantExcludeDomain {
|
|
||||||
output.WriteString("@@")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantExcludeDomainRegex {
|
|
||||||
output.WriteString("@@/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range domain {
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range domainRegex {
|
|
||||||
output.WriteString("/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range excludeDomain {
|
|
||||||
output.WriteString("@@")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range excludeDomainRegex {
|
|
||||||
output.WriteString("@@/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/\n")
|
|
||||||
}
|
|
||||||
return output.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
||||||
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
||||||
ruleLine = ruleLine[12:]
|
ruleLine = ruleLine[12:]
|
||||||
@@ -408,9 +294,11 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
|
|||||||
ruleLine = ruleLine[13:]
|
ruleLine = ruleLine[13:]
|
||||||
} else if strings.HasPrefix(ruleLine, "^") {
|
} else if strings.HasPrefix(ruleLine, "^") {
|
||||||
ruleLine = ruleLine[1:]
|
ruleLine = ruleLine[1:]
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)) == nil ||
|
_, parseErr := strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)
|
||||||
common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "."), 10, 8)) == nil
|
return parseErr == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
||||||
@@ -454,5 +342,5 @@ func parseADGuardIPCIDRLine(ruleLine string) (netip.Prefix, error) {
|
|||||||
for len(ruleParts) < 4 {
|
for len(ruleParts) < 4 {
|
||||||
ruleParts = append(ruleParts, 0)
|
ruleParts = append(ruleParts, 0)
|
||||||
}
|
}
|
||||||
return netip.PrefixFrom(netip.AddrFrom4([4]byte(ruleParts)), bitLen), nil
|
return netip.PrefixFrom(netip.AddrFrom4(*(*[4]byte)(ruleParts)), bitLen), nil
|
||||||
}
|
}
|
||||||
@@ -7,15 +7,13 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConverter(t *testing.T) {
|
func TestConverter(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ruleString := `||sagernet.org^$important
|
rules, err := Convert(strings.NewReader(`
|
||||||
@@|sing-box.sagernet.org^$important
|
|
||||||
||example.org^
|
||example.org^
|
||||||
|example.com^
|
|example.com^
|
||||||
example.net^
|
example.net^
|
||||||
@@ -23,9 +21,10 @@ example.net^
|
|||||||
||example.edu.tw^
|
||example.edu.tw^
|
||||||
|example.gov
|
|example.gov
|
||||||
example.arpa
|
example.arpa
|
||||||
@@|sagernet.example.org^
|
@@|sagernet.example.org|
|
||||||
`
|
||sagernet.org^$important
|
||||||
rules, err := ToOptions(strings.NewReader(ruleString), logger.NOP())
|
@@|sing-box.sagernet.org^$important
|
||||||
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -76,18 +75,15 @@ example.arpa
|
|||||||
Domain: domain,
|
Domain: domain,
|
||||||
}), domain)
|
}), domain)
|
||||||
}
|
}
|
||||||
ruleFromOptions, err := FromOptions(rules)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, ruleString, string(ruleFromOptions))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHosts(t *testing.T) {
|
func TestHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := ToOptions(strings.NewReader(`
|
rules, err := Convert(strings.NewReader(`
|
||||||
127.0.0.1 localhost
|
127.0.0.1 localhost
|
||||||
::1 localhost #[IPv6]
|
::1 localhost #[IPv6]
|
||||||
0.0.0.0 google.com
|
0.0.0.0 google.com
|
||||||
`), logger.NOP())
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -114,10 +110,10 @@ func TestHosts(t *testing.T) {
|
|||||||
|
|
||||||
func TestSimpleHosts(t *testing.T) {
|
func TestSimpleHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := ToOptions(strings.NewReader(`
|
rules, err := Convert(strings.NewReader(`
|
||||||
example.com
|
example.com
|
||||||
www.example.org
|
www.example.org
|
||||||
`), logger.NOP())
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
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"`
|
|
||||||
}
|
|
||||||
@@ -97,6 +97,10 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
} else if networkManager.AutoDetectInterface() {
|
} else if networkManager.AutoDetectInterface() {
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||||
|
if networkStrategy == nil {
|
||||||
|
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||||
|
defaultNetworkStrategy = true
|
||||||
|
}
|
||||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||||
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
||||||
@@ -108,10 +112,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||||
}
|
}
|
||||||
if networkStrategy == nil {
|
|
||||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
|
||||||
defaultNetworkStrategy = true
|
|
||||||
}
|
|
||||||
bindFunc := networkManager.ProtectFunc()
|
bindFunc := networkManager.ProtectFunc()
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
dnsQueryOptions.Transport = dnsTransport.Default()
|
dnsQueryOptions.Transport = dnsTransport.Default()
|
||||||
} else if options.NewDialer {
|
} else if options.NewDialer {
|
||||||
return nil, E.New("missing domain resolver for domain server address")
|
return nil, E.New("missing domain resolver for domain server address")
|
||||||
} else {
|
} else if !options.DirectOutbound {
|
||||||
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
@@ -24,9 +26,7 @@ type slowOpenConn struct {
|
|||||||
destination M.Socksaddr
|
destination M.Socksaddr
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
create chan struct{}
|
create chan struct{}
|
||||||
done chan struct{}
|
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
closeOnce sync.Once
|
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +45,6 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des
|
|||||||
network: network,
|
network: network,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
create: make(chan struct{}),
|
create: make(chan struct{}),
|
||||||
done: make(chan struct{}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,8 +55,8 @@ func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
|||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
case <-c.done:
|
case <-c.ctx.Done():
|
||||||
return 0, os.ErrClosed
|
return 0, c.ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c.conn.Read(b)
|
return c.conn.Read(b)
|
||||||
@@ -75,15 +74,12 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
return c.conn.Write(b)
|
return c.conn.Write(b)
|
||||||
case <-c.done:
|
|
||||||
return 0, os.ErrClosed
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.err = err
|
c.conn = nil
|
||||||
} else {
|
c.err = E.Cause(err, "dial tcp fast open")
|
||||||
c.conn = conn
|
|
||||||
}
|
}
|
||||||
n = len(b)
|
n = len(b)
|
||||||
close(c.create)
|
close(c.create)
|
||||||
@@ -91,13 +87,7 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Close() error {
|
func (c *slowOpenConn) Close() error {
|
||||||
c.closeOnce.Do(func() {
|
return common.Close(c.conn)
|
||||||
close(c.done)
|
|
||||||
if c.conn != nil {
|
|
||||||
c.conn.Close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) LocalAddr() net.Addr {
|
func (c *slowOpenConn) LocalAddr() net.Addr {
|
||||||
@@ -162,8 +152,8 @@ func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
case <-c.done:
|
case <-c.ctx.Done():
|
||||||
return 0, c.err
|
return 0, c.ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bufio.Copy(w, c.conn)
|
return bufio.Copy(w, c.conn)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package interrupt
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,32 +73,3 @@ func (c *PacketConn) WriterReplaceable() bool {
|
|||||||
func (c *PacketConn) Upstream() any {
|
func (c *PacketConn) Upstream() any {
|
||||||
return c.PacketConn
|
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,7 +5,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,13 +36,6 @@ func (g *Group) NewPacketConn(conn net.PacketConn, isExternal bool) net.PacketCo
|
|||||||
return &PacketConn{PacketConn: conn, group: g, element: item}
|
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) {
|
func (g *Group) Interrupt(interruptExternalConnections bool) {
|
||||||
g.access.Lock()
|
g.access.Lock()
|
||||||
defer g.access.Unlock()
|
defer g.access.Unlock()
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
|||||||
if l.tproxy {
|
if l.tproxy {
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||||
return control.Raw(conn, func(fd uintptr) error {
|
return control.Raw(conn, func(fd uintptr) error {
|
||||||
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), false)
|
return redir.TProxy(fd, M.ParseSocksaddr(address).IsIPv6(), false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
|||||||
if l.tproxy {
|
if l.tproxy {
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||||
return control.Raw(conn, func(fd uintptr) error {
|
return control.Raw(conn, func(fd uintptr) error {
|
||||||
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), true)
|
return redir.TProxy(fd, M.ParseSocksaddr(address).IsIPv6(), true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -164,8 +164,9 @@ func (l *Listener) loopUDPOut() {
|
|||||||
if l.shutdown.Load() && E.IsClosed(err) {
|
if l.shutdown.Load() && E.IsClosed(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
l.udpConn.Close()
|
||||||
l.logger.Error("udp listener write back: ", destination, ": ", err)
|
l.logger.Error("udp listener write back: ", destination, ": ", err)
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case <-l.packetOutboundClosed:
|
case <-l.packetOutboundClosed:
|
||||||
|
|||||||
@@ -76,8 +76,6 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
|||||||
// rup8(sizeof(xtcpcb_n))
|
// rup8(sizeof(xtcpcb_n))
|
||||||
itemSize += 208
|
itemSize += 208
|
||||||
}
|
}
|
||||||
|
|
||||||
var fallbackUDPProcess string
|
|
||||||
// skip the first xinpgen(24 bytes) block
|
// skip the first xinpgen(24 bytes) block
|
||||||
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
||||||
// offset of xinpcb_n and xsocket_n
|
// offset of xinpcb_n and xsocket_n
|
||||||
@@ -92,34 +90,24 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
|||||||
flag := buf[inp+44]
|
flag := buf[inp+44]
|
||||||
|
|
||||||
var srcIP netip.Addr
|
var srcIP netip.Addr
|
||||||
srcIsIPv4 := false
|
|
||||||
switch {
|
switch {
|
||||||
case flag&0x1 > 0 && isIPv4:
|
case flag&0x1 > 0 && isIPv4:
|
||||||
// ipv4
|
// ipv4
|
||||||
srcIP = netip.AddrFrom4([4]byte(buf[inp+76 : inp+80]))
|
srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80]))
|
||||||
srcIsIPv4 = true
|
|
||||||
case flag&0x2 > 0 && !isIPv4:
|
case flag&0x2 > 0 && !isIPv4:
|
||||||
// ipv6
|
// ipv6
|
||||||
srcIP = netip.AddrFrom16([16]byte(buf[inp+64 : inp+80]))
|
srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80]))
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip == srcIP {
|
if ip != srcIP {
|
||||||
// xsocket_n.so_last_pid
|
continue
|
||||||
pid := readNativeUint32(buf[so+68 : so+72])
|
|
||||||
return getExecPathFromPID(pid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// udp packet connection may be not equal with srcIP
|
// xsocket_n.so_last_pid
|
||||||
if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
|
pid := readNativeUint32(buf[so+68 : so+72])
|
||||||
pid := readNativeUint32(buf[so+68 : so+72])
|
return getExecPathFromPID(pid)
|
||||||
fallbackUDPProcess, _ = getExecPathFromPID(pid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {
|
|
||||||
return fallbackUDPProcess, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", ErrNotFound
|
return "", ErrNotFound
|
||||||
|
|||||||
@@ -215,15 +215,16 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
case ruleItemWIFIBSSID:
|
case ruleItemWIFIBSSID:
|
||||||
rule.WIFIBSSID, err = readRuleItemString(reader)
|
rule.WIFIBSSID, err = readRuleItemString(reader)
|
||||||
case ruleItemAdGuardDomain:
|
case ruleItemAdGuardDomain:
|
||||||
|
if recover {
|
||||||
|
err = E.New("unable to decompile binary AdGuard rules to rule-set")
|
||||||
|
return
|
||||||
|
}
|
||||||
var matcher *domain.AdGuardMatcher
|
var matcher *domain.AdGuardMatcher
|
||||||
matcher, err = domain.ReadAdGuardMatcher(reader)
|
matcher, err = domain.ReadAdGuardMatcher(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rule.AdGuardDomainMatcher = matcher
|
rule.AdGuardDomainMatcher = matcher
|
||||||
if recover {
|
|
||||||
rule.AdGuardDomain = matcher.Dump()
|
|
||||||
}
|
|
||||||
case ruleItemNetworkType:
|
case ruleItemNetworkType:
|
||||||
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
||||||
case ruleItemNetworkIsExpensive:
|
case ruleItemNetworkIsExpensive:
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/libdns/alidns"
|
"github.com/libdns/alidns"
|
||||||
@@ -37,38 +37,7 @@ func (w *acmeWrapper) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type acmeLogWriter struct {
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *acmeLogWriter) Write(p []byte) (n int, err error) {
|
|
||||||
logLine := strings.ReplaceAll(string(p), " ", ": ")
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(logLine, "error: "):
|
|
||||||
w.logger.Error(logLine[7:])
|
|
||||||
case strings.HasPrefix(logLine, "warn: "):
|
|
||||||
w.logger.Warn(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "info: "):
|
|
||||||
w.logger.Info(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "debug: "):
|
|
||||||
w.logger.Debug(logLine[7:])
|
|
||||||
default:
|
|
||||||
w.logger.Debug(logLine)
|
|
||||||
}
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *acmeLogWriter) Sync() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encoderConfig() zapcore.EncoderConfig {
|
|
||||||
config := zap.NewProductionEncoderConfig()
|
|
||||||
config.TimeKey = zapcore.OmitKey
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
|
||||||
var acmeServer string
|
var acmeServer string
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
case "", "letsencrypt":
|
case "", "letsencrypt":
|
||||||
@@ -89,15 +58,14 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
} else {
|
} else {
|
||||||
storage = certmagic.Default.Storage
|
storage = certmagic.Default.Storage
|
||||||
}
|
}
|
||||||
zapLogger := zap.New(zapcore.NewCore(
|
|
||||||
zapcore.NewConsoleEncoder(encoderConfig()),
|
|
||||||
&acmeLogWriter{logger: logger},
|
|
||||||
zap.DebugLevel,
|
|
||||||
))
|
|
||||||
config := &certmagic.Config{
|
config := &certmagic.Config{
|
||||||
DefaultServerName: options.DefaultServerName,
|
DefaultServerName: options.DefaultServerName,
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
Logger: zapLogger,
|
Logger: zap.New(zapcore.NewCore(
|
||||||
|
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
|
||||||
|
os.Stderr,
|
||||||
|
zap.InfoLevel,
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
acmeConfig := certmagic.ACMEIssuer{
|
acmeConfig := certmagic.ACMEIssuer{
|
||||||
CA: acmeServer,
|
CA: acmeServer,
|
||||||
@@ -107,7 +75,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
||||||
AltHTTPPort: int(options.AlternativeHTTPPort),
|
AltHTTPPort: int(options.AlternativeHTTPPort),
|
||||||
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
||||||
Logger: zapLogger,
|
Logger: config.Logger,
|
||||||
}
|
}
|
||||||
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
|
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
|
||||||
var solver certmagic.DNS01Solver
|
var solver certmagic.DNS01Solver
|
||||||
@@ -135,7 +103,6 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
|
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
|
||||||
return config, nil
|
return config, nil
|
||||||
},
|
},
|
||||||
Logger: zapLogger,
|
|
||||||
})
|
})
|
||||||
config = certmagic.New(cache, *config)
|
config = certmagic.New(cache, *config)
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||||
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
"golang.org/x/crypto/cryptobyte"
|
"golang.org/x/crypto/cryptobyte"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) {
|
func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions, tlsConfig *tls.Config) (Config, error) {
|
||||||
var echConfig []byte
|
var echConfig []byte
|
||||||
if len(options.ECH.Config) > 0 {
|
if len(options.ECH.Config) > 0 {
|
||||||
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
|
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
|
||||||
@@ -45,12 +45,12 @@ func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, op
|
|||||||
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
||||||
return nil, E.New("invalid ECH configs pem")
|
return nil, E.New("invalid ECH configs pem")
|
||||||
}
|
}
|
||||||
clientConfig.SetECHConfigList(block.Bytes)
|
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
|
||||||
return clientConfig, nil
|
return &STDClientConfig{tlsConfig}, nil
|
||||||
} else {
|
} else {
|
||||||
return &ECHClientConfig{
|
return &STDECHClientConfig{
|
||||||
ECHCapableConfig: clientConfig,
|
STDClientConfig: STDClientConfig{tlsConfig},
|
||||||
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,15 +102,15 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ECHClientConfig struct {
|
type STDECHClientConfig struct {
|
||||||
ECHCapableConfig
|
STDClientConfig
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
dnsRouter adapter.DNSRouter
|
dnsRouter adapter.DNSRouter
|
||||||
lastTTL time.Duration
|
lastTTL time.Duration
|
||||||
lastUpdate time.Time
|
lastUpdate time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||||
tlsConn, err := s.fetchAndHandshake(ctx, conn)
|
tlsConn, err := s.fetchAndHandshake(ctx, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -122,17 +122,17 @@ func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (a
|
|||||||
return tlsConn, nil
|
return tlsConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
func (s *STDECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
defer s.access.Unlock()
|
defer s.access.Unlock()
|
||||||
if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL {
|
if len(s.config.EncryptedClientHelloConfigList) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL {
|
||||||
message := &mDNS.Msg{
|
message := &mDNS.Msg{
|
||||||
MsgHdr: mDNS.MsgHdr{
|
MsgHdr: mDNS.MsgHdr{
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
},
|
},
|
||||||
Question: []mDNS.Question{
|
Question: []mDNS.Question{
|
||||||
{
|
{
|
||||||
Name: mDNS.Fqdn(s.ServerName()),
|
Name: mDNS.Fqdn(s.config.ServerName),
|
||||||
Qtype: mDNS.TypeHTTPS,
|
Qtype: mDNS.TypeHTTPS,
|
||||||
Qclass: mDNS.ClassINET,
|
Qclass: mDNS.ClassINET,
|
||||||
},
|
},
|
||||||
@@ -157,21 +157,21 @@ func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn)
|
|||||||
}
|
}
|
||||||
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
|
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
|
||||||
s.lastUpdate = time.Now()
|
s.lastUpdate = time.Now()
|
||||||
s.SetECHConfigList(echConfigList)
|
s.config.EncryptedClientHelloConfigList = echConfigList
|
||||||
break match
|
break match
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(s.ECHConfigList()) == 0 {
|
if len(s.config.EncryptedClientHelloConfigList) == 0 {
|
||||||
return nil, E.New("no ECH config found in DNS records")
|
return nil, E.New("no ECH config found in DNS records")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s.Client(conn)
|
return s.Client(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECHClientConfig) Clone() Config {
|
func (s *STDECHClientConfig) Clone() Config {
|
||||||
return &ECHClientConfig{ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig), dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
return &STDECHClientConfig{STDClientConfig: STDClientConfig{s.config.Clone()}, dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||||
|
|||||||
155
common/tls/ech_keygen.go
Normal file
155
common/tls/ech_keygen.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/pem"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
"github.com/cloudflare/circl/hpke"
|
||||||
|
"github.com/cloudflare/circl/kem"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ECHKeygenDefault(serverName string) (configPem string, keyPem string, err error) {
|
||||||
|
cipherSuites := []echCipherSuite{
|
||||||
|
{
|
||||||
|
kdf: hpke.KDF_HKDF_SHA256,
|
||||||
|
aead: hpke.AEAD_AES128GCM,
|
||||||
|
}, {
|
||||||
|
kdf: hpke.KDF_HKDF_SHA256,
|
||||||
|
aead: hpke.AEAD_ChaCha20Poly1305,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
keyConfig := []myECHKeyConfig{
|
||||||
|
{id: 0, kem: hpke.KEM_X25519_HKDF_SHA256},
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPairs, err := echKeygen(0xfe0d, serverName, keyConfig, cipherSuites)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var configBuffer bytes.Buffer
|
||||||
|
var totalLen uint16
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
totalLen += uint16(len(keyPair.rawConf))
|
||||||
|
}
|
||||||
|
binary.Write(&configBuffer, binary.BigEndian, totalLen)
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
configBuffer.Write(keyPair.rawConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyBuffer bytes.Buffer
|
||||||
|
for _, keyPair := range keyPairs {
|
||||||
|
keyBuffer.Write(keyPair.rawKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBuffer.Bytes()}))
|
||||||
|
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBuffer.Bytes()}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type echKeyConfigPair struct {
|
||||||
|
id uint8
|
||||||
|
rawKey []byte
|
||||||
|
conf myECHKeyConfig
|
||||||
|
rawConf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type echCipherSuite struct {
|
||||||
|
kdf hpke.KDF
|
||||||
|
aead hpke.AEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
type myECHKeyConfig struct {
|
||||||
|
id uint8
|
||||||
|
kem hpke.KEM
|
||||||
|
seed []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite []echCipherSuite) ([]echKeyConfigPair, error) {
|
||||||
|
be := binary.BigEndian
|
||||||
|
// prepare for future update
|
||||||
|
if version != 0xfe0d {
|
||||||
|
return nil, E.New("unsupported ECH version", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
suiteBuf := make([]byte, 0, len(suite)*4+2)
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(len(suite))*4)
|
||||||
|
for _, s := range suite {
|
||||||
|
if !s.kdf.IsValid() || !s.aead.IsValid() {
|
||||||
|
return nil, E.New("invalid HPKE cipher suite")
|
||||||
|
}
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.kdf))
|
||||||
|
suiteBuf = be.AppendUint16(suiteBuf, uint16(s.aead))
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs := []echKeyConfigPair{}
|
||||||
|
for _, c := range conf {
|
||||||
|
pair := echKeyConfigPair{}
|
||||||
|
pair.id = c.id
|
||||||
|
pair.conf = c
|
||||||
|
|
||||||
|
if !c.kem.IsValid() {
|
||||||
|
return nil, E.New("invalid HPKE KEM")
|
||||||
|
}
|
||||||
|
|
||||||
|
kpGenerator := c.kem.Scheme().GenerateKeyPair
|
||||||
|
if len(c.seed) > 0 {
|
||||||
|
kpGenerator = func() (kem.PublicKey, kem.PrivateKey, error) {
|
||||||
|
pub, sec := c.kem.Scheme().DeriveKeyPair(c.seed)
|
||||||
|
return pub, sec, nil
|
||||||
|
}
|
||||||
|
if len(c.seed) < c.kem.Scheme().PrivateKeySize() {
|
||||||
|
return nil, E.New("HPKE KEM seed too short")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, sec, err := kpGenerator()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "generate ECH config key pair")
|
||||||
|
}
|
||||||
|
b := []byte{}
|
||||||
|
b = be.AppendUint16(b, version)
|
||||||
|
b = be.AppendUint16(b, 0) // length field
|
||||||
|
// contents
|
||||||
|
// key config
|
||||||
|
b = append(b, c.id)
|
||||||
|
b = be.AppendUint16(b, uint16(c.kem))
|
||||||
|
pubBuf, err := pub.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "serialize ECH public key")
|
||||||
|
}
|
||||||
|
b = be.AppendUint16(b, uint16(len(pubBuf)))
|
||||||
|
b = append(b, pubBuf...)
|
||||||
|
|
||||||
|
b = append(b, suiteBuf...)
|
||||||
|
// end key config
|
||||||
|
// max name len, not supported
|
||||||
|
b = append(b, 0)
|
||||||
|
// server name
|
||||||
|
b = append(b, byte(len(serverName)))
|
||||||
|
b = append(b, []byte(serverName)...)
|
||||||
|
// extensions, not supported
|
||||||
|
b = be.AppendUint16(b, 0)
|
||||||
|
|
||||||
|
be.PutUint16(b[2:], uint16(len(b)-4))
|
||||||
|
|
||||||
|
pair.rawConf = b
|
||||||
|
|
||||||
|
secBuf, err := sec.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "serialize ECH private key")
|
||||||
|
}
|
||||||
|
sk := []byte{}
|
||||||
|
sk = be.AppendUint16(sk, uint16(len(secBuf)))
|
||||||
|
sk = append(sk, secBuf...)
|
||||||
|
sk = be.AppendUint16(sk, uint16(len(b)))
|
||||||
|
sk = append(sk, b...)
|
||||||
|
pair.rawKey = sk
|
||||||
|
|
||||||
|
pairs = append(pairs, pair)
|
||||||
|
}
|
||||||
|
return pairs, nil
|
||||||
|
}
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdh"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/pem"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/cryptobyte"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ECHCapableConfig interface {
|
|
||||||
Config
|
|
||||||
ECHConfigList() []byte
|
|
||||||
SetECHConfigList([]byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ECHKeygenDefault(publicName string) (configPem string, keyPem string, err error) {
|
|
||||||
echKey, err := ecdh.X25519().GenerateKey(rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
echConfig, err := marshalECHConfig(0, echKey.PublicKey().Bytes(), publicName, 0)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
configBuilder := cryptobyte.NewBuilder(nil)
|
|
||||||
configBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes(echConfig)
|
|
||||||
})
|
|
||||||
configBytes, err := configBuilder.Bytes()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
keyBuilder := cryptobyte.NewBuilder(nil)
|
|
||||||
keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes(echKey.Bytes())
|
|
||||||
})
|
|
||||||
keyBuilder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes(echConfig)
|
|
||||||
})
|
|
||||||
keyBytes, err := keyBuilder.Bytes()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
configPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH CONFIGS", Bytes: configBytes}))
|
|
||||||
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: keyBytes}))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalECHConfig(id uint8, pubKey []byte, publicName string, maxNameLen uint8) ([]byte, error) {
|
|
||||||
const extensionEncryptedClientHello = 0xfe0d
|
|
||||||
const DHKEM_X25519_HKDF_SHA256 = 0x0020
|
|
||||||
const KDF_HKDF_SHA256 = 0x0001
|
|
||||||
builder := cryptobyte.NewBuilder(nil)
|
|
||||||
builder.AddUint16(extensionEncryptedClientHello)
|
|
||||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddUint8(id)
|
|
||||||
|
|
||||||
builder.AddUint16(DHKEM_X25519_HKDF_SHA256) // The only DHKEM we support
|
|
||||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes(pubKey)
|
|
||||||
})
|
|
||||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
const (
|
|
||||||
AEAD_AES_128_GCM = 0x0001
|
|
||||||
AEAD_AES_256_GCM = 0x0002
|
|
||||||
AEAD_ChaCha20Poly1305 = 0x0003
|
|
||||||
)
|
|
||||||
for _, aeadID := range []uint16{AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_ChaCha20Poly1305} {
|
|
||||||
builder.AddUint16(KDF_HKDF_SHA256) // The only KDF we support
|
|
||||||
builder.AddUint16(aeadID)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
builder.AddUint8(maxNameLen)
|
|
||||||
builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {
|
|
||||||
builder.AddBytes([]byte(publicName))
|
|
||||||
})
|
|
||||||
builder.AddUint16(0) // extensions
|
|
||||||
})
|
|
||||||
return builder.Bytes()
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) {
|
func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions, tlsConfig *tls.Config) (Config, error) {
|
||||||
return nil, E.New("ECH requires go1.24, please recompile your binary.")
|
return nil, E.New("ECH requires go1.24, please recompile your binary.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option.
|
|||||||
if decodedLen > 8 {
|
if decodedLen > 8 {
|
||||||
return nil, E.New("invalid short_id")
|
return nil, E.New("invalid short_id")
|
||||||
}
|
}
|
||||||
return &RealityClientConfig{ctx, uClient.(*UTLSClientConfig), publicKey, shortID}, nil
|
return &RealityClientConfig{ctx, uClient, publicKey, shortID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RealityClientConfig) ServerName() string {
|
func (e *RealityClientConfig) ServerName() string {
|
||||||
|
|||||||
@@ -7,60 +7,43 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/tlsfragment"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type STDClientConfig struct {
|
type STDClientConfig struct {
|
||||||
ctx context.Context
|
config *tls.Config
|
||||||
config *tls.Config
|
|
||||||
fragment bool
|
|
||||||
fragmentFallbackDelay time.Duration
|
|
||||||
recordFragment bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) ServerName() string {
|
func (s *STDClientConfig) ServerName() string {
|
||||||
return c.config.ServerName
|
return s.config.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) SetServerName(serverName string) {
|
func (s *STDClientConfig) SetServerName(serverName string) {
|
||||||
c.config.ServerName = serverName
|
s.config.ServerName = serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) NextProtos() []string {
|
func (s *STDClientConfig) NextProtos() []string {
|
||||||
return c.config.NextProtos
|
return s.config.NextProtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) SetNextProtos(nextProto []string) {
|
func (s *STDClientConfig) SetNextProtos(nextProto []string) {
|
||||||
c.config.NextProtos = nextProto
|
s.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Config() (*STDConfig, error) {
|
func (s *STDClientConfig) Config() (*STDConfig, error) {
|
||||||
return c.config, nil
|
return s.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
if c.recordFragment {
|
return tls.Client(conn, s.config), nil
|
||||||
conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)
|
|
||||||
}
|
|
||||||
return tls.Client(conn, c.config), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Clone() Config {
|
func (s *STDClientConfig) Clone() Config {
|
||||||
return &STDClientConfig{c.ctx, c.config.Clone(), c.fragment, c.fragmentFallbackDelay, c.recordFragment}
|
return &STDClientConfig{s.config.Clone()}
|
||||||
}
|
|
||||||
|
|
||||||
func (c *STDClientConfig) ECHConfigList() []byte {
|
|
||||||
return c.config.EncryptedClientHelloConfigList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *STDClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) {
|
|
||||||
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
@@ -77,7 +60,9 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
var tlsConfig tls.Config
|
var tlsConfig tls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
if !options.DisableSNI {
|
if options.DisableSNI {
|
||||||
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
|
} else {
|
||||||
tlsConfig.ServerName = serverName
|
tlsConfig.ServerName = serverName
|
||||||
}
|
}
|
||||||
if options.Insecure {
|
if options.Insecure {
|
||||||
@@ -86,16 +71,12 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
tlsConfig.InsecureSkipVerify = true
|
tlsConfig.InsecureSkipVerify = true
|
||||||
tlsConfig.VerifyConnection = func(state tls.ConnectionState) error {
|
tlsConfig.VerifyConnection = func(state tls.ConnectionState) error {
|
||||||
verifyOptions := x509.VerifyOptions{
|
verifyOptions := x509.VerifyOptions{
|
||||||
Roots: tlsConfig.RootCAs,
|
|
||||||
DNSName: serverName,
|
DNSName: serverName,
|
||||||
Intermediates: x509.NewCertPool(),
|
Intermediates: x509.NewCertPool(),
|
||||||
}
|
}
|
||||||
for _, cert := range state.PeerCertificates[1:] {
|
for _, cert := range state.PeerCertificates[1:] {
|
||||||
verifyOptions.Intermediates.AddCert(cert)
|
verifyOptions.Intermediates.AddCert(cert)
|
||||||
}
|
}
|
||||||
if tlsConfig.Time != nil {
|
|
||||||
verifyOptions.CurrentTime = tlsConfig.Time()
|
|
||||||
}
|
|
||||||
_, err := state.PeerCertificates[0].Verify(verifyOptions)
|
_, err := state.PeerCertificates[0].Verify(verifyOptions)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -146,10 +127,8 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
}
|
}
|
||||||
tlsConfig.RootCAs = certPool
|
tlsConfig.RootCAs = certPool
|
||||||
}
|
}
|
||||||
stdConfig := &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
|
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
return parseECHClientConfig(ctx, stdConfig, options)
|
return parseECHClientConfig(ctx, options, &tlsConfig)
|
||||||
} else {
|
|
||||||
return stdConfig, nil
|
|
||||||
}
|
}
|
||||||
|
return &STDClientConfig{&tlsConfig}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
var err error
|
var err error
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
|
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,10 @@ type TimeServiceWrapper struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
|
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
|
||||||
return func() time.Time {
|
if w.TimeService == nil {
|
||||||
if w.TimeService != nil {
|
return nil
|
||||||
return w.TimeService.TimeFunc()()
|
|
||||||
} else {
|
|
||||||
return time.Now()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return w.TimeService.TimeFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TimeServiceWrapper) Upstream() any {
|
func (w *TimeServiceWrapper) Upstream() any {
|
||||||
|
|||||||
@@ -8,12 +8,11 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/tlsfragment"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
@@ -23,62 +22,48 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UTLSClientConfig struct {
|
type UTLSClientConfig struct {
|
||||||
ctx context.Context
|
config *utls.Config
|
||||||
config *utls.Config
|
id utls.ClientHelloID
|
||||||
id utls.ClientHelloID
|
|
||||||
fragment bool
|
|
||||||
fragmentFallbackDelay time.Duration
|
|
||||||
recordFragment bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) ServerName() string {
|
func (e *UTLSClientConfig) ServerName() string {
|
||||||
return c.config.ServerName
|
return e.config.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetServerName(serverName string) {
|
func (e *UTLSClientConfig) SetServerName(serverName string) {
|
||||||
c.config.ServerName = serverName
|
e.config.ServerName = serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) NextProtos() []string {
|
func (e *UTLSClientConfig) NextProtos() []string {
|
||||||
return c.config.NextProtos
|
return e.config.NextProtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetNextProtos(nextProto []string) {
|
func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
|
||||||
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
|
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
|
||||||
nextProto = append(nextProto, "http/1.1")
|
nextProto = append(nextProto, "http/1.1")
|
||||||
}
|
}
|
||||||
c.config.NextProtos = nextProto
|
e.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) Config() (*STDConfig, error) {
|
func (e *UTLSClientConfig) Config() (*STDConfig, error) {
|
||||||
return nil, E.New("unsupported usage for uTLS")
|
return nil, E.New("unsupported usage for uTLS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
if c.recordFragment {
|
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, e.config.NextProtos}, nil
|
||||||
conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)
|
|
||||||
}
|
|
||||||
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, c.config.Clone(), c.id)}, c.config.NextProtos}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
||||||
c.config.SessionIDGenerator = generator
|
e.config.SessionIDGenerator = generator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) Clone() Config {
|
func (e *UTLSClientConfig) Clone() Config {
|
||||||
return &UTLSClientConfig{
|
return &UTLSClientConfig{
|
||||||
c.ctx, c.config.Clone(), c.id, c.fragment, c.fragmentFallbackDelay, c.recordFragment,
|
config: e.config.Clone(),
|
||||||
|
id: e.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) ECHConfigList() []byte {
|
|
||||||
return c.config.EncryptedClientHelloConfigList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) {
|
|
||||||
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
|
|
||||||
}
|
|
||||||
|
|
||||||
type utlsConnWrapper struct {
|
type utlsConnWrapper struct {
|
||||||
*utls.UConn
|
*utls.UConn
|
||||||
}
|
}
|
||||||
@@ -131,12 +116,14 @@ func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error {
|
|||||||
return c.UConn.HandshakeContext(ctx)
|
return c.UConn.HandshakeContext(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
} else if serverAddress != "" {
|
} else if serverAddress != "" {
|
||||||
serverName = serverAddress
|
if _, err := netip.ParseAddr(serverName); err != nil {
|
||||||
|
serverName = serverAddress
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if serverName == "" && !options.Insecure {
|
if serverName == "" && !options.Insecure {
|
||||||
return nil, E.New("missing server_name or insecure=true")
|
return nil, E.New("missing server_name or insecure=true")
|
||||||
@@ -145,16 +132,15 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
var tlsConfig utls.Config
|
var tlsConfig utls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
if !options.DisableSNI {
|
if options.DisableSNI {
|
||||||
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
|
} else {
|
||||||
tlsConfig.ServerName = serverName
|
tlsConfig.ServerName = serverName
|
||||||
}
|
}
|
||||||
if options.Insecure {
|
if options.Insecure {
|
||||||
tlsConfig.InsecureSkipVerify = options.Insecure
|
tlsConfig.InsecureSkipVerify = options.Insecure
|
||||||
} else if options.DisableSNI {
|
} else if options.DisableSNI {
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
return nil, E.New("disable_sni is unsupported in uTLS")
|
||||||
return nil, E.New("disable_sni is unsupported in reality")
|
|
||||||
}
|
|
||||||
tlsConfig.InsecureServerNameToVerify = serverName
|
|
||||||
}
|
}
|
||||||
if len(options.ALPN) > 0 {
|
if len(options.ALPN) > 0 {
|
||||||
tlsConfig.NextProtos = options.ALPN
|
tlsConfig.NextProtos = options.ALPN
|
||||||
@@ -206,15 +192,7 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
uConfig := &UTLSClientConfig{ctx, &tlsConfig, id, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
|
return &UTLSClientConfig{&tlsConfig, id}, nil
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
|
||||||
return nil, E.New("Reality is conflict with ECH")
|
|
||||||
}
|
|
||||||
return parseECHClientConfig(ctx, uConfig, options)
|
|
||||||
} else {
|
|
||||||
return uConfig, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -242,7 +220,7 @@ func init() {
|
|||||||
|
|
||||||
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
||||||
switch name {
|
switch name {
|
||||||
case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq", "chrome_pq_psk":
|
case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq":
|
||||||
fallthrough
|
fallthrough
|
||||||
case "chrome", "":
|
case "chrome", "":
|
||||||
return utls.HelloChrome_Auto, nil
|
return utls.HelloChrome_Auto, nil
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
@@ -20,21 +19,16 @@ type Conn struct {
|
|||||||
tcpConn *net.TCPConn
|
tcpConn *net.TCPConn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
firstPacketWritten bool
|
firstPacketWritten bool
|
||||||
splitPacket bool
|
|
||||||
splitRecord bool
|
splitRecord bool
|
||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConn(conn net.Conn, ctx context.Context, splitPacket bool, splitRecord bool, fallbackDelay time.Duration) *Conn {
|
func NewConn(conn net.Conn, ctx context.Context, splitRecord bool, fallbackDelay time.Duration) *Conn {
|
||||||
if fallbackDelay == 0 {
|
|
||||||
fallbackDelay = C.TLSFragmentFallbackDelay
|
|
||||||
}
|
|
||||||
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
||||||
return &Conn{
|
return &Conn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
tcpConn: tcpConn,
|
tcpConn: tcpConn,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
splitPacket: splitPacket,
|
|
||||||
splitRecord: splitRecord,
|
splitRecord: splitRecord,
|
||||||
fallbackDelay: fallbackDelay,
|
fallbackDelay: fallbackDelay,
|
||||||
}
|
}
|
||||||
@@ -45,9 +39,9 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
c.firstPacketWritten = true
|
c.firstPacketWritten = true
|
||||||
}()
|
}()
|
||||||
serverName := IndexTLSServerName(b)
|
serverName := indexTLSServerName(b)
|
||||||
if serverName != nil {
|
if serverName != nil {
|
||||||
if c.splitPacket {
|
if !c.splitRecord {
|
||||||
if c.tcpConn != nil {
|
if c.tcpConn != nil {
|
||||||
err = c.tcpConn.SetNoDelay(true)
|
err = c.tcpConn.SetNoDelay(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -87,41 +81,33 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
||||||
}
|
}
|
||||||
if c.splitRecord {
|
if c.splitRecord {
|
||||||
if c.splitPacket {
|
|
||||||
buffer.Reset()
|
|
||||||
}
|
|
||||||
payloadLen := uint16(len(payload))
|
payloadLen := uint16(len(payload))
|
||||||
buffer.Write(b[:3])
|
buffer.Write(b[:3])
|
||||||
binary.Write(&buffer, binary.BigEndian, payloadLen)
|
binary.Write(&buffer, binary.BigEndian, payloadLen)
|
||||||
buffer.Write(payload)
|
buffer.Write(payload)
|
||||||
if c.splitPacket {
|
} else if c.tcpConn != nil && i != len(splitIndexes) {
|
||||||
payload = buffer.Bytes()
|
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if c.splitPacket {
|
_, err = c.Conn.Write(payload)
|
||||||
if c.tcpConn != nil && i != len(splitIndexes) {
|
if err != nil {
|
||||||
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
return
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = c.Conn.Write(payload)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.splitRecord && !c.splitPacket {
|
if c.splitRecord {
|
||||||
_, err = c.Conn.Write(buffer.Bytes())
|
_, err = c.Conn.Write(buffer.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if c.tcpConn != nil {
|
if c.tcpConn != nil {
|
||||||
err = c.tcpConn.SetNoDelay(false)
|
err = c.tcpConn.SetNoDelay(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return len(b), nil
|
return len(b), nil
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func TestTLSFragment(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, false, 0), &tls.Config{
|
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, 0), &tls.Config{
|
||||||
ServerName: "www.cloudflare.com",
|
ServerName: "www.cloudflare.com",
|
||||||
})
|
})
|
||||||
require.NoError(t, tlsConn.Handshake())
|
require.NoError(t, tlsConn.Handshake())
|
||||||
@@ -25,17 +25,7 @@ func TestTLSRecordFragment(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, true, 0), &tls.Config{
|
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, 0), &tls.Config{
|
||||||
ServerName: "www.cloudflare.com",
|
|
||||||
})
|
|
||||||
require.NoError(t, tlsConn.Handshake())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTLS2Fragment(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
|
||||||
require.NoError(t, err)
|
|
||||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, true, 0), &tls.Config{
|
|
||||||
ServerName: "www.cloudflare.com",
|
ServerName: "www.cloudflare.com",
|
||||||
})
|
})
|
||||||
require.NoError(t, tlsConn.Handshake())
|
require.NoError(t, tlsConn.Handshake())
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ const (
|
|||||||
tls13 uint16 = 0x0304
|
tls13 uint16 = 0x0304
|
||||||
)
|
)
|
||||||
|
|
||||||
type MyServerName struct {
|
type myServerName struct {
|
||||||
Index int
|
Index int
|
||||||
Length int
|
Length int
|
||||||
ServerName string
|
ServerName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func IndexTLSServerName(payload []byte) *MyServerName {
|
func indexTLSServerName(payload []byte) *myServerName {
|
||||||
if len(payload) < recordLayerHeaderLen || payload[0] != contentType {
|
if len(payload) < recordLayerHeaderLen || payload[0] != contentType {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -36,48 +36,47 @@ func IndexTLSServerName(payload []byte) *MyServerName {
|
|||||||
if len(payload) < recordLayerHeaderLen+int(segmentLen) {
|
if len(payload) < recordLayerHeaderLen+int(segmentLen) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen:])
|
serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen : recordLayerHeaderLen+int(segmentLen)])
|
||||||
if serverName == nil {
|
if serverName == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
serverName.Index += recordLayerHeaderLen
|
serverName.Length += recordLayerHeaderLen
|
||||||
return serverName
|
return serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexTLSServerNameFromHandshake(handshake []byte) *MyServerName {
|
func indexTLSServerNameFromHandshake(hs []byte) *myServerName {
|
||||||
if len(handshake) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
|
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if handshake[0] != handshakeType {
|
if hs[0] != handshakeType {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
handshakeLen := uint32(handshake[1])<<16 | uint32(handshake[2])<<8 | uint32(handshake[3])
|
handshakeLen := uint32(hs[1])<<16 | uint32(hs[2])<<8 | uint32(hs[3])
|
||||||
if len(handshake[4:]) != int(handshakeLen) {
|
if len(hs[4:]) != int(handshakeLen) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
tlsVersion := uint16(handshake[4])<<8 | uint16(handshake[5])
|
tlsVersion := uint16(hs[4])<<8 | uint16(hs[5])
|
||||||
if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {
|
if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
sessionIDLen := handshake[38]
|
sessionIDLen := hs[38]
|
||||||
currentIndex := handshakeHeaderLen + randomDataLen + sessionIDHeaderLen + int(sessionIDLen)
|
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen) {
|
||||||
if len(handshake) < currentIndex {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cipherSuites := handshake[currentIndex:]
|
cs := hs[handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen):]
|
||||||
if len(cipherSuites) < cipherSuiteHeaderLen {
|
if len(cs) < cipherSuiteHeaderLen {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
csLen := uint16(cipherSuites[0])<<8 | uint16(cipherSuites[1])
|
csLen := uint16(cs[0])<<8 | uint16(cs[1])
|
||||||
if len(cipherSuites) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
compressMethodLen := uint16(cipherSuites[cipherSuiteHeaderLen+int(csLen)])
|
compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
|
||||||
currentIndex += cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
|
||||||
if len(handshake) < currentIndex {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
serverName := indexTLSServerNameFromExtensions(handshake[currentIndex:])
|
currentIndex := cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)
|
||||||
|
serverName := indexTLSServerNameFromExtensions(cs[currentIndex:])
|
||||||
if serverName == nil {
|
if serverName == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -85,7 +84,7 @@ func indexTLSServerNameFromHandshake(handshake []byte) *MyServerName {
|
|||||||
return serverName
|
return serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexTLSServerNameFromExtensions(exs []byte) *MyServerName {
|
func indexTLSServerNameFromExtensions(exs []byte) *myServerName {
|
||||||
if len(exs) == 0 {
|
if len(exs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -119,8 +118,7 @@ func indexTLSServerNameFromExtensions(exs []byte) *MyServerName {
|
|||||||
}
|
}
|
||||||
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
||||||
sex = sex[sniExtensionHeaderLen:]
|
sex = sex[sniExtensionHeaderLen:]
|
||||||
|
return &myServerName{
|
||||||
return &MyServerName{
|
|
||||||
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
||||||
Length: int(sniLen),
|
Length: int(sniLen),
|
||||||
ServerName: string(sex),
|
ServerName: string(sex),
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
package tf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/tlsfragment"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIndexTLSServerName(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
payload, err := hex.DecodeString("16030105f8010005f403036e35de7389a679c54029cf452611f2211c70d9ac3897271de589ab6155f8e4ab20637d225f1ef969ad87ed78bfb9d171300bcb1703b6f314ccefb964f79b7d0961002a0a0a130213031301c02cc02bcca9c030c02fcca8c00ac009c014c013009d009c0035002fc008c012000a01000581baba00000000000f000d00000a6769746875622e636f6d00170000ff01000100000a000e000c3a3a11ec001d001700180019000b000201000010000e000c02683208687474702f312e31000500050100000000000d00160014040308040401050308050805050108060601020100120000003304ef04ed3a3a00010011ec04c0aeb2250c092a3463161cccb29d9183331a424964248579507ed23a180b0ceab2a5f5d9ce41547e497a89055471ea572867ba3a1fc3c9e45025274a20f60c6b60e62476b6afed0403af59ab83660ef4112ae20386a602010d0a5d454c0ed34c84ed4423e750213e6a2baab1bf9c4367a6007ab40a33d95220c2dcaa44f257024a5626b545db0510f4311b1a60714154909c6a61fdfca011fb2626d657aeb6070bf078508babe3b584555013e34acc56198ed4663742b3155a664a9901794c4586820a7dc162c01827291f3792e1237f801a8d1ef096013c181c4a58d2f6859ba75022d18cc4418bd4f351d5c18f83a58857d05af860c4b9ac018a5b63f17184e591532c6bc2cf2215d4a282c8a8a4f6f7aee110422c8bc9ebd3b1d609c568523aaae555db320e6c269473d87af38c256cbb9febc20aea6380c32a8916f7a373c8b1e37554e3260bf6621f6b804ee80b3c516b1d01985bf4c603b6daa9a5991de6a7a29f3a7122b8afb843a7660110fce62b43c615f5bcc2db688ba012649c0952b0a2c031e732d2b454c6b2968683cb8d244be2c9a7fa163222979eaf92722b92b862d81a3d94450c2b60c318421ebb4307c42d1f0473592a5c30e42039cc68cda9721e61aa63f49def17c15221680ed444896340133bbee67556f56b9f9d78a4df715f926a12add0cc9c862e46ea8b7316ae468282c18601b2771c9c9322f982228cf93effaacd3f80cbd12bce5fc36f56e2a3caf91e578a5fae00c9b23a8ed1a66764f4433c3628a70b8f0a6196adc60a4cb4226f07ba4c6b363fe9065563bfc1347452946386bab488686e837ab979c64f9047417fca635fe1bb4f074f256cc8af837c7b455e280426547755af90a61640169ef180aea3a77e662bb6dac1b6c3696027129b1a5edf495314e9c7f4b6110e16378ec893fa24642330a40aba1a85326101acb97c620fd8d71389e69eaed7bdb01bbe1fd428d66191150c7b2cd1ad4257391676a82ba8ce07fb2667c3b289f159003a7c7bc31d361b7b7f49a802961739d950dfcc0fa1c7abce5abdd2245101da391151490862028110465950b9e9c03d08a90998ab83267838d2e74a0593bc81f74cdf734519a05b351c0e5488c68dd810e6e9142ccc1e2f4a7f464297eb340e27acc6b9d64e12e38cce8492b3d939140b5a9e149a75597f10a23874c84323a07cdd657274378f887c85c4259b9c04cd33ba58ed630ef2a744f8e19dd34843dff331d2a6be7e2332c599289cd248a611c73d7481cd4a9bd43449a3836f14b2af18a1739e17999e4c67e85cc5bcecabb14185e5bcaff3c96098f03dc5aba819f29587758f49f940585354a2a780830528d68ccd166920dadcaa25cab5fc1907272a826aba3f08bc6b88757776812ecb6c7cec69a223ec0a13a7b62a2349a0f63ed7a27a3b15ba21d71fe6864ec6e089ae17cadd433fa3138f7ee24353c11365818f8fc34f43a05542d18efaac24bfccc1f748a0cc1a67ad379468b76fd34973dba785f5c91d618333cd810fe0700d1bbc8422029782628070a624c52c5309a4a64d625b11f8033ab28df34a1add297517fcc06b92b6817b3c5144438cf260867c57bde68c8c4b82e6a135ef676a52fbae5708002a404e6189a60e2836de565ad1b29e3819e5ed49f6810bcb28e1bd6de57306f94b79d9dae1cc4624d2a068499beef81cd5fe4b76dcbfff2a2008001d002001976128c6d5a934533f28b9914d2480aab2a8c1ab03d212529ce8b27640a716002d00020101002b000706caca03040303001b00030200015a5a000100")
|
|
||||||
require.NoError(t, err)
|
|
||||||
serverName := tf.IndexTLSServerName(payload)
|
|
||||||
require.NotNil(t, serverName)
|
|
||||||
require.Equal(t, serverName.ServerName, string(payload[serverName.Index:serverName.Index+serverName.Length]))
|
|
||||||
require.Equal(t, "github.com", serverName.ServerName)
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -126,15 +126,6 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp.Body.Close()
|
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)
|
t = uint16(time.Since(start) / time.Millisecond)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,337 +0,0 @@
|
|||||||
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())
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
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)
|
|
||||||
)
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
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{}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
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)))
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
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())
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
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[:])
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
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{}{}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -28,7 +28,6 @@ const (
|
|||||||
DNSTypeFakeIP = "fakeip"
|
DNSTypeFakeIP = "fakeip"
|
||||||
DNSTypeDHCP = "dhcp"
|
DNSTypeDHCP = "dhcp"
|
||||||
DNSTypeTailscale = "tailscale"
|
DNSTypeTailscale = "tailscale"
|
||||||
DNSTypeSDNS = "sdns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -15,19 +15,15 @@ const (
|
|||||||
TypeTrojan = "trojan"
|
TypeTrojan = "trojan"
|
||||||
TypeNaive = "naive"
|
TypeNaive = "naive"
|
||||||
TypeWireGuard = "wireguard"
|
TypeWireGuard = "wireguard"
|
||||||
TypeWARP = "warp"
|
|
||||||
TypeHysteria = "hysteria"
|
TypeHysteria = "hysteria"
|
||||||
TypeTor = "tor"
|
TypeTor = "tor"
|
||||||
TypeSSH = "ssh"
|
TypeSSH = "ssh"
|
||||||
TypeShadowTLS = "shadowtls"
|
TypeShadowTLS = "shadowtls"
|
||||||
TypeMieru = "mieru"
|
|
||||||
TypeAnyTLS = "anytls"
|
TypeAnyTLS = "anytls"
|
||||||
TypeShadowsocksR = "shadowsocksr"
|
TypeShadowsocksR = "shadowsocksr"
|
||||||
TypeVLESS = "vless"
|
TypeVLESS = "vless"
|
||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
TypeHysteria2 = "hysteria2"
|
TypeHysteria2 = "hysteria2"
|
||||||
TypeTunnelClient = "tunnel_client"
|
|
||||||
TypeTunnelServer = "tunnel_server"
|
|
||||||
TypeTailscale = "tailscale"
|
TypeTailscale = "tailscale"
|
||||||
TypeDERP = "derp"
|
TypeDERP = "derp"
|
||||||
TypeResolved = "resolved"
|
TypeResolved = "resolved"
|
||||||
@@ -69,8 +65,6 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "Naive"
|
return "Naive"
|
||||||
case TypeWireGuard:
|
case TypeWireGuard:
|
||||||
return "WireGuard"
|
return "WireGuard"
|
||||||
case TypeWARP:
|
|
||||||
return "WARP"
|
|
||||||
case TypeHysteria:
|
case TypeHysteria:
|
||||||
return "Hysteria"
|
return "Hysteria"
|
||||||
case TypeTor:
|
case TypeTor:
|
||||||
@@ -87,18 +81,12 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "TUIC"
|
return "TUIC"
|
||||||
case TypeHysteria2:
|
case TypeHysteria2:
|
||||||
return "Hysteria2"
|
return "Hysteria2"
|
||||||
case TypeMieru:
|
|
||||||
return "Mieru"
|
|
||||||
case TypeAnyTLS:
|
case TypeAnyTLS:
|
||||||
return "AnyTLS"
|
return "AnyTLS"
|
||||||
case TypeSelector:
|
case TypeSelector:
|
||||||
return "Selector"
|
return "Selector"
|
||||||
case TypeURLTest:
|
case TypeURLTest:
|
||||||
return "URLTest"
|
return "URLTest"
|
||||||
case TypeTunnelClient:
|
|
||||||
return "Tunnel Client"
|
|
||||||
case TypeTunnelServer:
|
|
||||||
return "Tunnel Server"
|
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,4 @@ const (
|
|||||||
V2RayTransportTypeQUIC = "quic"
|
V2RayTransportTypeQUIC = "quic"
|
||||||
V2RayTransportTypeGRPC = "grpc"
|
V2RayTransportTypeGRPC = "grpc"
|
||||||
V2RayTransportTypeHTTPUpgrade = "httpupgrade"
|
V2RayTransportTypeHTTPUpgrade = "httpupgrade"
|
||||||
V2RayTransportTypeXHTTP = "xhttp"
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
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"`
|
|
||||||
}
|
|
||||||
@@ -34,7 +34,6 @@ type Client struct {
|
|||||||
disableCache bool
|
disableCache bool
|
||||||
disableExpire bool
|
disableExpire bool
|
||||||
independentCache bool
|
independentCache bool
|
||||||
clientSubnet netip.Prefix
|
|
||||||
rdrc adapter.RDRCStore
|
rdrc adapter.RDRCStore
|
||||||
initRDRCFunc func() adapter.RDRCStore
|
initRDRCFunc func() adapter.RDRCStore
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
@@ -48,7 +47,6 @@ type ClientOptions struct {
|
|||||||
DisableExpire bool
|
DisableExpire bool
|
||||||
IndependentCache bool
|
IndependentCache bool
|
||||||
CacheCapacity uint32
|
CacheCapacity uint32
|
||||||
ClientSubnet netip.Prefix
|
|
||||||
RDRC func() adapter.RDRCStore
|
RDRC func() adapter.RDRCStore
|
||||||
Logger logger.ContextLogger
|
Logger logger.ContextLogger
|
||||||
}
|
}
|
||||||
@@ -59,7 +57,6 @@ func NewClient(options ClientOptions) *Client {
|
|||||||
disableCache: options.DisableCache,
|
disableCache: options.DisableCache,
|
||||||
disableExpire: options.DisableExpire,
|
disableExpire: options.DisableExpire,
|
||||||
independentCache: options.IndependentCache,
|
independentCache: options.IndependentCache,
|
||||||
clientSubnet: options.ClientSubnet,
|
|
||||||
initRDRCFunc: options.RDRC,
|
initRDRCFunc: options.RDRC,
|
||||||
logger: options.Logger,
|
logger: options.Logger,
|
||||||
}
|
}
|
||||||
@@ -107,12 +104,8 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return &responseMessage, nil
|
return &responseMessage, nil
|
||||||
}
|
}
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
clientSubnet := options.ClientSubnet
|
if options.ClientSubnet.IsValid() {
|
||||||
if !clientSubnet.IsValid() {
|
message = SetClientSubnet(message, options.ClientSubnet)
|
||||||
clientSubnet = c.clientSubnet
|
|
||||||
}
|
|
||||||
if clientSubnet.IsValid() {
|
|
||||||
message = SetClientSubnet(message, clientSubnet)
|
|
||||||
}
|
}
|
||||||
isSimpleRequest := len(message.Question) == 1 &&
|
isSimpleRequest := len(message.Question) == 1 &&
|
||||||
len(message.Ns) == 0 &&
|
len(message.Ns) == 0 &&
|
||||||
@@ -195,13 +188,8 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
if responseChecker != nil {
|
if responseChecker != nil {
|
||||||
var rejected bool
|
addr, addrErr := MessageToAddresses(response)
|
||||||
if !(response.Rcode == dns.RcodeSuccess || response.Rcode == dns.RcodeNameError) {
|
if addrErr != nil || !responseChecker(addr) {
|
||||||
rejected = true
|
|
||||||
} else {
|
|
||||||
rejected = !responseChecker(MessageToAddresses(response))
|
|
||||||
}
|
|
||||||
if rejected {
|
|
||||||
if c.rdrc != nil {
|
if c.rdrc != nil {
|
||||||
c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)
|
c.rdrc.SaveRDRCAsync(transport.Tag(), question.Name, question.Qtype, c.logger)
|
||||||
}
|
}
|
||||||
@@ -425,10 +413,7 @@ func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTran
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if response.Rcode != dns.RcodeSuccess {
|
return MessageToAddresses(response)
|
||||||
return nil, RcodeError(response.Rcode)
|
|
||||||
}
|
|
||||||
return MessageToAddresses(response), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) questionCache(question dns.Question, transport adapter.DNSTransport) ([]netip.Addr, error) {
|
func (c *Client) questionCache(question dns.Question, transport adapter.DNSTransport) ([]netip.Addr, error) {
|
||||||
@@ -436,10 +421,7 @@ func (c *Client) questionCache(question dns.Question, transport adapter.DNSTrans
|
|||||||
if response == nil {
|
if response == nil {
|
||||||
return nil, ErrNotCached
|
return nil, ErrNotCached
|
||||||
}
|
}
|
||||||
if response.Rcode != dns.RcodeSuccess {
|
return MessageToAddresses(response)
|
||||||
return nil, RcodeError(response.Rcode)
|
|
||||||
}
|
|
||||||
return MessageToAddresses(response), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int) {
|
func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransport) (*dns.Msg, int) {
|
||||||
@@ -516,7 +498,10 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
func MessageToAddresses(response *dns.Msg) ([]netip.Addr, error) {
|
||||||
|
if response.Rcode != dns.RcodeSuccess {
|
||||||
|
return nil, RcodeError(response.Rcode)
|
||||||
|
}
|
||||||
addresses := make([]netip.Addr, 0, len(response.Answer))
|
addresses := make([]netip.Addr, 0, len(response.Answer))
|
||||||
for _, rawAnswer := range response.Answer {
|
for _, rawAnswer := range response.Answer {
|
||||||
switch answer := rawAnswer.(type) {
|
switch answer := rawAnswer.(type) {
|
||||||
@@ -532,7 +517,7 @@ func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return addresses
|
return addresses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapError(err error) error {
|
func wrapError(err error) error {
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
|||||||
DisableExpire: options.DNSClientOptions.DisableExpire,
|
DisableExpire: options.DNSClientOptions.DisableExpire,
|
||||||
IndependentCache: options.DNSClientOptions.IndependentCache,
|
IndependentCache: options.DNSClientOptions.IndependentCache,
|
||||||
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
||||||
ClientSubnet: options.DNSClientOptions.ClientSubnet.Build(netip.Prefix{}),
|
|
||||||
RDRC: func() adapter.RDRCStore {
|
RDRC: func() adapter.RDRCStore {
|
||||||
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
||||||
if cacheFile == nil {
|
if cacheFile == nil {
|
||||||
|
|||||||
@@ -3,15 +3,11 @@ package transport
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
@@ -43,13 +39,11 @@ func RegisterHTTPS(registry *dns.TransportRegistry) {
|
|||||||
|
|
||||||
type HTTPSTransport struct {
|
type HTTPSTransport struct {
|
||||||
dns.TransportAdapter
|
dns.TransportAdapter
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
destination *url.URL
|
destination *url.URL
|
||||||
headers http.Header
|
headers http.Header
|
||||||
transportAccess sync.Mutex
|
transport *http.Transport
|
||||||
transport *http.Transport
|
|
||||||
transportResetAt time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) {
|
func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
@@ -167,33 +161,12 @@ func (t *HTTPSTransport) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTPSTransport) Close() error {
|
func (t *HTTPSTransport) Close() error {
|
||||||
t.transportAccess.Lock()
|
|
||||||
defer t.transportAccess.Unlock()
|
|
||||||
t.transport.CloseIdleConnections()
|
t.transport.CloseIdleConnections()
|
||||||
t.transport = t.transport.Clone()
|
t.transport = t.transport.Clone()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
startAt := time.Now()
|
|
||||||
response, err := t.exchange(ctx, message)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, os.ErrDeadlineExceeded) {
|
|
||||||
t.transportAccess.Lock()
|
|
||||||
defer t.transportAccess.Unlock()
|
|
||||||
if t.transportResetAt.After(startAt) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
t.transport.CloseIdleConnections()
|
|
||||||
t.transport = t.transport.Clone()
|
|
||||||
t.transportResetAt = time.Now()
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *HTTPSTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
exMessage := *message
|
exMessage := *message
|
||||||
exMessage.Id = 0
|
exMessage.Id = 0
|
||||||
exMessage.Compress = true
|
exMessage.Compress = true
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package local
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -90,9 +91,9 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
|
|||||||
startRacer := func(ctx context.Context, fqdn string) {
|
startRacer := func(ctx context.Context, fqdn string) {
|
||||||
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if response.Rcode != mDNS.RcodeSuccess {
|
var addresses []netip.Addr
|
||||||
err = dns.RcodeError(response.Rcode)
|
addresses, err = dns.MessageToAddresses(response)
|
||||||
} else if len(dns.MessageToAddresses(response)) == 0 {
|
if err == nil && len(addresses) == 0 {
|
||||||
err = E.New(fqdn, ": empty result")
|
err = E.New(fqdn, ": empty result")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
||||||
var state C.struct___res_state
|
if C.res_init() != 0 {
|
||||||
if C.res_ninit(&state) != 0 {
|
|
||||||
return &dnsConfig{
|
return &dnsConfig{
|
||||||
servers: defaultNS,
|
servers: defaultNS,
|
||||||
search: dnsDefaultSearch(),
|
search: dnsDefaultSearch(),
|
||||||
@@ -34,10 +33,10 @@ func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
|||||||
conf := &dnsConfig{
|
conf := &dnsConfig{
|
||||||
ndots: 1,
|
ndots: 1,
|
||||||
timeout: 5 * time.Second,
|
timeout: 5 * time.Second,
|
||||||
attempts: int(state.retry),
|
attempts: int(C._res.retry),
|
||||||
}
|
}
|
||||||
for i := 0; i < int(state.nscount); i++ {
|
for i := 0; i < int(C._res.nscount); i++ {
|
||||||
ns := state.nsaddr_list[i]
|
ns := C._res.nsaddr_list[i]
|
||||||
addr := C.inet_ntoa(ns.sin_addr)
|
addr := C.inet_ntoa(ns.sin_addr)
|
||||||
if addr == nil {
|
if addr == nil {
|
||||||
continue
|
continue
|
||||||
@@ -45,7 +44,7 @@ func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
|||||||
conf.servers = append(conf.servers, C.GoString(addr))
|
conf.servers = append(conf.servers, C.GoString(addr))
|
||||||
}
|
}
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
search := state.dnsrch[i]
|
search := C._res.dnsrch[i]
|
||||||
if search == nil {
|
if search == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDNSReadConfig(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
require.NoError(t, dnsReadConfig(context.Background(), "/etc/resolv.conf").err)
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
package transport
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ameshkov/dnscrypt/v2"
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/dns"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ adapter.DNSTransport = (*SDNSTransport)(nil)
|
|
||||||
|
|
||||||
func RegisterSDNS(registry *dns.TransportRegistry) {
|
|
||||||
dns.RegisterTransport[option.SDNSDNSServerOptions](registry, C.DNSTypeSDNS, NewSDNSTransport)
|
|
||||||
}
|
|
||||||
|
|
||||||
type SDNSTransport struct {
|
|
||||||
dns.TransportAdapter
|
|
||||||
client *dnscrypt.Client
|
|
||||||
name string
|
|
||||||
stamp string
|
|
||||||
|
|
||||||
mtx sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.SDNSDNSServerOptions) (adapter.DNSTransport, error) {
|
|
||||||
transportDialer, err := dns.NewRemoteDialer(ctx, options.RemoteDNSServerOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &SDNSTransport{
|
|
||||||
client: &dnscrypt.Client{
|
|
||||||
Net: "udp",
|
|
||||||
Timeout: 10 * time.Second,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return transportDialer.DialContext(ctx, N.NetworkName(network), M.ParseSocksaddr(addr))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
stamp: options.Stamp,
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *SDNSTransport) Name() string {
|
|
||||||
return t.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *SDNSTransport) Start(adapter.StartStage) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *SDNSTransport) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *SDNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
resolverInfo, err := t.client.Dial(t.stamp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return t.client.Exchange(message, resolverInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ func NewUDPRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
udpSize: 2048,
|
udpSize: 512,
|
||||||
tcpTransport: &TCPTransport{
|
tcpTransport: &TCPTransport{
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
@@ -97,19 +97,15 @@ func (t *UDPTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
t.access.Lock()
|
|
||||||
if edns0Opt := message.IsEdns0(); edns0Opt != nil {
|
|
||||||
if udpSize := int(edns0Opt.UDPSize()); udpSize > t.udpSize {
|
|
||||||
t.udpSize = udpSize
|
|
||||||
close(t.done)
|
|
||||||
t.done = make(chan struct{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.access.Unlock()
|
|
||||||
conn, err := t.open(ctx)
|
conn, err := t.open(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if edns0Opt := message.IsEdns0(); edns0Opt != nil {
|
||||||
|
if udpSize := int(edns0Opt.UDPSize()); udpSize > t.udpSize {
|
||||||
|
t.udpSize = udpSize
|
||||||
|
}
|
||||||
|
}
|
||||||
buffer := buf.NewSize(1 + message.Len())
|
buffer := buf.NewSize(1 + message.Len())
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
exMessage := *message
|
exMessage := *message
|
||||||
|
|||||||
@@ -2,234 +2,10 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.12.1
|
#### 1.12.0-beta.18
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.12.0
|
|
||||||
|
|
||||||
* Refactor DNS servers **1**
|
|
||||||
* Add domain resolver options**2**
|
|
||||||
* Add TLS fragment/record fragment support to route options and outbound TLS options **3**
|
|
||||||
* Add certificate options **4**
|
|
||||||
* Add Tailscale endpoint and DNS server **5**
|
|
||||||
* Drop support for go1.22 **6**
|
|
||||||
* Add AnyTLS protocol **7**
|
|
||||||
* Migrate to stdlib ECH implementation **8**
|
|
||||||
* Add NTP sniffer **9**
|
|
||||||
* Add wildcard SNI support for ShadowTLS inbound **10**
|
|
||||||
* Improve `auto_redirect` **11**
|
|
||||||
* Add control options for listeners **12**
|
|
||||||
* Add DERP service **13**
|
|
||||||
* Add Resolved service and DNS server **14**
|
|
||||||
* Add SSM API service **15**
|
|
||||||
* Add loopback address support for tun **16**
|
|
||||||
* Improve tun performance on Apple platforms **17**
|
|
||||||
* Update quic-go to v0.52.0
|
|
||||||
* Update gVisor to 20250319.0
|
|
||||||
* Update the status of graphical clients in stores **18**
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
DNS servers are refactored for better performance and scalability.
|
|
||||||
|
|
||||||
See [DNS server](/configuration/dns/server/).
|
|
||||||
|
|
||||||
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-servers).
|
|
||||||
|
|
||||||
Compatibility for old formats will be removed in sing-box 1.14.0.
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
Legacy `outbound` DNS rules are deprecated
|
|
||||||
and can be replaced by the new `domain_resolver` option.
|
|
||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial/#domain_resolver) and
|
|
||||||
[Route](/configuration/route/#default_domain_resolver).
|
|
||||||
|
|
||||||
For migration,
|
|
||||||
see [Migrate outbound DNS rule items to domain resolver](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver).
|
|
||||||
|
|
||||||
**3**:
|
|
||||||
|
|
||||||
See [Route Action](/configuration/route/rule_action/#tls_fragment) and [TLS](/configuration/shared/tls/).
|
|
||||||
|
|
||||||
**4**:
|
|
||||||
|
|
||||||
New certificate options allow you to manage the default list of trusted X509 CA certificates.
|
|
||||||
|
|
||||||
For the system certificate list, fixed Go not reading Android trusted certificates correctly.
|
|
||||||
|
|
||||||
You can also use the Mozilla Included List instead, or add trusted certificates yourself.
|
|
||||||
|
|
||||||
See [Certificate](/configuration/certificate/).
|
|
||||||
|
|
||||||
**5**:
|
|
||||||
|
|
||||||
See [Tailscale](/configuration/endpoint/tailscale/).
|
|
||||||
|
|
||||||
**6**:
|
|
||||||
|
|
||||||
Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to compile.
|
|
||||||
|
|
||||||
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
|
||||||
|
|
||||||
**7**:
|
|
||||||
|
|
||||||
The new AnyTLS protocol claims to mitigate TLS proxy traffic characteristics and comes with a new multiplexing scheme.
|
|
||||||
|
|
||||||
See [AnyTLS Inbound](/configuration/inbound/anytls/) and [AnyTLS Outbound](/configuration/outbound/anytls/).
|
|
||||||
|
|
||||||
**8**:
|
|
||||||
|
|
||||||
See [TLS](/configuration/shared/tls).
|
|
||||||
|
|
||||||
The build tag `with_ech` is no longer needed and has been removed.
|
|
||||||
|
|
||||||
**9**:
|
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/).
|
|
||||||
|
|
||||||
**10**:
|
|
||||||
|
|
||||||
See [ShadowTLS](/configuration/inbound/shadowtls/#wildcard_sni).
|
|
||||||
|
|
||||||
**11**:
|
|
||||||
|
|
||||||
Now `auto_redirect` fixes compatibility issues between tun and Docker bridge networks,
|
|
||||||
see [Tun](/configuration/inbound/tun/#auto_redirect).
|
|
||||||
|
|
||||||
**12**:
|
|
||||||
|
|
||||||
You can now set `bind_interface`, `routing_mark` and `reuse_addr` in Listen Fields.
|
|
||||||
|
|
||||||
See [Listen Fields](/configuration/shared/listen/).
|
|
||||||
|
|
||||||
**13**:
|
|
||||||
|
|
||||||
DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper).
|
|
||||||
|
|
||||||
See [DERP Service](/configuration/service/derp/).
|
|
||||||
|
|
||||||
**14**:
|
|
||||||
|
|
||||||
Resolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs
|
|
||||||
(e.g. NetworkManager) and provide DNS resolution.
|
|
||||||
|
|
||||||
See [Resolved Service](/configuration/service/resolved/) and [Resolved DNS Server](/configuration/dns/server/resolved/).
|
|
||||||
|
|
||||||
**15**:
|
|
||||||
|
|
||||||
SSM API service is a RESTful API server for managing Shadowsocks servers.
|
|
||||||
|
|
||||||
See [SSM API Service](/configuration/service/ssm-api/).
|
|
||||||
|
|
||||||
**16**:
|
|
||||||
|
|
||||||
TUN now implements SideStore's StosVPN.
|
|
||||||
|
|
||||||
See [Tun](/configuration/inbound/tun/#loopback_address).
|
|
||||||
|
|
||||||
**17**:
|
|
||||||
|
|
||||||
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
|
|
||||||
|
|
||||||
The following data was tested using [tun_bench](https://github.com/SagerNet/sing-box/blob/dev-next/cmd/internal/tun_bench/main.go) on M4 MacBook pro.
|
|
||||||
|
|
||||||
| Version | Stack | MTU | Upload | Download |
|
|
||||||
|-------------|--------|-------|--------|----------|
|
|
||||||
| 1.11.15 | gvisor | 1500 | 852M | 2.57G |
|
|
||||||
| 1.12.0-rc.4 | gvisor | 1500 | 2.90G | 4.68G |
|
|
||||||
| 1.11.15 | gvisor | 4064 | 2.31G | 6.34G |
|
|
||||||
| 1.12.0-rc.4 | gvisor | 4064 | 7.54G | 12.2G |
|
|
||||||
| 1.11.15 | gvisor | 65535 | 27.6G | 18.1G |
|
|
||||||
| 1.12.0-rc.4 | gvisor | 65535 | 39.8G | 34.7G |
|
|
||||||
| 1.11.15 | system | 1500 | 664M | 706M |
|
|
||||||
| 1.12.0-rc.4 | system | 1500 | 2.44G | 2.51G |
|
|
||||||
| 1.11.15 | system | 4064 | 1.88G | 1.94G |
|
|
||||||
| 1.12.0-rc.4 | system | 4064 | 6.45G | 6.27G |
|
|
||||||
| 1.11.15 | system | 65535 | 26.2G | 17.4G |
|
|
||||||
| 1.12.0-rc.4 | system | 65535 | 17.6G | 21.0G |
|
|
||||||
|
|
||||||
**18**:
|
|
||||||
|
|
||||||
We continue to experience issues updating our sing-box apps on the App Store and Play Store.
|
|
||||||
Until we rewrite and resubmit the apps, they are considered irrecoverable.
|
|
||||||
Therefore, after this release, we will not be repeating this notice unless there is new information.
|
|
||||||
|
|
||||||
### 1.11.15
|
|
||||||
|
|
||||||
* 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.12.0-beta.32
|
|
||||||
|
|
||||||
* Improve tun performance on Apple platforms **1**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
|
|
||||||
|
|
||||||
### 1.11.14
|
|
||||||
|
|
||||||
* 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.12.0-beta.24
|
|
||||||
|
|
||||||
* Allow `tls_fragment` and `tls_record_fragment` to be enabled together **1**
|
|
||||||
* Also add fragment options for TLS client configuration **2**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
For debugging only, it is recommended to disable if record fragmentation works.
|
|
||||||
|
|
||||||
See [Route Action](/configuration/route/rule_action/#tls_fragment).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
See [TLS](/configuration/shared/tls/).
|
|
||||||
|
|
||||||
#### 1.12.0-beta.23
|
|
||||||
|
|
||||||
* Add loopback address support for tun **1**
|
|
||||||
* Add cache support for ssm-api **2**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
TUN now implements SideStore's StosVPN.
|
|
||||||
|
|
||||||
See [Tun](/configuration/inbound/tun/#loopback_address).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
See [SSM API Service](/configuration/service/ssm-api/#cache_path).
|
|
||||||
|
|
||||||
#### 1.12.0-beta.21
|
|
||||||
|
|
||||||
* Fix missing `home` option for DERP service **1**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
You can now choose what the DERP home page shows, just like with derper's `-home` flag.
|
|
||||||
|
|
||||||
See [DERP](/configuration/service/derp/#home).
|
|
||||||
|
|
||||||
### 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.12.0-beta.17
|
#### 1.12.0-beta.17
|
||||||
|
|
||||||
* Update quic-go to v0.52.0
|
* Update quic-go to v0.52.0
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
---
|
---
|
||||||
icon: material/alert-decagram
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
|
||||||
|
|
||||||
:material-decagram: [servers](#servers)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [cache_capacity](#cache_capacity)
|
:material-plus: [cache_capacity](#cache_capacity)
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
---
|
---
|
||||||
icon: material/alert-decagram
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
|
||||||
|
|
||||||
:material-decagram: [servers](#servers)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [cache_capacity](#cache_capacity)
|
:material-plus: [cache_capacity](#cache_capacity)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
| 类型 | 格式 |
|
| 类型 | 格式 |
|
||||||
|-------------|---------------------------|
|
|-------------|---------------------------|
|
||||||
| `wireguard` | [WireGuard](./wireguard/) |
|
| `wireguard` | [WireGuard](./wiregaurd/) |
|
||||||
| `tailscale` | [Tailscale](./tailscale/) |
|
| `tailscale` | [Tailscale](./tailscale/) |
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user