mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-07 00:49:16 +03:00
Compare commits
1 Commits
v1.3-beta9
...
dev-l3-rou
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9092139e73 |
12
.github/workflows/debug.yml
vendored
12
.github/workflows/debug.yml
vendored
@@ -31,6 +31,12 @@ jobs:
|
|||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
|
- name: Cache go module
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: go-${{ hashFiles('**/go.sum') }}
|
||||||
- name: Add cache to Go proxy
|
- name: Add cache to Go proxy
|
||||||
run: |
|
run: |
|
||||||
version=`git rev-parse HEAD`
|
version=`git rev-parse HEAD`
|
||||||
@@ -190,6 +196,12 @@ jobs:
|
|||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
|
- name: Cache go module
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: go-${{ hashFiles('**/go.sum') }}
|
||||||
- name: Build
|
- name: Build
|
||||||
id: build
|
id: build
|
||||||
run: make
|
run: make
|
||||||
|
|||||||
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
@@ -31,6 +31,12 @@ jobs:
|
|||||||
uses: actions/setup-go@v4
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
|
- name: Cache go module
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/go/pkg/mod
|
||||||
|
key: go-${{ hashFiles('**/go.sum') }}
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v3
|
uses: golangci/golangci-lint-action@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -117,6 +117,8 @@ nfpms:
|
|||||||
dst: /etc/systemd/system/sing-box@.service
|
dst: /etc/systemd/system/sing-box@.service
|
||||||
- src: LICENSE
|
- src: LICENSE
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
|
scripts:
|
||||||
|
postremove: release/config/postremove.sh
|
||||||
source:
|
source:
|
||||||
enabled: false
|
enabled: false
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ RUN set -ex \
|
|||||||
&& apk add git build-base \
|
&& apk add git build-base \
|
||||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||||
&& go build -v -trimpath -tags with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api,with_acme \
|
&& go build -v -trimpath -tags with_quic,with_wireguard,with_reality_server,with_acme \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
|
|||||||
5
LICENSE
5
LICENSE
@@ -11,7 +11,4 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
In addition, no derivative work may use the name or imply association
|
|
||||||
with this application without prior consent.
|
|
||||||
13
Makefile
13
Makefile
@@ -77,20 +77,13 @@ test_stdio:
|
|||||||
go mod tidy && \
|
go mod tidy && \
|
||||||
go test -v -tags "$(TAGS_TEST),force_stdio" .
|
go test -v -tags "$(TAGS_TEST),force_stdio" .
|
||||||
|
|
||||||
android:
|
|
||||||
go run ./cmd/internal/build_libbox -target android
|
|
||||||
|
|
||||||
ios:
|
|
||||||
go run ./cmd/internal/build_libbox -target ios
|
|
||||||
|
|
||||||
lib:
|
lib:
|
||||||
go run ./cmd/internal/build_libbox -target android
|
go run ./cmd/internal/build_libbox
|
||||||
go run ./cmd/internal/build_libbox -target ios
|
|
||||||
|
|
||||||
lib_install:
|
lib_install:
|
||||||
go get -v -d
|
go get -v -d
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230413023804-244d7ff07035
|
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20221130124640-349ebaa752ca
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230413023804-244d7ff07035
|
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20221130124640-349ebaa752ca
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin dist sing-box
|
rm -rf bin dist sing-box
|
||||||
|
|||||||
@@ -8,10 +8,6 @@ The universal proxy platform.
|
|||||||
|
|
||||||
https://sing-box.sagernet.org
|
https://sing-box.sagernet.org
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
https://community.sagernet.org/c/sing-box/
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -29,7 +25,4 @@ GNU General Public License for more details.
|
|||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
In addition, no derivative work may use the name or imply association
|
|
||||||
with this application without prior consent.
|
|
||||||
```
|
```
|
||||||
@@ -13,7 +13,6 @@ type ClashServer interface {
|
|||||||
PreStarter
|
PreStarter
|
||||||
Mode() string
|
Mode() string
|
||||||
StoreSelected() bool
|
StoreSelected() bool
|
||||||
StoreFakeIP() bool
|
|
||||||
CacheFile() ClashCacheFile
|
CacheFile() ClashCacheFile
|
||||||
HistoryStorage() *urltest.HistoryStorage
|
HistoryStorage() *urltest.HistoryStorage
|
||||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
||||||
@@ -23,7 +22,6 @@ type ClashServer interface {
|
|||||||
type ClashCacheFile interface {
|
type ClashCacheFile interface {
|
||||||
LoadSelected(group string) string
|
LoadSelected(group string) string
|
||||||
StoreSelected(group string, selected string) error
|
StoreSelected(group string, selected string) error
|
||||||
FakeIPStorage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tracker interface {
|
type Tracker interface {
|
||||||
@@ -35,11 +33,6 @@ type OutboundGroup interface {
|
|||||||
All() []string
|
All() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type URLTestGroup interface {
|
|
||||||
OutboundGroup
|
|
||||||
URLTest(ctx context.Context, url string) (map[string]uint16, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func OutboundTag(detour Outbound) string {
|
func OutboundTag(detour Outbound) string {
|
||||||
if group, isGroup := detour.(OutboundGroup); isGroup {
|
if group, isGroup := detour.(OutboundGroup); isGroup {
|
||||||
return group.Now()
|
return group.Now()
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FakeIPStore interface {
|
|
||||||
Service
|
|
||||||
Contains(address netip.Addr) bool
|
|
||||||
Create(domain string, strategy dns.DomainStrategy) (netip.Addr, error)
|
|
||||||
Lookup(address netip.Addr) (string, bool)
|
|
||||||
Reset() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type FakeIPStorage interface {
|
|
||||||
FakeIPMetadata() *FakeIPMetadata
|
|
||||||
FakeIPSaveMetadata(metadata *FakeIPMetadata) error
|
|
||||||
FakeIPStore(address netip.Addr, domain string) error
|
|
||||||
FakeIPLoad(address netip.Addr) (string, bool)
|
|
||||||
FakeIPReset() error
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FakeIPMetadata struct {
|
|
||||||
Inet4Range netip.Prefix
|
|
||||||
Inet6Range netip.Prefix
|
|
||||||
Inet4Current netip.Addr
|
|
||||||
Inet6Current netip.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FakeIPMetadata) MarshalBinary() (data []byte, err error) {
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
for _, marshaler := range []encoding.BinaryMarshaler{m.Inet4Range, m.Inet6Range, m.Inet4Current, m.Inet6Current} {
|
|
||||||
data, err = marshaler.MarshalBinary()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
common.Must(binary.Write(&buffer, binary.BigEndian, uint16(len(data))))
|
|
||||||
buffer.Write(data)
|
|
||||||
}
|
|
||||||
data = buffer.Bytes()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *FakeIPMetadata) UnmarshalBinary(data []byte) error {
|
|
||||||
reader := bytes.NewReader(data)
|
|
||||||
for _, unmarshaler := range []encoding.BinaryUnmarshaler{&m.Inet4Range, &m.Inet6Range, &m.Inet4Current, &m.Inet6Current} {
|
|
||||||
var length uint16
|
|
||||||
common.Must(binary.Read(reader, binary.BigEndian, &length))
|
|
||||||
element := make([]byte, length)
|
|
||||||
_, err := io.ReadFull(reader, element)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = unmarshaler.UnmarshalBinary(element)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing-tun"
|
tun "github.com/sagernet/sing-tun"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ type Router interface {
|
|||||||
Outbound(tag string) (Outbound, bool)
|
Outbound(tag string) (Outbound, bool)
|
||||||
DefaultOutbound(network string) Outbound
|
DefaultOutbound(network string) Outbound
|
||||||
|
|
||||||
FakeIPStore() FakeIPStore
|
|
||||||
|
|
||||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) tun.RouteAction
|
RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) tun.RouteAction
|
||||||
@@ -37,7 +35,6 @@ type Router interface {
|
|||||||
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
||||||
|
|
||||||
InterfaceFinder() control.InterfaceFinder
|
InterfaceFinder() control.InterfaceFinder
|
||||||
UpdateInterfaces() error
|
|
||||||
DefaultInterface() string
|
DefaultInterface() string
|
||||||
AutoDetectInterface() bool
|
AutoDetectInterface() bool
|
||||||
AutoDetectInterfaceFunc() control.Func
|
AutoDetectInterfaceFunc() control.Func
|
||||||
@@ -84,7 +81,6 @@ type Rule interface {
|
|||||||
type DNSRule interface {
|
type DNSRule interface {
|
||||||
Rule
|
Rule
|
||||||
DisableCache() bool
|
DisableCache() bool
|
||||||
RewriteTTL() *uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type IPRule interface {
|
type IPRule interface {
|
||||||
|
|||||||
150
box.go
150
box.go
@@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/inbound"
|
"github.com/sagernet/sing-box/inbound"
|
||||||
@@ -30,25 +31,18 @@ type Box struct {
|
|||||||
outbounds []adapter.Outbound
|
outbounds []adapter.Outbound
|
||||||
logFactory log.Factory
|
logFactory log.Factory
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
|
logFile *os.File
|
||||||
preServices map[string]adapter.Service
|
preServices map[string]adapter.Service
|
||||||
postServices map[string]adapter.Service
|
postServices map[string]adapter.Service
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
func New(ctx context.Context, options option.Options, platformInterface platform.Interface) (*Box, error) {
|
||||||
option.Options
|
|
||||||
Context context.Context
|
|
||||||
PlatformInterface platform.Interface
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(options Options) (*Box, error) {
|
|
||||||
ctx := options.Context
|
|
||||||
if ctx == nil {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
createdAt := time.Now()
|
createdAt := time.Now()
|
||||||
|
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||||
|
|
||||||
var needClashAPI bool
|
var needClashAPI bool
|
||||||
var needV2RayAPI bool
|
var needV2RayAPI bool
|
||||||
if experimentalOptions.ClashAPI != nil && experimentalOptions.ClashAPI.ExternalController != "" {
|
if experimentalOptions.ClashAPI != nil && experimentalOptions.ClashAPI.ExternalController != "" {
|
||||||
@@ -57,21 +51,60 @@ func New(options Options) (*Box, error) {
|
|||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
var defaultLogWriter io.Writer
|
|
||||||
if options.PlatformInterface != nil {
|
logOptions := common.PtrValueOrDefault(options.Log)
|
||||||
defaultLogWriter = io.Discard
|
|
||||||
}
|
var logFactory log.Factory
|
||||||
logFactory, err := log.New(log.Options{
|
var observableLogFactory log.ObservableFactory
|
||||||
Context: ctx,
|
var logFile *os.File
|
||||||
Options: common.PtrValueOrDefault(options.Log),
|
var logWriter io.Writer
|
||||||
Observable: needClashAPI,
|
if logOptions.Disabled {
|
||||||
DefaultWriter: defaultLogWriter,
|
observableLogFactory = log.NewNOPFactory()
|
||||||
BaseTime: createdAt,
|
logFactory = observableLogFactory
|
||||||
PlatformWriter: options.PlatformInterface,
|
} else {
|
||||||
})
|
switch logOptions.Output {
|
||||||
if err != nil {
|
case "":
|
||||||
return nil, E.Cause(err, "create log factory")
|
if platformInterface != nil {
|
||||||
|
logWriter = io.Discard
|
||||||
|
} else {
|
||||||
|
logWriter = os.Stdout
|
||||||
|
}
|
||||||
|
case "stderr":
|
||||||
|
logWriter = os.Stderr
|
||||||
|
case "stdout":
|
||||||
|
logWriter = os.Stdout
|
||||||
|
default:
|
||||||
|
var err error
|
||||||
|
logFile, err = os.OpenFile(C.BasePath(logOptions.Output), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logWriter = logFile
|
||||||
|
}
|
||||||
|
logFormatter := log.Formatter{
|
||||||
|
BaseTime: createdAt,
|
||||||
|
DisableColors: logOptions.DisableColor || logFile != nil,
|
||||||
|
DisableTimestamp: !logOptions.Timestamp && logFile != nil,
|
||||||
|
FullTimestamp: logOptions.Timestamp,
|
||||||
|
TimestampFormat: "-0700 2006-01-02 15:04:05",
|
||||||
|
}
|
||||||
|
if needClashAPI {
|
||||||
|
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter, platformInterface)
|
||||||
|
logFactory = observableLogFactory
|
||||||
|
} else {
|
||||||
|
logFactory = log.NewFactory(logFormatter, logWriter, platformInterface)
|
||||||
|
}
|
||||||
|
if logOptions.Level != "" {
|
||||||
|
logLevel, err := log.ParseLevel(logOptions.Level)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse log level")
|
||||||
|
}
|
||||||
|
logFactory.SetLevel(logLevel)
|
||||||
|
} else {
|
||||||
|
logFactory.SetLevel(log.LevelTrace)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
router, err := route.NewRouter(
|
router, err := route.NewRouter(
|
||||||
ctx,
|
ctx,
|
||||||
logFactory,
|
logFactory,
|
||||||
@@ -79,7 +112,7 @@ func New(options Options) (*Box, error) {
|
|||||||
common.PtrValueOrDefault(options.DNS),
|
common.PtrValueOrDefault(options.DNS),
|
||||||
common.PtrValueOrDefault(options.NTP),
|
common.PtrValueOrDefault(options.NTP),
|
||||||
options.Inbounds,
|
options.Inbounds,
|
||||||
options.PlatformInterface,
|
platformInterface,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse route options")
|
return nil, E.Cause(err, "parse route options")
|
||||||
@@ -99,7 +132,7 @@ func New(options Options) (*Box, error) {
|
|||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
inboundOptions,
|
inboundOptions,
|
||||||
options.PlatformInterface,
|
platformInterface,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||||
@@ -118,7 +151,6 @@ func New(options Options) (*Box, error) {
|
|||||||
ctx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
|
||||||
outboundOptions)
|
outboundOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse outbound[", i, "]")
|
return nil, E.Cause(err, "parse outbound[", i, "]")
|
||||||
@@ -126,7 +158,7 @@ func New(options Options) (*Box, error) {
|
|||||||
outbounds = append(outbounds, out)
|
outbounds = append(outbounds, out)
|
||||||
}
|
}
|
||||||
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
||||||
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
|
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), option.Outbound{Type: "direct", Tag: "default"})
|
||||||
common.Must(oErr)
|
common.Must(oErr)
|
||||||
outbounds = append(outbounds, out)
|
outbounds = append(outbounds, out)
|
||||||
return out
|
return out
|
||||||
@@ -134,16 +166,10 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if options.PlatformInterface != nil {
|
|
||||||
err = options.PlatformInterface.Initialize(ctx, router)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initialize platform interface")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preServices := make(map[string]adapter.Service)
|
preServices := make(map[string]adapter.Service)
|
||||||
postServices := make(map[string]adapter.Service)
|
postServices := make(map[string]adapter.Service)
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(options.Experimental.ClashAPI))
|
clashServer, err := experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create clash api server")
|
return nil, E.Cause(err, "create clash api server")
|
||||||
}
|
}
|
||||||
@@ -165,6 +191,7 @@ func New(options Options) (*Box, error) {
|
|||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
logFactory: logFactory,
|
logFactory: logFactory,
|
||||||
logger: logFactory.Logger(),
|
logger: logFactory.Logger(),
|
||||||
|
logFile: logFile,
|
||||||
preServices: preServices,
|
preServices: preServices,
|
||||||
postServices: postServices,
|
postServices: postServices,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
@@ -211,21 +238,21 @@ func (s *Box) Start() error {
|
|||||||
|
|
||||||
func (s *Box) preStart() error {
|
func (s *Box) preStart() error {
|
||||||
for serviceName, service := range s.preServices {
|
for serviceName, service := range s.preServices {
|
||||||
s.logger.Trace("pre-start ", serviceName)
|
s.logger.Trace("pre-starting ", serviceName)
|
||||||
err := adapter.PreStart(service)
|
err := adapter.PreStart(service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "pre-starting ", serviceName)
|
return E.Cause(err, "pre-start ", serviceName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, out := range s.outbounds {
|
for i, out := range s.outbounds {
|
||||||
var tag string
|
|
||||||
if out.Tag() == "" {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
} else {
|
|
||||||
tag = out.Tag()
|
|
||||||
}
|
|
||||||
if starter, isStarter := out.(common.Starter); isStarter {
|
if starter, isStarter := out.(common.Starter); isStarter {
|
||||||
s.logger.Trace("initializing outbound/", out.Type(), "[", tag, "]")
|
var tag string
|
||||||
|
if out.Tag() == "" {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
tag = out.Tag()
|
||||||
|
}
|
||||||
|
s.logger.Trace("initializing outbound ", tag)
|
||||||
err := starter.Start()
|
err := starter.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
|
return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
|
||||||
@@ -254,17 +281,17 @@ func (s *Box) start() error {
|
|||||||
} else {
|
} else {
|
||||||
tag = in.Tag()
|
tag = in.Tag()
|
||||||
}
|
}
|
||||||
s.logger.Trace("initializing inbound/", in.Type(), "[", tag, "]")
|
s.logger.Trace("initializing inbound ", tag)
|
||||||
err = in.Start()
|
err = in.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for serviceName, service := range s.postServices {
|
for serviceName, service := range s.postServices {
|
||||||
s.logger.Trace("starting ", service)
|
s.logger.Trace("start ", serviceName)
|
||||||
err = service.Start()
|
err = service.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start ", serviceName)
|
return E.Cause(err, "starting ", serviceName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -279,21 +306,33 @@ func (s *Box) Close() error {
|
|||||||
}
|
}
|
||||||
var errors error
|
var errors error
|
||||||
for serviceName, service := range s.postServices {
|
for serviceName, service := range s.postServices {
|
||||||
s.logger.Trace("closing ", serviceName)
|
|
||||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||||
|
s.logger.Trace("closing ", serviceName)
|
||||||
return E.Cause(err, "close ", serviceName)
|
return E.Cause(err, "close ", serviceName)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for i, in := range s.inbounds {
|
for i, in := range s.inbounds {
|
||||||
s.logger.Trace("closing inbound/", in.Type(), "[", i, "]")
|
var tag string
|
||||||
|
if in.Tag() == "" {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
tag = in.Tag()
|
||||||
|
}
|
||||||
|
s.logger.Trace("closing inbound ", tag)
|
||||||
errors = E.Append(errors, in.Close(), func(err error) error {
|
errors = E.Append(errors, in.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for i, out := range s.outbounds {
|
for i, out := range s.outbounds {
|
||||||
s.logger.Trace("closing outbound/", out.Type(), "[", i, "]")
|
var tag string
|
||||||
|
if out.Tag() == "" {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
tag = out.Tag()
|
||||||
|
}
|
||||||
|
s.logger.Trace("closing outbound ", tag)
|
||||||
errors = E.Append(errors, common.Close(out), func(err error) error {
|
errors = E.Append(errors, common.Close(out), func(err error) error {
|
||||||
return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
|
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
s.logger.Trace("closing router")
|
s.logger.Trace("closing router")
|
||||||
@@ -308,12 +347,17 @@ func (s *Box) Close() error {
|
|||||||
return E.Cause(err, "close ", serviceName)
|
return E.Cause(err, "close ", serviceName)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
s.logger.Trace("closing log factory")
|
s.logger.Trace("closing logger")
|
||||||
if err := common.Close(s.logFactory); err != nil {
|
if err := common.Close(s.logFactory); err != nil {
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
errors = E.Append(errors, err, func(err error) error {
|
||||||
return E.Cause(err, "close log factory")
|
return E.Cause(err, "close log factory")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if s.logFile != nil {
|
||||||
|
errors = E.Append(errors, s.logFile.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close log file")
|
||||||
|
})
|
||||||
|
}
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,7 @@ func check() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(ctx, options, nil)
|
||||||
Context: ctx,
|
|
||||||
Options: options,
|
|
||||||
})
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
instance.Close()
|
instance.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/common/badjsonmerge"
|
"github.com/sagernet/sing-box/common/badjsonmerge"
|
||||||
@@ -128,10 +127,7 @@ func create() (*box.Box, context.CancelFunc, error) {
|
|||||||
options.Log.DisableColor = true
|
options.Log.DisableColor = true
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(ctx, options, nil)
|
||||||
Context: ctx,
|
|
||||||
Options: options,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancel()
|
cancel()
|
||||||
return nil, nil, E.Cause(err, "create service")
|
return nil, nil, E.Cause(err, "create service")
|
||||||
@@ -178,10 +174,7 @@ func run() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cancel()
|
cancel()
|
||||||
closeCtx, closed := context.WithCancel(context.Background())
|
|
||||||
go closeMonitor(closeCtx)
|
|
||||||
instance.Close()
|
instance.Close()
|
||||||
closed()
|
|
||||||
if osSignal != syscall.SIGHUP {
|
if osSignal != syscall.SIGHUP {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -189,13 +182,3 @@ func run() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeMonitor(ctx context.Context) {
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
log.Fatal("sing-box did not close!")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -25,7 +27,7 @@ func createPreStartedClient() (*box.Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
instance, err := box.New(box.Options{Options: options})
|
instance, err := box.New(context.Background(), options, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create service")
|
return nil, E.Cause(err, "create service")
|
||||||
}
|
}
|
||||||
|
|||||||
44
cmd/sing-box/debug.go
Normal file
44
cmd/sing-box/debug.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
//go:build debug
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
_ "net/http/pprof"
|
||||||
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/badjson"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
http.HandleFunc("/debug/gc", func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
writer.WriteHeader(http.StatusNoContent)
|
||||||
|
go debug.FreeOSMemory()
|
||||||
|
})
|
||||||
|
http.HandleFunc("/debug/memory", func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
var memStats runtime.MemStats
|
||||||
|
runtime.ReadMemStats(&memStats)
|
||||||
|
|
||||||
|
var memObject badjson.JSONObject
|
||||||
|
memObject.Put("heap", humanize.IBytes(memStats.HeapInuse))
|
||||||
|
memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
|
||||||
|
memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||||
|
memObject.Put("goroutines", runtime.NumGoroutine())
|
||||||
|
memObject.Put("rss", rusageMaxRSS())
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(writer)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
encoder.Encode(memObject)
|
||||||
|
})
|
||||||
|
go func() {
|
||||||
|
err := http.ListenAndServe("0.0.0.0:8964", nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
package box
|
//go:build debug
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//go:build !linux
|
//go:build debug && !linux
|
||||||
|
|
||||||
package box
|
package main
|
||||||
|
|
||||||
func rusageMaxRSS() float64 {
|
func rusageMaxRSS() float64 {
|
||||||
return -1
|
return -1
|
||||||
@@ -21,7 +21,7 @@ func TestMergeJSON(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: C.RuleTypeDefault,
|
Type: C.RuleTypeDefault,
|
||||||
DefaultOptions: option.DefaultRule{
|
DefaultOptions: option.DefaultRule{
|
||||||
Network: []string{N.NetworkTCP},
|
Network: N.NetworkTCP,
|
||||||
Outbound: "direct",
|
Outbound: "direct",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -42,7 +42,7 @@ func TestMergeJSON(t *testing.T) {
|
|||||||
{
|
{
|
||||||
Type: C.RuleTypeDefault,
|
Type: C.RuleTypeDefault,
|
||||||
DefaultOptions: option.DefaultRule{
|
DefaultOptions: option.DefaultRule{
|
||||||
Network: []string{N.NetworkUDP},
|
Network: N.NetworkUDP,
|
||||||
Outbound: "direct",
|
Outbound: "direct",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build go1.20 && !go1.21
|
//go:build go1.19 && !go1.20
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
@@ -14,60 +14,39 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
*tls.Conn
|
*tls.Conn
|
||||||
writer N.ExtendedWriter
|
writer N.ExtendedWriter
|
||||||
isHandshakeComplete *atomic.Bool
|
activeCall *int32
|
||||||
activeCall *atomic.Int32
|
closeNotifySent *bool
|
||||||
closeNotifySent *bool
|
version *uint16
|
||||||
version *uint16
|
rand io.Reader
|
||||||
rand io.Reader
|
halfAccess *sync.Mutex
|
||||||
halfAccess *sync.Mutex
|
halfError *error
|
||||||
halfError *error
|
cipher cipher.AEAD
|
||||||
cipher cipher.AEAD
|
explicitNonceLen int
|
||||||
explicitNonceLen int
|
halfPtr uintptr
|
||||||
halfPtr uintptr
|
halfSeq []byte
|
||||||
halfSeq []byte
|
halfScratchBuf []byte
|
||||||
halfScratchBuf []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TryCreate(conn aTLS.Conn) aTLS.Conn {
|
func Create(conn *tls.Conn) (TLSConn, error) {
|
||||||
tlsConn, ok := conn.(*tls.Conn)
|
if !handshakeComplete(conn) {
|
||||||
if !ok {
|
|
||||||
return conn
|
|
||||||
}
|
|
||||||
badConn, err := Create(tlsConn)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("initialize badtls: ", err)
|
|
||||||
return conn
|
|
||||||
}
|
|
||||||
return badConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func Create(conn *tls.Conn) (aTLS.Conn, error) {
|
|
||||||
rawConn := reflect.Indirect(reflect.ValueOf(conn))
|
|
||||||
rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete")
|
|
||||||
if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {
|
|
||||||
return nil, E.New("badtls: invalid isHandshakeComplete")
|
|
||||||
}
|
|
||||||
isHandshakeComplete := (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))
|
|
||||||
if !isHandshakeComplete.Load() {
|
|
||||||
return nil, E.New("handshake not finished")
|
return nil, E.New("handshake not finished")
|
||||||
}
|
}
|
||||||
|
rawConn := reflect.Indirect(reflect.ValueOf(conn))
|
||||||
rawActiveCall := rawConn.FieldByName("activeCall")
|
rawActiveCall := rawConn.FieldByName("activeCall")
|
||||||
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {
|
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Int32 {
|
||||||
return nil, E.New("badtls: invalid active call")
|
return nil, E.New("badtls: invalid active call")
|
||||||
}
|
}
|
||||||
activeCall := (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
|
activeCall := (*int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
|
||||||
rawHalfConn := rawConn.FieldByName("out")
|
rawHalfConn := rawConn.FieldByName("out")
|
||||||
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
|
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
|
||||||
return nil, E.New("badtls: invalid half conn")
|
return nil, E.New("badtls: invalid half conn")
|
||||||
@@ -129,20 +108,19 @@ func Create(conn *tls.Conn) (aTLS.Conn, error) {
|
|||||||
}
|
}
|
||||||
halfScratchBuf := rawHalfScratchBuf.Bytes()
|
halfScratchBuf := rawHalfScratchBuf.Bytes()
|
||||||
return &Conn{
|
return &Conn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
writer: bufio.NewExtendedWriter(conn.NetConn()),
|
writer: bufio.NewExtendedWriter(conn.NetConn()),
|
||||||
isHandshakeComplete: isHandshakeComplete,
|
activeCall: activeCall,
|
||||||
activeCall: activeCall,
|
closeNotifySent: closeNotifySent,
|
||||||
closeNotifySent: closeNotifySent,
|
version: version,
|
||||||
version: version,
|
halfAccess: halfAccess,
|
||||||
halfAccess: halfAccess,
|
halfError: halfError,
|
||||||
halfError: halfError,
|
cipher: aeadCipher,
|
||||||
cipher: aeadCipher,
|
explicitNonceLen: explicitNonceLen,
|
||||||
explicitNonceLen: explicitNonceLen,
|
rand: randReader,
|
||||||
rand: randReader,
|
halfPtr: rawHalfConn.UnsafeAddr(),
|
||||||
halfPtr: rawHalfConn.UnsafeAddr(),
|
halfSeq: halfSeq,
|
||||||
halfSeq: halfSeq,
|
halfScratchBuf: halfScratchBuf,
|
||||||
halfScratchBuf: halfScratchBuf,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,15 +130,15 @@ func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
|||||||
return common.Error(c.Write(buffer.Bytes()))
|
return common.Error(c.Write(buffer.Bytes()))
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
x := c.activeCall.Load()
|
x := atomic.LoadInt32(c.activeCall)
|
||||||
if x&1 != 0 {
|
if x&1 != 0 {
|
||||||
return net.ErrClosed
|
return net.ErrClosed
|
||||||
}
|
}
|
||||||
if c.activeCall.CompareAndSwap(x, x+2) {
|
if atomic.CompareAndSwapInt32(c.activeCall, x, x+2) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer c.activeCall.Add(-2)
|
defer atomic.AddInt32(c.activeCall, -2)
|
||||||
c.halfAccess.Lock()
|
c.halfAccess.Lock()
|
||||||
defer c.halfAccess.Unlock()
|
defer c.halfAccess.Unlock()
|
||||||
if err := *c.halfError; err != nil {
|
if err := *c.halfError; err != nil {
|
||||||
@@ -208,7 +186,6 @@ func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
|||||||
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead()))
|
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead()))
|
||||||
}
|
}
|
||||||
incSeq(c.halfPtr)
|
incSeq(c.halfPtr)
|
||||||
log.Trace("badtls write ", buffer.Len())
|
|
||||||
return c.writer.WriteBuffer(buffer)
|
return c.writer.WriteBuffer(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build !go1.19 || go1.21
|
//go:build !go1.19 || go1.20
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
|
|||||||
13
common/badtls/conn.go
Normal file
13
common/badtls/conn.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package badtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TLSConn interface {
|
||||||
|
net.Conn
|
||||||
|
HandshakeContext(ctx context.Context) error
|
||||||
|
ConnectionState() tls.ConnectionState
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
//go:build go1.20 && !go.1.21
|
//go:build go1.19 && !go.1.20
|
||||||
|
|
||||||
package badtls
|
package badtls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"reflect"
|
"reflect"
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
)
|
)
|
||||||
@@ -15,6 +16,9 @@ const (
|
|||||||
//go:linkname errShutdown crypto/tls.errShutdown
|
//go:linkname errShutdown crypto/tls.errShutdown
|
||||||
var errShutdown error
|
var errShutdown error
|
||||||
|
|
||||||
|
//go:linkname handshakeComplete crypto/tls.(*Conn).handshakeComplete
|
||||||
|
func handshakeComplete(conn *tls.Conn) bool
|
||||||
|
|
||||||
//go:linkname incSeq crypto/tls.(*halfConn).incSeq
|
//go:linkname incSeq crypto/tls.(*halfConn).incSeq
|
||||||
func incSeq(conn uintptr)
|
func incSeq(conn uintptr)
|
||||||
|
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
package badversion
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Version struct {
|
|
||||||
Major int
|
|
||||||
Minor int
|
|
||||||
Patch int
|
|
||||||
PreReleaseIdentifier string
|
|
||||||
PreReleaseVersion int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) After(anotherVersion Version) bool {
|
|
||||||
if v.Major > anotherVersion.Major {
|
|
||||||
return true
|
|
||||||
} else if v.Major < anotherVersion.Major {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.Minor > anotherVersion.Minor {
|
|
||||||
return true
|
|
||||||
} else if v.Minor < anotherVersion.Minor {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.Patch > anotherVersion.Patch {
|
|
||||||
return true
|
|
||||||
} else if v.Patch < anotherVersion.Patch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.PreReleaseIdentifier == "" && anotherVersion.PreReleaseIdentifier != "" {
|
|
||||||
return true
|
|
||||||
} else if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.PreReleaseIdentifier != "" && anotherVersion.PreReleaseIdentifier != "" {
|
|
||||||
if v.PreReleaseIdentifier == "beta" && anotherVersion.PreReleaseIdentifier == "alpha" {
|
|
||||||
return true
|
|
||||||
} else if v.PreReleaseIdentifier == "alpha" && anotherVersion.PreReleaseIdentifier == "beta" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if v.PreReleaseVersion > anotherVersion.PreReleaseVersion {
|
|
||||||
return true
|
|
||||||
} else if v.PreReleaseVersion < anotherVersion.PreReleaseVersion {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) String() string {
|
|
||||||
version := F.ToString(v.Major, ".", v.Minor, ".", v.Patch)
|
|
||||||
if v.PreReleaseIdentifier != "" {
|
|
||||||
version = F.ToString(version, "-", v.PreReleaseIdentifier, ".", v.PreReleaseVersion)
|
|
||||||
}
|
|
||||||
return version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v Version) BadString() string {
|
|
||||||
version := F.ToString(v.Major, ".", v.Minor)
|
|
||||||
if v.Patch > 0 {
|
|
||||||
version = F.ToString(version, ".", v.Patch)
|
|
||||||
}
|
|
||||||
if v.PreReleaseIdentifier != "" {
|
|
||||||
version = F.ToString(version, "-", v.PreReleaseIdentifier)
|
|
||||||
if v.PreReleaseVersion > 0 {
|
|
||||||
version = F.ToString(version, v.PreReleaseVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return version
|
|
||||||
}
|
|
||||||
|
|
||||||
func Parse(versionName string) (version Version) {
|
|
||||||
if strings.HasPrefix(versionName, "v") {
|
|
||||||
versionName = versionName[1:]
|
|
||||||
}
|
|
||||||
if strings.Contains(versionName, "-") {
|
|
||||||
parts := strings.Split(versionName, "-")
|
|
||||||
versionName = parts[0]
|
|
||||||
identifier := parts[1]
|
|
||||||
if strings.Contains(identifier, ".") {
|
|
||||||
identifierParts := strings.Split(identifier, ".")
|
|
||||||
version.PreReleaseIdentifier = identifierParts[0]
|
|
||||||
if len(identifierParts) >= 2 {
|
|
||||||
version.PreReleaseVersion, _ = strconv.Atoi(identifierParts[1])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if strings.HasPrefix(identifier, "alpha") {
|
|
||||||
version.PreReleaseIdentifier = "alpha"
|
|
||||||
version.PreReleaseVersion, _ = strconv.Atoi(identifier[5:])
|
|
||||||
} else if strings.HasPrefix(identifier, "beta") {
|
|
||||||
version.PreReleaseIdentifier = "beta"
|
|
||||||
version.PreReleaseVersion, _ = strconv.Atoi(identifier[4:])
|
|
||||||
} else {
|
|
||||||
version.PreReleaseIdentifier = identifier
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
versionElements := strings.Split(versionName, ".")
|
|
||||||
versionLen := len(versionElements)
|
|
||||||
if versionLen >= 1 {
|
|
||||||
version.Major, _ = strconv.Atoi(versionElements[0])
|
|
||||||
}
|
|
||||||
if versionLen >= 2 {
|
|
||||||
version.Minor, _ = strconv.Atoi(versionElements[1])
|
|
||||||
}
|
|
||||||
if versionLen >= 3 {
|
|
||||||
version.Patch, _ = strconv.Atoi(versionElements[2])
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package badversion
|
|
||||||
|
|
||||||
import "github.com/sagernet/sing-box/common/json"
|
|
||||||
|
|
||||||
func (v Version) MarshalJSON() ([]byte, error) {
|
|
||||||
return json.Marshal(v.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Version) UnmarshalJSON(data []byte) error {
|
|
||||||
var version string
|
|
||||||
err := json.Unmarshal(data, &version)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*v = Parse(version)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package badversion
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCompareVersion(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String())
|
|
||||||
require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString())
|
|
||||||
require.True(t, Parse("1.3.0").After(Parse("1.3-beta1")))
|
|
||||||
require.True(t, Parse("1.3.0").After(Parse("1.3.0-beta1")))
|
|
||||||
require.True(t, Parse("1.3.0-beta1").After(Parse("1.3.0-alpha1")))
|
|
||||||
require.True(t, Parse("1.3.1").After(Parse("1.3.0")))
|
|
||||||
require.True(t, Parse("1.4").After(Parse("1.3")))
|
|
||||||
}
|
|
||||||
48
common/canceler/instance.go
Normal file
48
common/canceler/instance.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package canceler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Instance struct {
|
||||||
|
ctx context.Context
|
||||||
|
cancelFunc context.CancelFunc
|
||||||
|
timer *time.Timer
|
||||||
|
timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(ctx context.Context, cancelFunc context.CancelFunc, timeout time.Duration) *Instance {
|
||||||
|
instance := &Instance{
|
||||||
|
ctx,
|
||||||
|
cancelFunc,
|
||||||
|
time.NewTimer(timeout),
|
||||||
|
timeout,
|
||||||
|
}
|
||||||
|
go instance.wait()
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) Update() bool {
|
||||||
|
if !i.timer.Stop() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !i.timer.Reset(i.timeout) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) wait() {
|
||||||
|
select {
|
||||||
|
case <-i.timer.C:
|
||||||
|
case <-i.ctx.Done():
|
||||||
|
}
|
||||||
|
i.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) Close() error {
|
||||||
|
i.timer.Stop()
|
||||||
|
i.cancelFunc()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
49
common/canceler/packet.go
Normal file
49
common/canceler/packet.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package canceler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PacketConn struct {
|
||||||
|
N.PacketConn
|
||||||
|
instance *Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacketConn(ctx context.Context, conn N.PacketConn, timeout time.Duration) (context.Context, N.PacketConn) {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
instance := New(ctx, cancel, timeout)
|
||||||
|
return ctx, &PacketConn{conn, instance}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
destination, err = c.PacketConn.ReadPacket(buffer)
|
||||||
|
if err == nil {
|
||||||
|
c.instance.Update()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
err := c.PacketConn.WritePacket(buffer, destination)
|
||||||
|
if err == nil {
|
||||||
|
c.instance.Update()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Close() error {
|
||||||
|
return common.Close(
|
||||||
|
c.PacketConn,
|
||||||
|
c.instance,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Upstream() any {
|
||||||
|
return c.PacketConn
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ type Conn struct {
|
|||||||
element *list.Element[io.Closer]
|
element *list.Element[io.Closer]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConn(conn net.Conn) (net.Conn, error) {
|
func NewConn(conn net.Conn) (*Conn, error) {
|
||||||
connAccess.Lock()
|
connAccess.Lock()
|
||||||
element := openConnection.PushBack(conn)
|
element := openConnection.PushBack(conn)
|
||||||
connAccess.Unlock()
|
connAccess.Unlock()
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type PacketConn struct {
|
|||||||
element *list.Element[io.Closer]
|
element *list.Element[io.Closer]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
|
func NewPacketConn(conn net.PacketConn) (*PacketConn, error) {
|
||||||
connAccess.Lock()
|
connAccess.Lock()
|
||||||
element := openConnection.PushBack(conn)
|
element := openConnection.PushBack(conn)
|
||||||
connAccess.Unlock()
|
connAccess.Unlock()
|
||||||
|
|||||||
@@ -14,16 +14,10 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Count() int {
|
func Count() int {
|
||||||
if !Enabled {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return openConnection.Len()
|
return openConnection.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
func List() []io.Closer {
|
func List() []io.Closer {
|
||||||
if !Enabled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
connAccess.RLock()
|
connAccess.RLock()
|
||||||
defer connAccess.RUnlock()
|
defer connAccess.RUnlock()
|
||||||
connList := make([]io.Closer, 0, openConnection.Len())
|
connList := make([]io.Closer, 0, openConnection.Len())
|
||||||
@@ -34,9 +28,6 @@ func List() []io.Closer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Close() {
|
func Close() {
|
||||||
if !Enabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
connAccess.Lock()
|
connAccess.Lock()
|
||||||
defer connAccess.Unlock()
|
defer connAccess.Unlock()
|
||||||
for element := openConnection.Front(); element != nil; element = element.Next() {
|
for element := openConnection.Front(); element != nil; element = element.Next() {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
|
"github.com/sagernet/sing-box/common/warning"
|
||||||
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"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
@@ -16,6 +17,41 @@ import (
|
|||||||
"github.com/sagernet/tfo-go"
|
"github.com/sagernet/tfo-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var warnBindInterfaceOnUnsupportedPlatform = warning.New(
|
||||||
|
func() bool {
|
||||||
|
return !(C.IsLinux || C.IsWindows || C.IsDarwin)
|
||||||
|
},
|
||||||
|
"outbound option `bind_interface` is only supported on Linux and Windows",
|
||||||
|
)
|
||||||
|
|
||||||
|
var warnRoutingMarkOnUnsupportedPlatform = warning.New(
|
||||||
|
func() bool {
|
||||||
|
return !C.IsLinux
|
||||||
|
},
|
||||||
|
"outbound option `routing_mark` is only supported on Linux",
|
||||||
|
)
|
||||||
|
|
||||||
|
var warnReuseAdderOnUnsupportedPlatform = warning.New(
|
||||||
|
func() bool {
|
||||||
|
return !(C.IsDarwin || C.IsDragonfly || C.IsFreebsd || C.IsLinux || C.IsNetbsd || C.IsOpenbsd || C.IsSolaris || C.IsWindows)
|
||||||
|
},
|
||||||
|
"outbound option `reuse_addr` is unsupported on current platform",
|
||||||
|
)
|
||||||
|
|
||||||
|
var warnProtectPathOnNonAndroid = warning.New(
|
||||||
|
func() bool {
|
||||||
|
return !C.IsAndroid
|
||||||
|
},
|
||||||
|
"outbound option `protect_path` is only supported on Android",
|
||||||
|
)
|
||||||
|
|
||||||
|
var warnTFOOnUnsupportedPlatform = warning.New(
|
||||||
|
func() bool {
|
||||||
|
return !(C.IsDarwin || C.IsFreebsd || C.IsLinux || C.IsWindows)
|
||||||
|
},
|
||||||
|
"outbound option `tcp_fast_open` is unsupported on current platform",
|
||||||
|
)
|
||||||
|
|
||||||
type DefaultDialer struct {
|
type DefaultDialer struct {
|
||||||
dialer4 tfo.Dialer
|
dialer4 tfo.Dialer
|
||||||
dialer6 tfo.Dialer
|
dialer6 tfo.Dialer
|
||||||
@@ -30,6 +66,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
|||||||
var dialer net.Dialer
|
var dialer net.Dialer
|
||||||
var listener net.ListenConfig
|
var listener net.ListenConfig
|
||||||
if options.BindInterface != "" {
|
if options.BindInterface != "" {
|
||||||
|
warnBindInterfaceOnUnsupportedPlatform.Check()
|
||||||
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1)
|
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1)
|
||||||
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)
|
||||||
@@ -43,6 +80,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
|||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
}
|
}
|
||||||
if options.RoutingMark != 0 {
|
if options.RoutingMark != 0 {
|
||||||
|
warnRoutingMarkOnUnsupportedPlatform.Check()
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
||||||
} else if router.DefaultMark() != 0 {
|
} else if router.DefaultMark() != 0 {
|
||||||
@@ -50,9 +88,11 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
|||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
|
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
|
||||||
}
|
}
|
||||||
if options.ReuseAddr {
|
if options.ReuseAddr {
|
||||||
|
warnReuseAdderOnUnsupportedPlatform.Check()
|
||||||
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
||||||
}
|
}
|
||||||
if options.ProtectPath != "" {
|
if options.ProtectPath != "" {
|
||||||
|
warnProtectPathOnNonAndroid.Check()
|
||||||
dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
|
dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
|
||||||
listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
|
listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
|
||||||
}
|
}
|
||||||
@@ -61,6 +101,9 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
|||||||
} else {
|
} else {
|
||||||
dialer.Timeout = C.TCPTimeout
|
dialer.Timeout = C.TCPTimeout
|
||||||
}
|
}
|
||||||
|
if options.TCPFastOpen {
|
||||||
|
warnTFOOnUnsupportedPlatform.Check()
|
||||||
|
}
|
||||||
var udpFragment bool
|
var udpFragment bool
|
||||||
if options.UDPFragment != nil {
|
if options.UDPFragment != nil {
|
||||||
udpFragment = *options.UDPFragment
|
udpFragment = *options.UDPFragment
|
||||||
@@ -111,9 +154,9 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
|||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkUDP:
|
case N.NetworkUDP:
|
||||||
if !address.IsIPv6() {
|
if !address.IsIPv6() {
|
||||||
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
return d.udpDialer4.DialContext(ctx, network, address.String())
|
||||||
} else {
|
} else {
|
||||||
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
return d.udpDialer6.DialContext(ctx, network, address.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !address.IsIPv6() {
|
if !address.IsIPv6() {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
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"
|
||||||
)
|
)
|
||||||
@@ -69,11 +68,11 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conn, destinationAddress, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
|
conn, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
return NewResolvePacketConn(ctx, d.router, d.strategy, conn), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ResolveDialer) Upstream() any {
|
func (d *ResolveDialer) Upstream() any {
|
||||||
|
|||||||
84
common/dialer/resolve_conn.go
Normal file
84
common/dialer/resolve_conn.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-dns"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewResolvePacketConn(ctx context.Context, router adapter.Router, strategy dns.DomainStrategy, conn net.PacketConn) N.NetPacketConn {
|
||||||
|
if udpConn, ok := conn.(*net.UDPConn); ok {
|
||||||
|
return &ResolveUDPConn{udpConn, ctx, router, strategy}
|
||||||
|
} else {
|
||||||
|
return &ResolvePacketConn{conn, ctx, router, strategy}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResolveUDPConn struct {
|
||||||
|
*net.UDPConn
|
||||||
|
ctx context.Context
|
||||||
|
router adapter.Router
|
||||||
|
strategy dns.DomainStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ResolveUDPConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
|
||||||
|
n, addr, err := w.ReadFromUDPAddrPort(buffer.FreeBytes())
|
||||||
|
if err != nil {
|
||||||
|
return M.Socksaddr{}, err
|
||||||
|
}
|
||||||
|
buffer.Truncate(n)
|
||||||
|
return M.SocksaddrFromNetIP(addr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ResolveUDPConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
defer buffer.Release()
|
||||||
|
if destination.IsFqdn() {
|
||||||
|
addresses, err := w.router.Lookup(w.ctx, destination.Fqdn, w.strategy)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return common.Error(w.UDPConn.WriteToUDPAddrPort(buffer.Bytes(), M.SocksaddrFrom(addresses[0], destination.Port).AddrPort()))
|
||||||
|
}
|
||||||
|
return common.Error(w.UDPConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ResolveUDPConn) Upstream() any {
|
||||||
|
return w.UDPConn
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResolvePacketConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
ctx context.Context
|
||||||
|
router adapter.Router
|
||||||
|
strategy dns.DomainStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ResolvePacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
|
||||||
|
_, addr, err := buffer.ReadPacketFrom(w)
|
||||||
|
if err != nil {
|
||||||
|
return M.Socksaddr{}, err
|
||||||
|
}
|
||||||
|
return M.SocksaddrFromNet(addr), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ResolvePacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
defer buffer.Release()
|
||||||
|
if destination.IsFqdn() {
|
||||||
|
addresses, err := w.router.Lookup(w.ctx, destination.Fqdn, w.strategy)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return common.Error(w.WriteTo(buffer.Bytes(), M.SocksaddrFrom(addresses[0], destination.Port).UDPAddr()))
|
||||||
|
}
|
||||||
|
return common.Error(w.WriteTo(buffer.Bytes(), destination.UDPAddr()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *ResolvePacketConn) Upstream() any {
|
||||||
|
return w.PacketConn
|
||||||
|
}
|
||||||
@@ -124,10 +124,6 @@ func (c *slowOpenConn) LazyHeadroom() bool {
|
|||||||
return c.conn == nil
|
return c.conn == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) NeedHandshake() bool {
|
|
||||||
return c.conn == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *slowOpenConn) ReadFrom(r io.Reader) (n int64, err error) {
|
func (c *slowOpenConn) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
if c.conn != nil {
|
if c.conn != nil {
|
||||||
return bufio.Copy(c.conn, r)
|
return bufio.Copy(c.conn, r)
|
||||||
|
|||||||
@@ -28,10 +28,9 @@ type Client struct {
|
|||||||
maxConnections int
|
maxConnections int
|
||||||
minStreams int
|
minStreams int
|
||||||
maxStreams int
|
maxStreams int
|
||||||
paddingEnabled bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(ctx context.Context, dialer N.Dialer, protocol Protocol, maxConnections int, minStreams int, maxStreams int, paddingEnabled bool) (*Client, error) {
|
func NewClient(ctx context.Context, dialer N.Dialer, protocol Protocol, maxConnections int, minStreams int, maxStreams int) *Client {
|
||||||
return &Client{
|
return &Client{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
@@ -39,11 +38,10 @@ func NewClient(ctx context.Context, dialer N.Dialer, protocol Protocol, maxConne
|
|||||||
maxConnections: maxConnections,
|
maxConnections: maxConnections,
|
||||||
minStreams: minStreams,
|
minStreams: minStreams,
|
||||||
maxStreams: maxStreams,
|
maxStreams: maxStreams,
|
||||||
paddingEnabled: paddingEnabled,
|
}
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientWithOptions(ctx context.Context, dialer N.Dialer, options option.MultiplexOptions) (*Client, error) {
|
func NewClientWithOptions(ctx context.Context, dialer N.Dialer, options option.MultiplexOptions) (N.Dialer, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -54,7 +52,7 @@ func NewClientWithOptions(ctx context.Context, dialer N.Dialer, options option.M
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewClient(ctx, dialer, protocol, options.MaxConnections, options.MinStreams, options.MaxStreams, options.Padding)
|
return NewClient(ctx, dialer, protocol, options.MaxConnections, options.MinStreams, options.MaxStreams), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (c *Client) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
@@ -122,16 +120,17 @@ func (c *Client) offer() (abstractSession, error) {
|
|||||||
sessions = append(sessions, element.Value)
|
sessions = append(sessions, element.Value)
|
||||||
element = element.Next()
|
element = element.Next()
|
||||||
}
|
}
|
||||||
session := common.MinBy(common.Filter(sessions, abstractSession.CanTakeNewRequest), abstractSession.NumStreams)
|
sLen := len(sessions)
|
||||||
if session == nil {
|
if sLen == 0 {
|
||||||
return c.offerNew()
|
return c.offerNew()
|
||||||
}
|
}
|
||||||
|
session := common.MinBy(sessions, abstractSession.NumStreams)
|
||||||
numStreams := session.NumStreams()
|
numStreams := session.NumStreams()
|
||||||
if numStreams == 0 {
|
if numStreams == 0 {
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
if c.maxConnections > 0 {
|
if c.maxConnections > 0 {
|
||||||
if len(sessions) >= c.maxConnections || numStreams < c.minStreams {
|
if sLen >= c.maxConnections || numStreams < c.minStreams {
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -147,19 +146,10 @@ func (c *Client) offerNew() (abstractSession, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var version byte
|
if vectorisedWriter, isVectorised := bufio.CreateVectorisedWriter(conn); isVectorised {
|
||||||
if c.paddingEnabled {
|
conn = &vectorisedProtocolConn{protocolConn{Conn: conn, protocol: c.protocol}, vectorisedWriter}
|
||||||
version = Version1
|
|
||||||
} else {
|
} else {
|
||||||
version = Version0
|
conn = &protocolConn{Conn: conn, protocol: c.protocol}
|
||||||
}
|
|
||||||
conn = newProtocolConn(conn, Request{
|
|
||||||
Version: version,
|
|
||||||
Protocol: c.protocol,
|
|
||||||
PaddingEnabled: c.paddingEnabled,
|
|
||||||
})
|
|
||||||
if c.paddingEnabled {
|
|
||||||
conn = newPaddingConn(conn)
|
|
||||||
}
|
}
|
||||||
session, err := c.protocol.newClient(conn)
|
session, err := c.protocol.newClient(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -169,15 +159,6 @@ func (c *Client) offerNew() (abstractSession, error) {
|
|||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Reset() {
|
|
||||||
c.access.Lock()
|
|
||||||
defer c.access.Unlock()
|
|
||||||
for _, session := range c.connections.Array() {
|
|
||||||
session.Close()
|
|
||||||
}
|
|
||||||
c.connections.Init()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Close() error {
|
func (c *Client) Close() error {
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
@@ -224,7 +205,7 @@ func (c *ClientConn) Write(b []byte) (n int, err error) {
|
|||||||
Network: N.NetworkTCP,
|
Network: N.NetworkTCP,
|
||||||
Destination: c.destination,
|
Destination: c.destination,
|
||||||
}
|
}
|
||||||
_buffer := buf.StackNewSize(streamRequestLen(request) + len(b))
|
_buffer := buf.StackNewSize(requestLen(request) + len(b))
|
||||||
defer common.KeepAlive(_buffer)
|
defer common.KeepAlive(_buffer)
|
||||||
buffer := common.Dup(_buffer)
|
buffer := common.Dup(_buffer)
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
@@ -268,10 +249,6 @@ func (c *ClientConn) WriterReplaceable() bool {
|
|||||||
return c.requestWrite
|
return c.requestWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientConn) NeedAdditionalReadDeadline() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientConn) Upstream() any {
|
func (c *ClientConn) Upstream() any {
|
||||||
return c.Conn
|
return c.Conn
|
||||||
}
|
}
|
||||||
@@ -318,7 +295,7 @@ func (c *ClientPacketConn) writeRequest(payload []byte) (n int, err error) {
|
|||||||
Network: N.NetworkUDP,
|
Network: N.NetworkUDP,
|
||||||
Destination: c.destination,
|
Destination: c.destination,
|
||||||
}
|
}
|
||||||
rLen := streamRequestLen(request)
|
rLen := requestLen(request)
|
||||||
if len(payload) > 0 {
|
if len(payload) > 0 {
|
||||||
rLen += 2 + len(payload)
|
rLen += 2 + len(payload)
|
||||||
}
|
}
|
||||||
@@ -400,10 +377,6 @@ func (c *ClientPacketConn) RemoteAddr() net.Addr {
|
|||||||
return c.destination.UDPAddr()
|
return c.destination.UDPAddr()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientPacketConn) NeedAdditionalReadDeadline() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketConn) Upstream() any {
|
func (c *ClientPacketConn) Upstream() any {
|
||||||
return c.ExtendedConn
|
return c.ExtendedConn
|
||||||
}
|
}
|
||||||
@@ -440,11 +413,7 @@ func (c *ClientPacketAddrConn) ReadFrom(p []byte) (n int, addr net.Addr, err err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if destination.IsFqdn() {
|
addr = destination.UDPAddr()
|
||||||
addr = destination
|
|
||||||
} else {
|
|
||||||
addr = destination.UDPAddr()
|
|
||||||
}
|
|
||||||
var length uint16
|
var length uint16
|
||||||
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -463,7 +432,7 @@ func (c *ClientPacketAddrConn) writeRequest(payload []byte, destination M.Socksa
|
|||||||
Destination: c.destination,
|
Destination: c.destination,
|
||||||
PacketAddr: true,
|
PacketAddr: true,
|
||||||
}
|
}
|
||||||
rLen := streamRequestLen(request)
|
rLen := requestLen(request)
|
||||||
if len(payload) > 0 {
|
if len(payload) > 0 {
|
||||||
rLen += M.SocksaddrSerializer.AddrPortLen(destination) + 2 + len(payload)
|
rLen += M.SocksaddrSerializer.AddrPortLen(destination) + 2 + len(payload)
|
||||||
}
|
}
|
||||||
@@ -545,10 +514,6 @@ func (c *ClientPacketAddrConn) FrontHeadroom() int {
|
|||||||
return 2 + M.MaxSocksaddrLength
|
return 2 + M.MaxSocksaddrLength
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientPacketAddrConn) NeedAdditionalReadDeadline() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientPacketAddrConn) Upstream() any {
|
func (c *ClientPacketAddrConn) Upstream() any {
|
||||||
return c.ExtendedConn
|
return c.ExtendedConn
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,235 +0,0 @@
|
|||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/transport/v2rayhttp"
|
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const idleTimeout = 30 * time.Second
|
|
||||||
|
|
||||||
var _ abstractSession = (*H2MuxServerSession)(nil)
|
|
||||||
|
|
||||||
type H2MuxServerSession struct {
|
|
||||||
server http2.Server
|
|
||||||
active atomic.Int32
|
|
||||||
conn net.Conn
|
|
||||||
inbound chan net.Conn
|
|
||||||
done chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewH2MuxServer(conn net.Conn) *H2MuxServerSession {
|
|
||||||
session := &H2MuxServerSession{
|
|
||||||
conn: conn,
|
|
||||||
inbound: make(chan net.Conn),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
server: http2.Server{
|
|
||||||
IdleTimeout: idleTimeout,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
session.server.ServeConn(conn, &http2.ServeConnOpts{
|
|
||||||
Handler: session,
|
|
||||||
})
|
|
||||||
_ = session.Close()
|
|
||||||
}()
|
|
||||||
return session
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *H2MuxServerSession) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
s.active.Add(1)
|
|
||||||
defer s.active.Add(-1)
|
|
||||||
writer.WriteHeader(http.StatusOK)
|
|
||||||
conn := newHTTP2Wrapper(&v2rayhttp.ServerHTTPConn{
|
|
||||||
HTTP2Conn: v2rayhttp.NewHTTPConn(request.Body, writer),
|
|
||||||
Flusher: writer.(http.Flusher),
|
|
||||||
})
|
|
||||||
s.inbound <- conn
|
|
||||||
select {
|
|
||||||
case <-conn.done:
|
|
||||||
case <-s.done:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *H2MuxServerSession) Open() (net.Conn, error) {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *H2MuxServerSession) Accept() (net.Conn, error) {
|
|
||||||
select {
|
|
||||||
case conn := <-s.inbound:
|
|
||||||
return conn, nil
|
|
||||||
case <-s.done:
|
|
||||||
return nil, os.ErrClosed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *H2MuxServerSession) NumStreams() int {
|
|
||||||
return int(s.active.Load())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *H2MuxServerSession) Close() error {
|
|
||||||
select {
|
|
||||||
case <-s.done:
|
|
||||||
default:
|
|
||||||
close(s.done)
|
|
||||||
}
|
|
||||||
return s.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *H2MuxServerSession) IsClosed() bool {
|
|
||||||
select {
|
|
||||||
case <-s.done:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *H2MuxServerSession) CanTakeNewRequest() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type h2MuxConnWrapper struct {
|
|
||||||
N.ExtendedConn
|
|
||||||
done chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHTTP2Wrapper(conn net.Conn) *h2MuxConnWrapper {
|
|
||||||
return &h2MuxConnWrapper{
|
|
||||||
ExtendedConn: bufio.NewExtendedConn(conn),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *h2MuxConnWrapper) Write(p []byte) (n int, err error) {
|
|
||||||
select {
|
|
||||||
case <-w.done:
|
|
||||||
return 0, net.ErrClosed
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return w.ExtendedConn.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *h2MuxConnWrapper) WriteBuffer(buffer *buf.Buffer) error {
|
|
||||||
select {
|
|
||||||
case <-w.done:
|
|
||||||
return net.ErrClosed
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return w.ExtendedConn.WriteBuffer(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *h2MuxConnWrapper) Close() error {
|
|
||||||
select {
|
|
||||||
case <-w.done:
|
|
||||||
default:
|
|
||||||
close(w.done)
|
|
||||||
}
|
|
||||||
return w.ExtendedConn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *h2MuxConnWrapper) Upstream() any {
|
|
||||||
return w.ExtendedConn
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ abstractSession = (*H2MuxClientSession)(nil)
|
|
||||||
|
|
||||||
type H2MuxClientSession struct {
|
|
||||||
transport *http2.Transport
|
|
||||||
clientConn *http2.ClientConn
|
|
||||||
done chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewH2MuxClient(conn net.Conn) (*H2MuxClientSession, error) {
|
|
||||||
session := &H2MuxClientSession{
|
|
||||||
transport: &http2.Transport{
|
|
||||||
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
|
||||||
return conn, nil
|
|
||||||
},
|
|
||||||
ReadIdleTimeout: idleTimeout,
|
|
||||||
},
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
|
||||||
session.transport.ConnPool = session
|
|
||||||
clientConn, err := session.transport.NewClientConn(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
session.clientConn = clientConn
|
|
||||||
return session, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *H2MuxClientSession) GetClientConn(req *http.Request, addr string) (*http2.ClientConn, error) {
|
|
||||||
return s.clientConn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *H2MuxClientSession) MarkDead(conn *http2.ClientConn) {
|
|
||||||
s.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *H2MuxClientSession) Open() (net.Conn, error) {
|
|
||||||
pipeInReader, pipeInWriter := io.Pipe()
|
|
||||||
request := &http.Request{
|
|
||||||
Method: http.MethodConnect,
|
|
||||||
Body: pipeInReader,
|
|
||||||
URL: &url.URL{Scheme: "https", Host: "localhost"},
|
|
||||||
}
|
|
||||||
conn := v2rayhttp.NewLateHTTPConn(pipeInWriter)
|
|
||||||
go func() {
|
|
||||||
response, err := s.transport.RoundTrip(request)
|
|
||||||
if err != nil {
|
|
||||||
conn.Setup(nil, err)
|
|
||||||
} else if response.StatusCode != 200 {
|
|
||||||
response.Body.Close()
|
|
||||||
conn.Setup(nil, E.New("unexpected status: ", response.StatusCode, " ", response.Status))
|
|
||||||
} else {
|
|
||||||
conn.Setup(response.Body, nil)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *H2MuxClientSession) Accept() (net.Conn, error) {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *H2MuxClientSession) NumStreams() int {
|
|
||||||
return s.clientConn.State().StreamsActive
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *H2MuxClientSession) Close() error {
|
|
||||||
select {
|
|
||||||
case <-s.done:
|
|
||||||
default:
|
|
||||||
close(s.done)
|
|
||||||
}
|
|
||||||
return s.clientConn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *H2MuxClientSession) IsClosed() bool {
|
|
||||||
select {
|
|
||||||
case <-s.done:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
return s.clientConn.State().Closed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *H2MuxClientSession) CanTakeNewRequest() bool {
|
|
||||||
return s.clientConn.CanTakeNewRequest()
|
|
||||||
}
|
|
||||||
@@ -1,240 +0,0 @@
|
|||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
|
||||||
)
|
|
||||||
|
|
||||||
const kFirstPaddings = 16
|
|
||||||
|
|
||||||
type paddingConn struct {
|
|
||||||
N.ExtendedConn
|
|
||||||
writer N.VectorisedWriter
|
|
||||||
readPadding int
|
|
||||||
writePadding int
|
|
||||||
readRemaining int
|
|
||||||
paddingRemaining int
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPaddingConn(conn net.Conn) net.Conn {
|
|
||||||
writer, isVectorised := bufio.CreateVectorisedWriter(conn)
|
|
||||||
if isVectorised {
|
|
||||||
return &vectorisedPaddingConn{
|
|
||||||
paddingConn{
|
|
||||||
ExtendedConn: bufio.NewExtendedConn(conn),
|
|
||||||
writer: bufio.NewVectorisedWriter(conn),
|
|
||||||
},
|
|
||||||
writer,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return &paddingConn{
|
|
||||||
ExtendedConn: bufio.NewExtendedConn(conn),
|
|
||||||
writer: bufio.NewVectorisedWriter(conn),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *paddingConn) Read(p []byte) (n int, err error) {
|
|
||||||
if c.readRemaining > 0 {
|
|
||||||
if len(p) > c.readRemaining {
|
|
||||||
p = p[:c.readRemaining]
|
|
||||||
}
|
|
||||||
n, err = c.ExtendedConn.Read(p)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.readRemaining -= n
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.paddingRemaining > 0 {
|
|
||||||
err = rw.SkipN(c.ExtendedConn, c.paddingRemaining)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.paddingRemaining = 0
|
|
||||||
}
|
|
||||||
if c.readPadding < kFirstPaddings {
|
|
||||||
var paddingHdr []byte
|
|
||||||
if len(p) >= 4 {
|
|
||||||
paddingHdr = p[:4]
|
|
||||||
} else {
|
|
||||||
_paddingHdr := make([]byte, 4)
|
|
||||||
defer common.KeepAlive(_paddingHdr)
|
|
||||||
paddingHdr = common.Dup(_paddingHdr)
|
|
||||||
}
|
|
||||||
_, err = io.ReadFull(c.ExtendedConn, paddingHdr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
originalDataSize := int(binary.BigEndian.Uint16(paddingHdr[:2]))
|
|
||||||
paddingLen := int(binary.BigEndian.Uint16(paddingHdr[2:]))
|
|
||||||
if len(p) > originalDataSize {
|
|
||||||
p = p[:originalDataSize]
|
|
||||||
}
|
|
||||||
n, err = c.ExtendedConn.Read(p)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.readPadding++
|
|
||||||
c.readRemaining = originalDataSize - n
|
|
||||||
c.paddingRemaining = paddingLen
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.ExtendedConn.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *paddingConn) Write(p []byte) (n int, err error) {
|
|
||||||
for pLen := len(p); pLen > 0; {
|
|
||||||
var data []byte
|
|
||||||
if pLen > 65535 {
|
|
||||||
data = p[:65535]
|
|
||||||
p = p[65535:]
|
|
||||||
pLen -= 65535
|
|
||||||
} else {
|
|
||||||
data = p
|
|
||||||
pLen = 0
|
|
||||||
}
|
|
||||||
var writeN int
|
|
||||||
writeN, err = c.write(data)
|
|
||||||
n += writeN
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *paddingConn) write(p []byte) (n int, err error) {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
paddingLen := 256 + rand.Intn(512)
|
|
||||||
_buffer := buf.StackNewSize(4 + len(p) + paddingLen)
|
|
||||||
defer common.KeepAlive(_buffer)
|
|
||||||
buffer := common.Dup(_buffer)
|
|
||||||
defer buffer.Release()
|
|
||||||
header := buffer.Extend(4)
|
|
||||||
binary.BigEndian.PutUint16(header[:2], uint16(len(p)))
|
|
||||||
binary.BigEndian.PutUint16(header[2:], uint16(paddingLen))
|
|
||||||
common.Must1(buffer.Write(p))
|
|
||||||
buffer.Extend(paddingLen)
|
|
||||||
_, err = c.ExtendedConn.Write(buffer.Bytes())
|
|
||||||
if err == nil {
|
|
||||||
n = len(p)
|
|
||||||
}
|
|
||||||
c.writePadding++
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.ExtendedConn.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *paddingConn) ReadBuffer(buffer *buf.Buffer) error {
|
|
||||||
p := buffer.FreeBytes()
|
|
||||||
if c.readRemaining > 0 {
|
|
||||||
if len(p) > c.readRemaining {
|
|
||||||
p = p[:c.readRemaining]
|
|
||||||
}
|
|
||||||
n, err := c.ExtendedConn.Read(p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.readRemaining -= n
|
|
||||||
buffer.Truncate(n)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if c.paddingRemaining > 0 {
|
|
||||||
err := rw.SkipN(c.ExtendedConn, c.paddingRemaining)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.paddingRemaining = 0
|
|
||||||
}
|
|
||||||
if c.readPadding < kFirstPaddings {
|
|
||||||
var paddingHdr []byte
|
|
||||||
if len(p) >= 4 {
|
|
||||||
paddingHdr = p[:4]
|
|
||||||
} else {
|
|
||||||
_paddingHdr := make([]byte, 4)
|
|
||||||
defer common.KeepAlive(_paddingHdr)
|
|
||||||
paddingHdr = common.Dup(_paddingHdr)
|
|
||||||
}
|
|
||||||
_, err := io.ReadFull(c.ExtendedConn, paddingHdr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
originalDataSize := int(binary.BigEndian.Uint16(paddingHdr[:2]))
|
|
||||||
paddingLen := int(binary.BigEndian.Uint16(paddingHdr[2:]))
|
|
||||||
|
|
||||||
if len(p) > originalDataSize {
|
|
||||||
p = p[:originalDataSize]
|
|
||||||
}
|
|
||||||
n, err := c.ExtendedConn.Read(p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.readPadding++
|
|
||||||
c.readRemaining = originalDataSize - n
|
|
||||||
c.paddingRemaining = paddingLen
|
|
||||||
buffer.Truncate(n)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return c.ExtendedConn.ReadBuffer(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *paddingConn) WriteBuffer(buffer *buf.Buffer) error {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
bufferLen := buffer.Len()
|
|
||||||
if bufferLen > 65535 {
|
|
||||||
return common.Error(c.Write(buffer.Bytes()))
|
|
||||||
}
|
|
||||||
paddingLen := 256 + rand.Intn(512)
|
|
||||||
header := buffer.ExtendHeader(4)
|
|
||||||
binary.BigEndian.PutUint16(header[:2], uint16(bufferLen))
|
|
||||||
binary.BigEndian.PutUint16(header[2:], uint16(paddingLen))
|
|
||||||
buffer.Extend(paddingLen)
|
|
||||||
c.writePadding++
|
|
||||||
}
|
|
||||||
return c.ExtendedConn.WriteBuffer(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *paddingConn) FrontHeadroom() int {
|
|
||||||
return 4 + 256 + 1024
|
|
||||||
}
|
|
||||||
|
|
||||||
type vectorisedPaddingConn struct {
|
|
||||||
paddingConn
|
|
||||||
writer N.VectorisedWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *vectorisedPaddingConn) WriteVectorised(buffers []*buf.Buffer) error {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
bufferLen := buf.LenMulti(buffers)
|
|
||||||
if bufferLen > 65535 {
|
|
||||||
defer buf.ReleaseMulti(buffers)
|
|
||||||
for _, buffer := range buffers {
|
|
||||||
_, err := c.Write(buffer.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
paddingLen := 256 + rand.Intn(512)
|
|
||||||
header := buf.NewSize(4)
|
|
||||||
common.Must(
|
|
||||||
binary.Write(header, binary.BigEndian, uint16(bufferLen)),
|
|
||||||
binary.Write(header, binary.BigEndian, uint16(paddingLen)),
|
|
||||||
)
|
|
||||||
c.writePadding++
|
|
||||||
padding := buf.NewSize(paddingLen)
|
|
||||||
padding.Extend(paddingLen)
|
|
||||||
buffers = append(append([]*buf.Buffer{header}, buffers...), padding)
|
|
||||||
}
|
|
||||||
return c.writer.WriteVectorised(buffers)
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@ package mux
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
@@ -26,7 +25,6 @@ var Destination = M.Socksaddr{
|
|||||||
const (
|
const (
|
||||||
ProtocolSMux Protocol = iota
|
ProtocolSMux Protocol = iota
|
||||||
ProtocolYAMux
|
ProtocolYAMux
|
||||||
ProtocolH2Mux
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Protocol byte
|
type Protocol byte
|
||||||
@@ -37,10 +35,8 @@ func ParseProtocol(name string) (Protocol, error) {
|
|||||||
return ProtocolSMux, nil
|
return ProtocolSMux, nil
|
||||||
case "yamux":
|
case "yamux":
|
||||||
return ProtocolYAMux, nil
|
return ProtocolYAMux, nil
|
||||||
case "h2mux":
|
|
||||||
return ProtocolH2Mux, nil
|
|
||||||
default:
|
default:
|
||||||
return ProtocolSMux, E.New("unknown multiplex protocol: ", name)
|
return ProtocolYAMux, E.New("unknown multiplex protocol: ", name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,13 +49,7 @@ func (p Protocol) newServer(conn net.Conn) (abstractSession, error) {
|
|||||||
}
|
}
|
||||||
return &smuxSession{session}, nil
|
return &smuxSession{session}, nil
|
||||||
case ProtocolYAMux:
|
case ProtocolYAMux:
|
||||||
session, err := yamux.Server(conn, yaMuxConfig())
|
return yamux.Server(conn, yaMuxConfig())
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &yamuxSession{session}, nil
|
|
||||||
case ProtocolH2Mux:
|
|
||||||
return NewH2MuxServer(conn), nil
|
|
||||||
default:
|
default:
|
||||||
panic("unknown protocol")
|
panic("unknown protocol")
|
||||||
}
|
}
|
||||||
@@ -74,13 +64,7 @@ func (p Protocol) newClient(conn net.Conn) (abstractSession, error) {
|
|||||||
}
|
}
|
||||||
return &smuxSession{session}, nil
|
return &smuxSession{session}, nil
|
||||||
case ProtocolYAMux:
|
case ProtocolYAMux:
|
||||||
session, err := yamux.Client(conn, yaMuxConfig())
|
return yamux.Client(conn, yaMuxConfig())
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &yamuxSession{session}, nil
|
|
||||||
case ProtocolH2Mux:
|
|
||||||
return NewH2MuxClient(conn)
|
|
||||||
default:
|
default:
|
||||||
panic("unknown protocol")
|
panic("unknown protocol")
|
||||||
}
|
}
|
||||||
@@ -106,22 +90,17 @@ func (p Protocol) String() string {
|
|||||||
return "smux"
|
return "smux"
|
||||||
case ProtocolYAMux:
|
case ProtocolYAMux:
|
||||||
return "yamux"
|
return "yamux"
|
||||||
case ProtocolH2Mux:
|
|
||||||
return "h2mux"
|
|
||||||
default:
|
default:
|
||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version0 = iota
|
version0 = 0
|
||||||
Version1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Request struct {
|
type Request struct {
|
||||||
Version byte
|
Protocol Protocol
|
||||||
Protocol Protocol
|
|
||||||
PaddingEnabled bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadRequest(reader io.Reader) (*Request, error) {
|
func ReadRequest(reader io.Reader) (*Request, error) {
|
||||||
@@ -129,60 +108,22 @@ func ReadRequest(reader io.Reader) (*Request, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if version < Version0 || version > Version1 {
|
if version != version0 {
|
||||||
return nil, E.New("unsupported version: ", version)
|
return nil, E.New("unsupported version: ", version)
|
||||||
}
|
}
|
||||||
protocol, err := rw.ReadByte(reader)
|
protocol, err := rw.ReadByte(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var paddingEnabled bool
|
if protocol > byte(ProtocolYAMux) {
|
||||||
if version == Version1 {
|
return nil, E.New("unsupported protocol: ", protocol)
|
||||||
err = binary.Read(reader, binary.BigEndian, &paddingEnabled)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if paddingEnabled {
|
|
||||||
var paddingLen uint16
|
|
||||||
err = binary.Read(reader, binary.BigEndian, &paddingLen)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = rw.SkipN(reader, int(paddingLen))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return &Request{Version: version, Protocol: Protocol(protocol), PaddingEnabled: paddingEnabled}, nil
|
return &Request{Protocol: Protocol(protocol)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func EncodeRequest(request Request, payload []byte) *buf.Buffer {
|
func EncodeRequest(buffer *buf.Buffer, request Request) {
|
||||||
var requestLen int
|
buffer.WriteByte(version0)
|
||||||
requestLen += 2
|
buffer.WriteByte(byte(request.Protocol))
|
||||||
var paddingLen uint16
|
|
||||||
if request.Version == Version1 {
|
|
||||||
requestLen += 1
|
|
||||||
if request.PaddingEnabled {
|
|
||||||
requestLen += 2
|
|
||||||
paddingLen = uint16(256 + rand.Intn(512))
|
|
||||||
requestLen += int(paddingLen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer := buf.NewSize(requestLen + len(payload))
|
|
||||||
common.Must(
|
|
||||||
buffer.WriteByte(request.Version),
|
|
||||||
buffer.WriteByte(byte(request.Protocol)),
|
|
||||||
)
|
|
||||||
if request.Version == Version1 {
|
|
||||||
common.Must(binary.Write(buffer, binary.BigEndian, request.PaddingEnabled))
|
|
||||||
if request.PaddingEnabled {
|
|
||||||
common.Must(binary.Write(buffer, binary.BigEndian, paddingLen))
|
|
||||||
buffer.Extend(int(paddingLen))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
common.Must1(buffer.Write(payload))
|
|
||||||
return buffer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -219,7 +160,7 @@ func ReadStreamRequest(reader io.Reader) (*StreamRequest, error) {
|
|||||||
return &StreamRequest{network, destination, udpAddr}, nil
|
return &StreamRequest{network, destination, udpAddr}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func streamRequestLen(request StreamRequest) int {
|
func requestLen(request StreamRequest) int {
|
||||||
var rLen int
|
var rLen int
|
||||||
rLen += 1 // version
|
rLen += 1 // version
|
||||||
rLen += 2 // flags
|
rLen += 2 // flags
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Ha
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if request.PaddingEnabled {
|
|
||||||
conn = newPaddingConn(conn)
|
|
||||||
}
|
|
||||||
session, err := request.Protocol.newServer(conn)
|
session, err := request.Protocol.newServer(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -134,10 +131,6 @@ func (c *ServerConn) FrontHeadroom() int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerConn) NeedAdditionalReadDeadline() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerConn) Upstream() any {
|
func (c *ServerConn) Upstream() any {
|
||||||
return c.ExtendedConn
|
return c.ExtendedConn
|
||||||
}
|
}
|
||||||
@@ -190,10 +183,6 @@ func (c *ServerPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksad
|
|||||||
return c.ExtendedConn.WriteBuffer(buffer)
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerPacketConn) NeedAdditionalReadDeadline() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerPacketConn) Upstream() any {
|
func (c *ServerPacketConn) Upstream() any {
|
||||||
return c.ExtendedConn
|
return c.ExtendedConn
|
||||||
}
|
}
|
||||||
@@ -256,10 +245,6 @@ func (c *ServerPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Soc
|
|||||||
return c.ExtendedConn.WriteBuffer(buffer)
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ServerPacketAddrConn) NeedAdditionalReadDeadline() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerPacketAddrConn) Upstream() any {
|
func (c *ServerPacketAddrConn) Upstream() any {
|
||||||
return c.ExtendedConn
|
return c.ExtendedConn
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/smux"
|
"github.com/sagernet/smux"
|
||||||
|
|
||||||
"github.com/hashicorp/yamux"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type abstractSession interface {
|
type abstractSession interface {
|
||||||
@@ -18,7 +17,6 @@ type abstractSession interface {
|
|||||||
NumStreams() int
|
NumStreams() int
|
||||||
Close() error
|
Close() error
|
||||||
IsClosed() bool
|
IsClosed() bool
|
||||||
CanTakeNewRequest() bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ abstractSession = (*smuxSession)(nil)
|
var _ abstractSession = (*smuxSession)(nil)
|
||||||
@@ -35,49 +33,25 @@ func (s *smuxSession) Accept() (net.Conn, error) {
|
|||||||
return s.AcceptStream()
|
return s.AcceptStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *smuxSession) CanTakeNewRequest() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type yamuxSession struct {
|
|
||||||
*yamux.Session
|
|
||||||
}
|
|
||||||
|
|
||||||
func (y *yamuxSession) CanTakeNewRequest() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type protocolConn struct {
|
type protocolConn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
request Request
|
protocol Protocol
|
||||||
protocolWritten bool
|
protocolWritten bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newProtocolConn(conn net.Conn, request Request) net.Conn {
|
|
||||||
writer, isVectorised := bufio.CreateVectorisedWriter(conn)
|
|
||||||
if isVectorised {
|
|
||||||
return &vectorisedProtocolConn{
|
|
||||||
protocolConn{
|
|
||||||
Conn: conn,
|
|
||||||
request: request,
|
|
||||||
},
|
|
||||||
writer,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return &protocolConn{
|
|
||||||
Conn: conn,
|
|
||||||
request: request,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *protocolConn) Write(p []byte) (n int, err error) {
|
func (c *protocolConn) Write(p []byte) (n int, err error) {
|
||||||
if c.protocolWritten {
|
if c.protocolWritten {
|
||||||
return c.Conn.Write(p)
|
return c.Conn.Write(p)
|
||||||
}
|
}
|
||||||
buffer := EncodeRequest(c.request, p)
|
_buffer := buf.StackNewSize(2 + len(p))
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
EncodeRequest(buffer, Request{
|
||||||
|
Protocol: c.protocol,
|
||||||
|
})
|
||||||
|
common.Must(common.Error(buffer.Write(p)))
|
||||||
n, err = c.Conn.Write(buffer.Bytes())
|
n, err = c.Conn.Write(buffer.Bytes())
|
||||||
buffer.Release()
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
n--
|
n--
|
||||||
}
|
}
|
||||||
@@ -98,14 +72,20 @@ func (c *protocolConn) Upstream() any {
|
|||||||
|
|
||||||
type vectorisedProtocolConn struct {
|
type vectorisedProtocolConn struct {
|
||||||
protocolConn
|
protocolConn
|
||||||
writer N.VectorisedWriter
|
N.VectorisedWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *vectorisedProtocolConn) WriteVectorised(buffers []*buf.Buffer) error {
|
func (c *vectorisedProtocolConn) WriteVectorised(buffers []*buf.Buffer) error {
|
||||||
if c.protocolWritten {
|
if c.protocolWritten {
|
||||||
return c.writer.WriteVectorised(buffers)
|
return c.VectorisedWriter.WriteVectorised(buffers)
|
||||||
}
|
}
|
||||||
c.protocolWritten = true
|
c.protocolWritten = true
|
||||||
buffer := EncodeRequest(c.request, nil)
|
_buffer := buf.StackNewSize(2)
|
||||||
return c.writer.WriteVectorised(append([]*buf.Buffer{buffer}, buffers...))
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
EncodeRequest(buffer, Request{
|
||||||
|
Protocol: c.protocol,
|
||||||
|
})
|
||||||
|
return c.VectorisedWriter.WriteVectorised(append([]*buf.Buffer{buffer}, buffers...))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,10 @@ package process
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os/user"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Searcher interface {
|
type Searcher interface {
|
||||||
@@ -30,15 +28,5 @@ type Info struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||||
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
|
return findProcessInfo(searcher, ctx, network, source, destination)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if info.UserId != -1 {
|
|
||||||
osUser, _ := user.LookupId(F.ToString(info.UserId))
|
|
||||||
if osUser != nil {
|
|
||||||
info.User = osUser.Username
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return info, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
25
common/process/searcher_with_name.go
Normal file
25
common/process/searcher_with_name.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
//go:build linux && !android
|
||||||
|
|
||||||
|
package process
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
"os/user"
|
||||||
|
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||||
|
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if info.UserId != -1 {
|
||||||
|
osUser, _ := user.LookupId(F.ToString(info.UserId))
|
||||||
|
if osUser != nil {
|
||||||
|
info.User = osUser.Username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
12
common/process/searcher_without_name.go
Normal file
12
common/process/searcher_without_name.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build !linux || android
|
||||||
|
|
||||||
|
package process
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
)
|
||||||
|
|
||||||
|
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||||
|
return searcher.FindProcessInfo(ctx, network, source, destination)
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"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"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
"github.com/sagernet/sing/protocol/http"
|
"github.com/sagernet/sing/protocol/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,5 +15,5 @@ func HTTPHost(ctx context.Context, reader io.Reader) (*adapter.InboundContext, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: M.ParseSocksaddr(request.Host).AddrString()}, nil
|
return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: request.Host}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
package sniff_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/sniff"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSniffHTTP1(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
pkt := "GET / HTTP/1.1\r\nHost: www.google.com\r\nAccept: */*\r\n\r\n"
|
|
||||||
metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, metadata.Domain, "www.google.com")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSniffHTTP1WithPort(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
pkt := "GET / HTTP/1.1\r\nHost: www.gov.cn:8080\r\nAccept: */*\r\n\r\n"
|
|
||||||
metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, metadata.Domain, "www.gov.cn")
|
|
||||||
}
|
|
||||||
@@ -24,12 +24,12 @@ func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout
|
|||||||
}
|
}
|
||||||
err := conn.SetReadDeadline(time.Now().Add(timeout))
|
err := conn.SetReadDeadline(time.Now().Add(timeout))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "set read deadline")
|
return nil, err
|
||||||
}
|
}
|
||||||
_, err = buffer.ReadOnceFrom(conn)
|
_, err = buffer.ReadOnceFrom(conn)
|
||||||
err = E.Errors(err, conn.SetReadDeadline(time.Time{}))
|
err = E.Errors(err, conn.SetReadDeadline(time.Time{}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "read payload")
|
return nil, err
|
||||||
}
|
}
|
||||||
var metadata *adapter.InboundContext
|
var metadata *adapter.InboundContext
|
||||||
var errors []error
|
var errors []error
|
||||||
|
|||||||
@@ -124,9 +124,8 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
|
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
|
||||||
|
|
||||||
hello.SessionId[0] = 1
|
hello.SessionId[0] = 1
|
||||||
hello.SessionId[1] = 8
|
hello.SessionId[1] = 7
|
||||||
hello.SessionId[2] = 1
|
hello.SessionId[2] = 5
|
||||||
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))
|
|
||||||
copy(hello.SessionId[8:], e.shortID[:])
|
copy(hello.SessionId[8:], e.shortID[:])
|
||||||
|
|
||||||
if debug.Enabled {
|
if debug.Enabled {
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
|||||||
tlsConfig.ServerName = options.ServerName
|
tlsConfig.ServerName = options.ServerName
|
||||||
}
|
}
|
||||||
if len(options.ALPN) > 0 {
|
if len(options.ALPN) > 0 {
|
||||||
tlsConfig.NextProtos = append(options.ALPN, tlsConfig.NextProtos...)
|
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
|
||||||
}
|
}
|
||||||
if options.MinVersion != "" {
|
if options.MinVersion != "" {
|
||||||
minVersion, err := ParseTLSVersion(options.MinVersion)
|
minVersion, err := ParseTLSVersion(options.MinVersion)
|
||||||
|
|||||||
@@ -50,9 +50,6 @@ func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
|
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
|
||||||
if link == "" {
|
|
||||||
link = "https://www.gstatic.com/generate_204"
|
|
||||||
}
|
|
||||||
linkURL, err := url.Parse(link)
|
linkURL, err := url.Parse(link)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
31
common/warning/warning.go
Normal file
31
common/warning/warning.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package warning
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Warning struct {
|
||||||
|
logger log.Logger
|
||||||
|
check CheckFunc
|
||||||
|
message string
|
||||||
|
checkOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckFunc = func() bool
|
||||||
|
|
||||||
|
func New(checkFunc CheckFunc, message string) Warning {
|
||||||
|
return Warning{
|
||||||
|
check: checkFunc,
|
||||||
|
message: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Warning) Check() {
|
||||||
|
w.checkOnce.Do(func() {
|
||||||
|
if w.check() {
|
||||||
|
log.Warn(w.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -3,13 +3,28 @@ package constant
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dirName = "sing-box"
|
const dirName = "sing-box"
|
||||||
|
|
||||||
var resourcePaths []string
|
var (
|
||||||
|
basePath string
|
||||||
|
resourcePaths []string
|
||||||
|
)
|
||||||
|
|
||||||
|
func BasePath(name string) string {
|
||||||
|
if basePath == "" || strings.HasPrefix(name, "/") {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return filepath.Join(basePath, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetBasePath(path string) {
|
||||||
|
basePath = path
|
||||||
|
}
|
||||||
|
|
||||||
func FindPath(name string) (string, bool) {
|
func FindPath(name string) (string, bool) {
|
||||||
name = os.ExpandEnv(name)
|
name = os.ExpandEnv(name)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func applyDebugOptions(options option.DebugOptions) {
|
func applyDebugOptions(options option.DebugOptions) {
|
||||||
applyDebugListenOption(options)
|
|
||||||
if options.GCPercent != nil {
|
if options.GCPercent != nil {
|
||||||
debug.SetGCPercent(*options.GCPercent)
|
debug.SetGCPercent(*options.GCPercent)
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func applyDebugOptions(options option.DebugOptions) {
|
func applyDebugOptions(options option.DebugOptions) {
|
||||||
applyDebugListenOption(options)
|
|
||||||
if options.GCPercent != nil {
|
if options.GCPercent != nil {
|
||||||
debug.SetGCPercent(*options.GCPercent)
|
debug.SetGCPercent(*options.GCPercent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
package box
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/pprof"
|
|
||||||
"runtime"
|
|
||||||
"runtime/debug"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/badjson"
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
|
|
||||||
"github.com/dustin/go-humanize"
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
var debugHTTPServer *http.Server
|
|
||||||
|
|
||||||
func applyDebugListenOption(options option.DebugOptions) {
|
|
||||||
if debugHTTPServer != nil {
|
|
||||||
debugHTTPServer.Close()
|
|
||||||
debugHTTPServer = nil
|
|
||||||
}
|
|
||||||
if options.Listen == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r := chi.NewMux()
|
|
||||||
r.Route("/debug", func(r chi.Router) {
|
|
||||||
r.Get("/gc", func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
writer.WriteHeader(http.StatusNoContent)
|
|
||||||
go debug.FreeOSMemory()
|
|
||||||
})
|
|
||||||
r.Get("/memory", func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
var memStats runtime.MemStats
|
|
||||||
runtime.ReadMemStats(&memStats)
|
|
||||||
|
|
||||||
var memObject badjson.JSONObject
|
|
||||||
memObject.Put("heap", humanize.IBytes(memStats.HeapInuse))
|
|
||||||
memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
|
|
||||||
memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
|
|
||||||
memObject.Put("goroutines", runtime.NumGoroutine())
|
|
||||||
memObject.Put("rss", rusageMaxRSS())
|
|
||||||
|
|
||||||
encoder := json.NewEncoder(writer)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
encoder.Encode(memObject)
|
|
||||||
})
|
|
||||||
r.HandleFunc("/pprof", pprof.Index)
|
|
||||||
r.HandleFunc("/pprof/*", pprof.Index)
|
|
||||||
r.HandleFunc("/pprof/cmdline", pprof.Cmdline)
|
|
||||||
r.HandleFunc("/pprof/profile", pprof.Profile)
|
|
||||||
r.HandleFunc("/pprof/symbol", pprof.Symbol)
|
|
||||||
r.HandleFunc("/pprof/trace", pprof.Trace)
|
|
||||||
})
|
|
||||||
debugHTTPServer = &http.Server{
|
|
||||||
Addr: options.Listen,
|
|
||||||
Handler: r,
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
err := debugHTTPServer.ListenAndServe()
|
|
||||||
if err != nil && !E.IsClosed(err) {
|
|
||||||
log.Error(E.Cause(err, "serve debug HTTP server"))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
@@ -1,128 +1,3 @@
|
|||||||
#### 1.3-beta9
|
|
||||||
|
|
||||||
* Improve multiplex **1**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
*1*:
|
|
||||||
|
|
||||||
Added new `h2mux` multiplex protocol and `padding` multiplex option, see [Multiplex](/configuration/shared/multiplex).
|
|
||||||
|
|
||||||
#### 1.2.6
|
|
||||||
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
#### 1.3-beta8
|
|
||||||
|
|
||||||
* Fix `system` tun stack for ios
|
|
||||||
* Fix network monitor for android/ios
|
|
||||||
* Update VLESS and XUDP protocol **1**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
*1:
|
|
||||||
|
|
||||||
This is an incompatible update for XUDP in VLESS if vision flow is enabled.
|
|
||||||
|
|
||||||
#### 1.3-beta7
|
|
||||||
|
|
||||||
* Add `path` and `headers` options for HTTP outbound
|
|
||||||
* Add multi-user support for Shadowsocks legacy AEAD inbound
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.2.4
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.3-beta6
|
|
||||||
|
|
||||||
* Fix WireGuard reconnect
|
|
||||||
* Perform URLTest recheck after network changes
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
#### 1.3-beta5
|
|
||||||
|
|
||||||
* Add Clash.Meta API compatibility for Clash API
|
|
||||||
* Download Yacd-meta by default if the specified Clash `external_ui` directory is empty
|
|
||||||
* Add path and headers option for HTTP outbound
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.3-beta4
|
|
||||||
|
|
||||||
* Fix bugs
|
|
||||||
|
|
||||||
#### 1.3-beta2
|
|
||||||
|
|
||||||
* Download clash-dashboard if the specified Clash `external_ui` directory is empty
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
#### 1.3-beta1
|
|
||||||
|
|
||||||
* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support
|
|
||||||
* Add [L3 routing](/configuration/route/ip-rule) support **1**
|
|
||||||
* Add `rewrite_ttl` DNS rule action
|
|
||||||
* Add [FakeIP](/configuration/dns/fakeip) support **2**
|
|
||||||
* Add `store_fakeip` Clash API option
|
|
||||||
* Add multi-peer support for [WireGuard](/configuration/outbound/wireguard#peers) outbound
|
|
||||||
* Add loopback detect
|
|
||||||
|
|
||||||
*1*:
|
|
||||||
|
|
||||||
It can currently be used to [route connections directly to WireGuard](/examples/wireguard-direct) or block connections
|
|
||||||
at the IP layer.
|
|
||||||
|
|
||||||
*2*:
|
|
||||||
|
|
||||||
See [FAQ](/faq/fakeip) for more information.
|
|
||||||
|
|
||||||
#### 1.2.3
|
|
||||||
|
|
||||||
* Introducing our [new Android client application](/installation/clients/sfa)
|
|
||||||
* Improve UDP domain destination NAT
|
|
||||||
* Update reality protocol
|
|
||||||
* Fix TTL calculation for DNS response
|
|
||||||
* Fix v2ray HTTP transport compatibility
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
#### 1.2.2
|
|
||||||
|
|
||||||
* Accept `any` outbound in dns rule **1**
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
*1*:
|
|
||||||
|
|
||||||
Now you can use the `any` outbound rule to match server address queries instead of filling in all server domains
|
|
||||||
to `domain` rule.
|
|
||||||
|
|
||||||
#### 1.2.1
|
|
||||||
|
|
||||||
* Fix missing default host in v2ray http transport`s request
|
|
||||||
* Flush DNS cache for macOS when tun start/close
|
|
||||||
* Fix tun's DNS hijacking compatibility with systemd-resolved
|
|
||||||
|
|
||||||
#### 1.2.0
|
|
||||||
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
Important changes since 1.1:
|
|
||||||
|
|
||||||
* Introducing our [new iOS client application](/installation/clients/sfi)
|
|
||||||
* Introducing [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
|
|
||||||
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
|
|
||||||
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
|
|
||||||
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
|
|
||||||
* Add [reality TLS](/configuration/shared/tls) support
|
|
||||||
* Add [NTP service](/configuration/ntp)
|
|
||||||
* Add [DHCP DNS server](/configuration/dns/server) support
|
|
||||||
* Add SSH [host key validation](/configuration/outbound/ssh) support
|
|
||||||
* Add [query_type](/configuration/dns/rule) DNS rule item
|
|
||||||
* Add fallback support for v2ray transport
|
|
||||||
* Add custom TLS server support for http based v2ray transports
|
|
||||||
* Add health check support for http-based v2ray transports
|
|
||||||
* Add multiple configuration support
|
|
||||||
|
|
||||||
#### 1.2-rc1
|
|
||||||
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
#### 1.2-beta10
|
#### 1.2-beta10
|
||||||
|
|
||||||
* Add multiple configuration support **1**
|
* Add multiple configuration support **1**
|
||||||
@@ -130,11 +5,9 @@ Important changes since 1.1:
|
|||||||
|
|
||||||
*1*:
|
*1*:
|
||||||
|
|
||||||
Now you can pass the parameter `--config` or `-c` multiple times, or use the new parameter `--config-directory` or `-C`
|
Now you can pass the parameter `--config` or `-c` multiple times, or use the new parameter `--config-directory` or `-C` to load all configuration files in a directory.
|
||||||
to load all configuration files in a directory.
|
|
||||||
|
|
||||||
Loaded configuration files are sorted by name. If you want to control the merge order, add a numeric prefix to the file
|
Loaded configuration files are sorted by name. If you want to control the merge order, add a numeric prefix to the file name.
|
||||||
name.
|
|
||||||
|
|
||||||
#### 1.1.7
|
#### 1.1.7
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
# FakeIP
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"enabled": true,
|
|
||||||
"inet4_range": "198.18.0.0/15",
|
|
||||||
"inet6_range": "fc00::/18"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### enabled
|
|
||||||
|
|
||||||
Enable FakeIP service.
|
|
||||||
|
|
||||||
#### inet4_range
|
|
||||||
|
|
||||||
IPv4 address range for FakeIP.
|
|
||||||
|
|
||||||
#### inet6_address
|
|
||||||
|
|
||||||
IPv6 address range for FakeIP.
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# FakeIP
|
|
||||||
|
|
||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"enabled": true,
|
|
||||||
"inet4_range": "198.18.0.0/15",
|
|
||||||
"inet6_range": "fc00::/18"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 字段
|
|
||||||
|
|
||||||
#### enabled
|
|
||||||
|
|
||||||
启用 FakeIP 服务。
|
|
||||||
|
|
||||||
#### inet4_range
|
|
||||||
|
|
||||||
用于 FakeIP 的 IPv4 地址范围。
|
|
||||||
|
|
||||||
#### inet6_range
|
|
||||||
|
|
||||||
用于 FakeIP 的 IPv6 地址范围。
|
|
||||||
@@ -10,9 +10,7 @@
|
|||||||
"final": "",
|
"final": "",
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_expire": false,
|
"disable_expire": false
|
||||||
"reverse_mapping": false,
|
|
||||||
"fakeip": {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +22,6 @@
|
|||||||
|----------|--------------------------------|
|
|----------|--------------------------------|
|
||||||
| `server` | List of [DNS Server](./server) |
|
| `server` | List of [DNS Server](./server) |
|
||||||
| `rules` | List of [DNS Rule](./rule) |
|
| `rules` | List of [DNS Rule](./rule) |
|
||||||
| `fakeip` | [FakeIP](./fakeip) |
|
|
||||||
|
|
||||||
#### final
|
#### final
|
||||||
|
|
||||||
@@ -46,15 +43,4 @@ Disable dns cache.
|
|||||||
|
|
||||||
#### disable_expire
|
#### disable_expire
|
||||||
|
|
||||||
Disable dns cache expire.
|
Disable dns cache expire.
|
||||||
|
|
||||||
#### reverse_mapping
|
|
||||||
|
|
||||||
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.
|
|
||||||
|
|
||||||
Since this process relies on the act of resolving domain names by an application before making a request, it can be
|
|
||||||
problematic in environments such as macOS, where DNS is proxied and cached by the system.
|
|
||||||
|
|
||||||
#### fakeip
|
|
||||||
|
|
||||||
[FakeIP](./fakeip) settings.
|
|
||||||
@@ -10,9 +10,7 @@
|
|||||||
"final": "",
|
"final": "",
|
||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_expire": false,
|
"disable_expire": false
|
||||||
"reverse_mapping": false,
|
|
||||||
"fakeip": {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,14 +43,4 @@
|
|||||||
|
|
||||||
#### disable_expire
|
#### disable_expire
|
||||||
|
|
||||||
禁用 DNS 缓存过期。
|
禁用 DNS 缓存过期。
|
||||||
|
|
||||||
#### reverse_mapping
|
|
||||||
|
|
||||||
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
|
|
||||||
|
|
||||||
由于此过程依赖于应用程序在发出请求之前解析域名的行为,因此在 macOS 等 DNS 由系统代理和缓存的环境中可能会出现问题。
|
|
||||||
|
|
||||||
#### fakeip
|
|
||||||
|
|
||||||
[FakeIP](./fakeip) 设置。
|
|
||||||
@@ -84,16 +84,14 @@
|
|||||||
"direct"
|
"direct"
|
||||||
],
|
],
|
||||||
"server": "local",
|
"server": "local",
|
||||||
"disable_cache": false,
|
"disable_cache": false
|
||||||
"rewrite_ttl": 100
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"type": "logical",
|
||||||
"mode": "and",
|
"mode": "and",
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"server": "local",
|
"server": "local",
|
||||||
"disable_cache": false,
|
"disable_cache": false
|
||||||
"rewrite_ttl": 100
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -234,8 +232,6 @@ Invert match result.
|
|||||||
|
|
||||||
Match outbound.
|
Match outbound.
|
||||||
|
|
||||||
`any` can be used as a value to match any outbound.
|
|
||||||
|
|
||||||
#### server
|
#### server
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
@@ -246,10 +242,6 @@ Tag of the target dns server.
|
|||||||
|
|
||||||
Disable cache and save cache in this query.
|
Disable cache and save cache in this query.
|
||||||
|
|
||||||
#### rewrite_ttl
|
|
||||||
|
|
||||||
Rewrite TTL in DNS responses.
|
|
||||||
|
|
||||||
### Logical Fields
|
### Logical Fields
|
||||||
|
|
||||||
#### type
|
#### type
|
||||||
@@ -262,4 +254,18 @@ Rewrite TTL in DNS responses.
|
|||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
Included default rules.
|
Included default rules.
|
||||||
|
|
||||||
|
#### invert
|
||||||
|
|
||||||
|
Invert match result.
|
||||||
|
|
||||||
|
#### server
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
Tag of the target dns server.
|
||||||
|
|
||||||
|
#### disable_cache
|
||||||
|
|
||||||
|
Disable cache and save cache in this query.
|
||||||
@@ -231,8 +231,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
匹配出站。
|
匹配出站。
|
||||||
|
|
||||||
`any` 可作为值用于匹配任意出站。
|
|
||||||
|
|
||||||
#### server
|
#### server
|
||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
@@ -243,10 +241,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
在此查询中禁用缓存。
|
在此查询中禁用缓存。
|
||||||
|
|
||||||
#### rewrite_ttl
|
|
||||||
|
|
||||||
重写 DNS 回应中的 TTL。
|
|
||||||
|
|
||||||
### 逻辑字段
|
### 逻辑字段
|
||||||
|
|
||||||
#### type
|
#### type
|
||||||
@@ -259,4 +253,18 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
包括的默认规则。
|
包括的默认规则。
|
||||||
|
|
||||||
|
#### invert
|
||||||
|
|
||||||
|
反选匹配结果。
|
||||||
|
|
||||||
|
#### server
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
目标 DNS 服务器的标签。
|
||||||
|
|
||||||
|
#### disable_cache
|
||||||
|
|
||||||
|
在此查询中禁用缓存。
|
||||||
@@ -30,18 +30,17 @@ The tag of the dns server.
|
|||||||
|
|
||||||
The address of the dns server.
|
The address of the dns server.
|
||||||
|
|
||||||
| Protocol | Format |
|
| Protocol | Format |
|
||||||
|---------------------|-------------------------------|
|
|----------|-------------------------------|
|
||||||
| `System` | `local` |
|
| `System` | `local` |
|
||||||
| `TCP` | `tcp://1.0.0.1` |
|
| `TCP` | `tcp://1.0.0.1` |
|
||||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||||
| `TLS` | `tls://dns.google` |
|
| `TLS` | `tls://dns.google` |
|
||||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||||
| `QUIC` | `quic://dns.adguard.com` |
|
| `QUIC` | `quic://dns.adguard.com` |
|
||||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||||
| `RCode` | `rcode://refused` |
|
| `RCode` | `rcode://refused` |
|
||||||
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
|
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
|
||||||
| [FakeIP](./fakeip) | `fakeip` |
|
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
|
|||||||
@@ -30,18 +30,17 @@ DNS 服务器的标签。
|
|||||||
|
|
||||||
DNS 服务器的地址。
|
DNS 服务器的地址。
|
||||||
|
|
||||||
| 协议 | 格式 |
|
| 协议 | 格式 |
|
||||||
|--------------------|------------------------------|
|
|----------|------------------------------|
|
||||||
| `System` | `local` |
|
| `System` | `local` |
|
||||||
| `TCP` | `tcp://1.0.0.1` |
|
| `TCP` | `tcp://1.0.0.1` |
|
||||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||||
| `TLS` | `tls://dns.google` |
|
| `TLS` | `tls://dns.google` |
|
||||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||||
| `QUIC` | `quic://dns.adguard.com` |
|
| `QUIC` | `quic://dns.adguard.com` |
|
||||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||||
| `RCode` | `rcode://refused` |
|
| `RCode` | `rcode://refused` |
|
||||||
| `DHCP` | `dhcp://auto` 或 `dhcp://en0` |
|
| `DHCP` | `dhcp://auto` 或 `dhcp://en0` |
|
||||||
| [FakeIP](./fakeip) | `fakeip` |
|
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
"clash_api": {
|
"clash_api": {
|
||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "folder",
|
"external_ui": "folder",
|
||||||
"external_ui_download_url": "",
|
|
||||||
"external_ui_download_detour": "",
|
|
||||||
"secret": "",
|
"secret": "",
|
||||||
"default_mode": "rule",
|
"default_mode": "rule",
|
||||||
"store_selected": false,
|
"store_selected": false,
|
||||||
@@ -55,18 +53,6 @@ A relative path to the configuration directory or an absolute path to a
|
|||||||
directory in which you put some static web resource. sing-box will then
|
directory in which you put some static web resource. sing-box will then
|
||||||
serve it at `http://{{external-controller}}/ui`.
|
serve it at `http://{{external-controller}}/ui`.
|
||||||
|
|
||||||
#### external_ui_download_url
|
|
||||||
|
|
||||||
ZIP download URL for the external UI, will be used if the specified `external_ui` directory is empty.
|
|
||||||
|
|
||||||
`https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip` will be used if empty.
|
|
||||||
|
|
||||||
#### external_ui_download_detour
|
|
||||||
|
|
||||||
The tag of the outbound to download the external UI.
|
|
||||||
|
|
||||||
Default outbound will be used if empty.
|
|
||||||
|
|
||||||
#### secret
|
#### secret
|
||||||
|
|
||||||
Secret for the RESTful API (optional)
|
Secret for the RESTful API (optional)
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
"clash_api": {
|
"clash_api": {
|
||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "folder",
|
"external_ui": "folder",
|
||||||
"external_ui_download_url": "",
|
|
||||||
"external_ui_download_detour": "",
|
|
||||||
"secret": "",
|
"secret": "",
|
||||||
"default_mode": "rule",
|
"default_mode": "rule",
|
||||||
"store_selected": false,
|
"store_selected": false,
|
||||||
@@ -53,18 +51,6 @@ RESTful web API 监听地址。如果为空,则禁用 Clash API。
|
|||||||
|
|
||||||
到静态网页资源目录的相对路径或绝对路径。sing-box 会在 `http://{{external-controller}}/ui` 下提供它。
|
到静态网页资源目录的相对路径或绝对路径。sing-box 会在 `http://{{external-controller}}/ui` 下提供它。
|
||||||
|
|
||||||
#### external_ui_download_url
|
|
||||||
|
|
||||||
静态网页资源的 ZIP 下载 URL,如果指定的 `external_ui` 目录为空,将使用。
|
|
||||||
|
|
||||||
默认使用 `https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip`。
|
|
||||||
|
|
||||||
#### external_ui_download_detour
|
|
||||||
|
|
||||||
用于下载静态网页资源的出站的标签。
|
|
||||||
|
|
||||||
如果为空,将使用默认出站。
|
|
||||||
|
|
||||||
#### secret
|
#### secret
|
||||||
|
|
||||||
RESTful API 的密钥(可选)
|
RESTful API 的密钥(可选)
|
||||||
|
|||||||
@@ -40,8 +40,4 @@ No authentication required if empty.
|
|||||||
|
|
||||||
Only supported on Linux, Android, Windows, and macOS.
|
Only supported on Linux, Android, Windows, and macOS.
|
||||||
|
|
||||||
!!! warning ""
|
|
||||||
|
|
||||||
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
|
|
||||||
|
|
||||||
Automatically set system proxy configuration when start and clean up when stop.
|
Automatically set system proxy configuration when start and clean up when stop.
|
||||||
|
|||||||
@@ -40,8 +40,4 @@ HTTP 用户
|
|||||||
|
|
||||||
仅支持 Linux、Android、Windows 和 macOS。
|
仅支持 Linux、Android、Windows 和 macOS。
|
||||||
|
|
||||||
!!! warning ""
|
|
||||||
|
|
||||||
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
|
|
||||||
|
|
||||||
启动时自动设置系统代理,停止时自动清理。
|
启动时自动设置系统代理,停止时自动清理。
|
||||||
@@ -37,8 +37,4 @@ No authentication required if empty.
|
|||||||
|
|
||||||
Only supported on Linux, Android, Windows, and macOS.
|
Only supported on Linux, Android, Windows, and macOS.
|
||||||
|
|
||||||
!!! warning ""
|
Automatically set system proxy configuration when start and clean up when stop.
|
||||||
|
|
||||||
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
|
|
||||||
|
|
||||||
Automatically set system proxy configuration when start and clean up when stop.
|
|
||||||
@@ -37,8 +37,4 @@ SOCKS 和 HTTP 用户
|
|||||||
|
|
||||||
仅支持 Linux、Android、Windows 和 macOS。
|
仅支持 Linux、Android、Windows 和 macOS。
|
||||||
|
|
||||||
!!! warning ""
|
|
||||||
|
|
||||||
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
|
|
||||||
|
|
||||||
启动时自动设置系统代理,停止时自动清理。
|
启动时自动设置系统代理,停止时自动清理。
|
||||||
@@ -107,7 +107,8 @@ Enforce strict routing rules when `auto_route` is enabled:
|
|||||||
* Let unsupported network unreachable
|
* Let unsupported network unreachable
|
||||||
* Route all connections to tun
|
* Route all connections to tun
|
||||||
|
|
||||||
It prevents address leaks and makes DNS hijacking work on Android, but your device will not be accessible by others.
|
It prevents address leaks and makes DNS hijacking work on Android and Linux with systemd-resolved, but your device will
|
||||||
|
not be accessible by others.
|
||||||
|
|
||||||
*In Windows*:
|
*In Windows*:
|
||||||
|
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ tun 接口的 IPv6 前缀。
|
|||||||
* 让不支持的网络无法到达
|
* 让不支持的网络无法到达
|
||||||
* 将所有连接路由到 tun
|
* 将所有连接路由到 tun
|
||||||
|
|
||||||
它可以防止地址泄漏,并使 DNS 劫持在 Android 上工作,但你的设备将无法其他设备被访问。
|
它可以防止地址泄漏,并使 DNS 劫持在 Android 和使用 systemd-resolved 的 Linux 上工作,但你的设备将无法其他设备被访问。
|
||||||
|
|
||||||
*在 Windows 中*:
|
*在 Windows 中*:
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"username": "sekai",
|
"username": "sekai",
|
||||||
"password": "admin",
|
"password": "admin",
|
||||||
"path": "",
|
|
||||||
"headers": {},
|
|
||||||
"tls": {},
|
"tls": {},
|
||||||
|
|
||||||
... // Dial Fields
|
... // Dial Fields
|
||||||
@@ -41,14 +39,6 @@ Basic authorization username.
|
|||||||
|
|
||||||
Basic authorization password.
|
Basic authorization password.
|
||||||
|
|
||||||
#### path
|
|
||||||
|
|
||||||
Path of HTTP request.
|
|
||||||
|
|
||||||
#### headers
|
|
||||||
|
|
||||||
Extra headers of HTTP request.
|
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||||
|
|||||||
@@ -11,8 +11,6 @@
|
|||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"username": "sekai",
|
"username": "sekai",
|
||||||
"password": "admin",
|
"password": "admin",
|
||||||
"path": "",
|
|
||||||
"headers": {},
|
|
||||||
"tls": {},
|
"tls": {},
|
||||||
|
|
||||||
... // 拨号字段
|
... // 拨号字段
|
||||||
@@ -41,14 +39,6 @@ Basic 认证用户名。
|
|||||||
|
|
||||||
Basic 认证密码。
|
Basic 认证密码。
|
||||||
|
|
||||||
#### path
|
|
||||||
|
|
||||||
HTTP 请求路径。
|
|
||||||
|
|
||||||
#### headers
|
|
||||||
|
|
||||||
HTTP 请求的额外标头。
|
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
||||||
|
|||||||
@@ -37,10 +37,4 @@
|
|||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
The tag of the outbound.
|
The tag of the outbound.
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
#### Outbounds that support IP connection
|
|
||||||
|
|
||||||
* `WireGuard`
|
|
||||||
@@ -36,10 +36,4 @@
|
|||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
出站的标签。
|
出站的标签。
|
||||||
|
|
||||||
### 特性
|
|
||||||
|
|
||||||
#### 支持 IP 连接的出站
|
|
||||||
|
|
||||||
* `WireGuard`
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"proxy-b",
|
"proxy-b",
|
||||||
"proxy-c"
|
"proxy-c"
|
||||||
],
|
],
|
||||||
"url": "https://www.gstatic.com/generate_204",
|
"url": "http://www.gstatic.com/generate_204",
|
||||||
"interval": "1m",
|
"interval": "1m",
|
||||||
"tolerance": 50
|
"tolerance": 50
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ List of outbound tags to test.
|
|||||||
|
|
||||||
#### url
|
#### url
|
||||||
|
|
||||||
The URL to test. `https://www.gstatic.com/generate_204` will be used if empty.
|
The URL to test. `http://www.gstatic.com/generate_204` will be used if empty.
|
||||||
|
|
||||||
#### interval
|
#### interval
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"proxy-b",
|
"proxy-b",
|
||||||
"proxy-c"
|
"proxy-c"
|
||||||
],
|
],
|
||||||
"url": "https://www.gstatic.com/generate_204",
|
"url": "http://www.gstatic.com/generate_204",
|
||||||
"interval": "1m",
|
"interval": "1m",
|
||||||
"tolerance": 50
|
"tolerance": 50
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
#### url
|
#### url
|
||||||
|
|
||||||
用于测试的链接。默认使用 `https://www.gstatic.com/generate_204`。
|
用于测试的链接。默认使用 `http://www.gstatic.com/generate_204`。
|
||||||
|
|
||||||
#### interval
|
#### interval
|
||||||
|
|
||||||
|
|||||||
@@ -13,18 +13,6 @@
|
|||||||
"10.0.0.2/32"
|
"10.0.0.2/32"
|
||||||
],
|
],
|
||||||
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
|
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
|
||||||
"peers": [
|
|
||||||
{
|
|
||||||
"server": "127.0.0.1",
|
|
||||||
"server_port": 1080,
|
|
||||||
"public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
|
|
||||||
"pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=",
|
|
||||||
"allowed_ips": [
|
|
||||||
"0.0.0.0/0"
|
|
||||||
],
|
|
||||||
"reserved": [0, 0, 0]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
|
"peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
|
||||||
"pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=",
|
"pre_shared_key": "31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM=",
|
||||||
"reserved": [0, 0, 0],
|
"reserved": [0, 0, 0],
|
||||||
@@ -48,13 +36,13 @@
|
|||||||
|
|
||||||
#### server
|
#### server
|
||||||
|
|
||||||
==Required if multi-peer disabled==
|
==Required==
|
||||||
|
|
||||||
The server address.
|
The server address.
|
||||||
|
|
||||||
#### server_port
|
#### server_port
|
||||||
|
|
||||||
==Required if multi-peer disabled==
|
==Required==
|
||||||
|
|
||||||
The server port.
|
The server port.
|
||||||
|
|
||||||
@@ -87,25 +75,9 @@ wg genkey
|
|||||||
echo "private key" || wg pubkey
|
echo "private key" || wg pubkey
|
||||||
```
|
```
|
||||||
|
|
||||||
#### peers
|
|
||||||
|
|
||||||
Multi-peer support.
|
|
||||||
|
|
||||||
If enabled, `server, server_port, peer_public_key, pre_shared_key` will be ignored.
|
|
||||||
|
|
||||||
#### peers.allowed_ips
|
|
||||||
|
|
||||||
WireGuard allowed IPs.
|
|
||||||
|
|
||||||
#### peers.reserved
|
|
||||||
|
|
||||||
WireGuard reserved field bytes.
|
|
||||||
|
|
||||||
`$outbound.reserved` will be used if empty.
|
|
||||||
|
|
||||||
#### peer_public_key
|
#### peer_public_key
|
||||||
|
|
||||||
==Required if multi-peer disabled==
|
==Required==
|
||||||
|
|
||||||
WireGuard peer public key.
|
WireGuard peer public key.
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
"route": {
|
"route": {
|
||||||
"geoip": {},
|
"geoip": {},
|
||||||
"geosite": {},
|
"geosite": {},
|
||||||
"ip_rules": [],
|
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"final": "",
|
"final": "",
|
||||||
"auto_detect_interface": false,
|
"auto_detect_interface": false,
|
||||||
@@ -20,12 +19,11 @@
|
|||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
| Key | Format |
|
| Key | Format |
|
||||||
|------------|------------------------------------|
|
|-----------|------------------------------|
|
||||||
| `geoip` | [GeoIP](./geoip) |
|
| `geoip` | [GeoIP](./geoip) |
|
||||||
| `geosite` | [Geosite](./geosite) |
|
| `geosite` | [Geosite](./geosite) |
|
||||||
| `ip_rules` | List of [IP Route Rule](./ip-rule) |
|
| `rules` | List of [Route Rule](./rule) |
|
||||||
| `rules` | List of [Route Rule](./rule) |
|
|
||||||
|
|
||||||
#### final
|
#### final
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
"route": {
|
"route": {
|
||||||
"geoip": {},
|
"geoip": {},
|
||||||
"geosite": {},
|
"geosite": {},
|
||||||
"ip_rules": [],
|
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"final": "",
|
"final": "",
|
||||||
"auto_detect_interface": false,
|
"auto_detect_interface": false,
|
||||||
@@ -20,12 +19,11 @@
|
|||||||
|
|
||||||
### 字段
|
### 字段
|
||||||
|
|
||||||
| 键 | 格式 |
|
| 键 | 格式 |
|
||||||
|------------|-------------------------|
|
|-----------|----------------------|
|
||||||
| `geoip` | [GeoIP](./geoip) |
|
| `geoip` | [GeoIP](./geoip) |
|
||||||
| `geosite` | [GeoSite](./geosite) |
|
| `geosite` | [GeoSite](./geosite) |
|
||||||
| `ip_rules` | 一组 [IP 路由规则](./ip-rule) |
|
| `rules` | 一组 [路由规则](./rule) |
|
||||||
| `rules` | 一组 [路由规则](./rule) |
|
|
||||||
|
|
||||||
#### final
|
#### final
|
||||||
|
|
||||||
@@ -67,4 +65,4 @@
|
|||||||
|
|
||||||
默认为出站连接设置路由标记。
|
默认为出站连接设置路由标记。
|
||||||
|
|
||||||
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"ip_rules": [
|
|
||||||
{
|
|
||||||
"inbound": [
|
|
||||||
"mixed-in"
|
|
||||||
],
|
|
||||||
"ip_version": 6,
|
|
||||||
"network": [
|
|
||||||
"tcp"
|
|
||||||
],
|
|
||||||
"domain": [
|
|
||||||
"test.com"
|
|
||||||
],
|
|
||||||
"domain_suffix": [
|
|
||||||
".cn"
|
|
||||||
],
|
|
||||||
"domain_keyword": [
|
|
||||||
"test"
|
|
||||||
],
|
|
||||||
"domain_regex": [
|
|
||||||
"^stun\\..+"
|
|
||||||
],
|
|
||||||
"geosite": [
|
|
||||||
"cn"
|
|
||||||
],
|
|
||||||
"source_geoip": [
|
|
||||||
"private"
|
|
||||||
],
|
|
||||||
"geoip": [
|
|
||||||
"cn"
|
|
||||||
],
|
|
||||||
"source_ip_cidr": [
|
|
||||||
"10.0.0.0/24",
|
|
||||||
"192.168.0.1"
|
|
||||||
],
|
|
||||||
"ip_cidr": [
|
|
||||||
"10.0.0.0/24",
|
|
||||||
"192.168.0.1"
|
|
||||||
],
|
|
||||||
"source_port": [
|
|
||||||
12345
|
|
||||||
],
|
|
||||||
"source_port_range": [
|
|
||||||
"1000:2000",
|
|
||||||
":3000",
|
|
||||||
"4000:"
|
|
||||||
],
|
|
||||||
"port": [
|
|
||||||
80,
|
|
||||||
443
|
|
||||||
],
|
|
||||||
"port_range": [
|
|
||||||
"1000:2000",
|
|
||||||
":3000",
|
|
||||||
"4000:"
|
|
||||||
],
|
|
||||||
"invert": false,
|
|
||||||
"action": "direct",
|
|
||||||
"outbound": "wireguard"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "logical",
|
|
||||||
"mode": "and",
|
|
||||||
"rules": [],
|
|
||||||
"invert": false,
|
|
||||||
"action": "direct",
|
|
||||||
"outbound": "wireguard"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
You can ignore the JSON Array [] tag when the content is only one item
|
|
||||||
|
|
||||||
### Default Fields
|
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
The default rule uses the following matching logic:
|
|
||||||
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
|
|
||||||
(`port` || `port_range`) &&
|
|
||||||
(`source_geoip` || `source_ip_cidr`) &&
|
|
||||||
(`source_port` || `source_port_range`) &&
|
|
||||||
`other fields`
|
|
||||||
|
|
||||||
#### inbound
|
|
||||||
|
|
||||||
Tags of [Inbound](/configuration/inbound).
|
|
||||||
|
|
||||||
#### ip_version
|
|
||||||
|
|
||||||
4 or 6.
|
|
||||||
|
|
||||||
Not limited if empty.
|
|
||||||
|
|
||||||
#### network
|
|
||||||
|
|
||||||
Match network protocol.
|
|
||||||
|
|
||||||
Available values:
|
|
||||||
|
|
||||||
* `tcp`
|
|
||||||
* `udp`
|
|
||||||
* `icmpv4`
|
|
||||||
* `icmpv6`
|
|
||||||
|
|
||||||
#### domain
|
|
||||||
|
|
||||||
Match full domain.
|
|
||||||
|
|
||||||
#### domain_suffix
|
|
||||||
|
|
||||||
Match domain suffix.
|
|
||||||
|
|
||||||
#### domain_keyword
|
|
||||||
|
|
||||||
Match domain using keyword.
|
|
||||||
|
|
||||||
#### domain_regex
|
|
||||||
|
|
||||||
Match domain using regular expression.
|
|
||||||
|
|
||||||
#### geosite
|
|
||||||
|
|
||||||
Match geosite.
|
|
||||||
|
|
||||||
#### source_geoip
|
|
||||||
|
|
||||||
Match source geoip.
|
|
||||||
|
|
||||||
#### geoip
|
|
||||||
|
|
||||||
Match geoip.
|
|
||||||
|
|
||||||
#### source_ip_cidr
|
|
||||||
|
|
||||||
Match source ip cidr.
|
|
||||||
|
|
||||||
#### ip_cidr
|
|
||||||
|
|
||||||
Match ip cidr.
|
|
||||||
|
|
||||||
#### source_port
|
|
||||||
|
|
||||||
Match source port.
|
|
||||||
|
|
||||||
#### source_port_range
|
|
||||||
|
|
||||||
Match source port range.
|
|
||||||
|
|
||||||
#### port
|
|
||||||
|
|
||||||
Match port.
|
|
||||||
|
|
||||||
#### port_range
|
|
||||||
|
|
||||||
Match port range.
|
|
||||||
|
|
||||||
#### invert
|
|
||||||
|
|
||||||
Invert match result.
|
|
||||||
|
|
||||||
#### action
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
| Action | Description |
|
|
||||||
|--------|--------------------------------------------------------------------|
|
|
||||||
| return | Stop IP routing and assemble the connection to the transport layer |
|
|
||||||
| block | Block the connection |
|
|
||||||
| direct | Directly forward the connection |
|
|
||||||
|
|
||||||
#### outbound
|
|
||||||
|
|
||||||
==Required if action is direct==
|
|
||||||
|
|
||||||
Tag of the target outbound.
|
|
||||||
|
|
||||||
Only outbound which supports IP connection can be used, see [Outbounds that support IP connection](/configuration/outbound/#outbounds-that-support-ip-connection).
|
|
||||||
|
|
||||||
### Logical Fields
|
|
||||||
|
|
||||||
#### type
|
|
||||||
|
|
||||||
`logical`
|
|
||||||
|
|
||||||
#### mode
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
`and` or `or`
|
|
||||||
|
|
||||||
#### rules
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Included default rules.
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"ip_rules": [
|
|
||||||
{
|
|
||||||
"inbound": [
|
|
||||||
"mixed-in"
|
|
||||||
],
|
|
||||||
"ip_version": 6,
|
|
||||||
"network": [
|
|
||||||
"tcp"
|
|
||||||
],
|
|
||||||
"domain": [
|
|
||||||
"test.com"
|
|
||||||
],
|
|
||||||
"domain_suffix": [
|
|
||||||
".cn"
|
|
||||||
],
|
|
||||||
"domain_keyword": [
|
|
||||||
"test"
|
|
||||||
],
|
|
||||||
"domain_regex": [
|
|
||||||
"^stun\\..+"
|
|
||||||
],
|
|
||||||
"geosite": [
|
|
||||||
"cn"
|
|
||||||
],
|
|
||||||
"source_geoip": [
|
|
||||||
"private"
|
|
||||||
],
|
|
||||||
"geoip": [
|
|
||||||
"cn"
|
|
||||||
],
|
|
||||||
"source_ip_cidr": [
|
|
||||||
"10.0.0.0/24",
|
|
||||||
"192.168.0.1"
|
|
||||||
],
|
|
||||||
"ip_cidr": [
|
|
||||||
"10.0.0.0/24",
|
|
||||||
"192.168.0.1"
|
|
||||||
],
|
|
||||||
"source_port": [
|
|
||||||
12345
|
|
||||||
],
|
|
||||||
"source_port_range": [
|
|
||||||
"1000:2000",
|
|
||||||
":3000",
|
|
||||||
"4000:"
|
|
||||||
],
|
|
||||||
"port": [
|
|
||||||
80,
|
|
||||||
443
|
|
||||||
],
|
|
||||||
"port_range": [
|
|
||||||
"1000:2000",
|
|
||||||
":3000",
|
|
||||||
"4000:"
|
|
||||||
],
|
|
||||||
"invert": false,
|
|
||||||
"action": "direct",
|
|
||||||
"outbound": "wireguard"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "logical",
|
|
||||||
"mode": "and",
|
|
||||||
"rules": [],
|
|
||||||
"invert": false,
|
|
||||||
"action": "direct",
|
|
||||||
"outbound": "wireguard"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签。
|
|
||||||
|
|
||||||
### Default Fields
|
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
默认规则使用以下匹配逻辑:
|
|
||||||
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
|
|
||||||
(`port` || `port_range`) &&
|
|
||||||
(`source_geoip` || `source_ip_cidr`) &&
|
|
||||||
(`source_port` || `source_port_range`) &&
|
|
||||||
`other fields`
|
|
||||||
|
|
||||||
#### inbound
|
|
||||||
|
|
||||||
[入站](/zh/configuration/inbound) 标签。
|
|
||||||
|
|
||||||
#### ip_version
|
|
||||||
|
|
||||||
4 或 6。
|
|
||||||
|
|
||||||
默认不限制。
|
|
||||||
|
|
||||||
#### network
|
|
||||||
|
|
||||||
匹配网络协议。
|
|
||||||
|
|
||||||
可用值:
|
|
||||||
|
|
||||||
* `tcp`
|
|
||||||
* `udp`
|
|
||||||
* `icmpv4`
|
|
||||||
* `icmpv6`
|
|
||||||
|
|
||||||
#### domain
|
|
||||||
|
|
||||||
匹配完整域名。
|
|
||||||
|
|
||||||
#### domain_suffix
|
|
||||||
|
|
||||||
匹配域名后缀。
|
|
||||||
|
|
||||||
#### domain_keyword
|
|
||||||
|
|
||||||
匹配域名关键字。
|
|
||||||
|
|
||||||
#### domain_regex
|
|
||||||
|
|
||||||
匹配域名正则表达式。
|
|
||||||
|
|
||||||
#### geosite
|
|
||||||
|
|
||||||
匹配 GeoSite。
|
|
||||||
|
|
||||||
#### source_geoip
|
|
||||||
|
|
||||||
匹配源 GeoIP。
|
|
||||||
|
|
||||||
#### geoip
|
|
||||||
|
|
||||||
匹配 GeoIP。
|
|
||||||
|
|
||||||
#### source_ip_cidr
|
|
||||||
|
|
||||||
匹配源 IP CIDR。
|
|
||||||
|
|
||||||
#### ip_cidr
|
|
||||||
|
|
||||||
匹配 IP CIDR。
|
|
||||||
|
|
||||||
#### source_port
|
|
||||||
|
|
||||||
匹配源端口。
|
|
||||||
|
|
||||||
#### source_port_range
|
|
||||||
|
|
||||||
匹配源端口范围。
|
|
||||||
|
|
||||||
#### port
|
|
||||||
|
|
||||||
匹配端口。
|
|
||||||
|
|
||||||
#### port_range
|
|
||||||
|
|
||||||
匹配端口范围。
|
|
||||||
|
|
||||||
#### invert
|
|
||||||
|
|
||||||
反选匹配结果。
|
|
||||||
|
|
||||||
#### action
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
| Action | 描述 |
|
|
||||||
|--------|---------------------|
|
|
||||||
| return | 停止 IP 路由并将该连接组装到传输层 |
|
|
||||||
| block | 屏蔽该连接 |
|
|
||||||
| direct | 直接转发该连接 |
|
|
||||||
|
|
||||||
|
|
||||||
#### outbound
|
|
||||||
|
|
||||||
==action 为 direct 则必填==
|
|
||||||
|
|
||||||
目标出站的标签。
|
|
||||||
|
|
||||||
### 逻辑字段
|
|
||||||
|
|
||||||
#### type
|
|
||||||
|
|
||||||
`logical`
|
|
||||||
|
|
||||||
#### mode
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
`and` 或 `or`
|
|
||||||
|
|
||||||
#### rules
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
包括的默认规则。
|
|
||||||
@@ -9,9 +9,7 @@
|
|||||||
"mixed-in"
|
"mixed-in"
|
||||||
],
|
],
|
||||||
"ip_version": 6,
|
"ip_version": 6,
|
||||||
"network": [
|
"network": "tcp",
|
||||||
"tcp"
|
|
||||||
],
|
|
||||||
"auth_user": [
|
"auth_user": [
|
||||||
"usera",
|
"usera",
|
||||||
"userb"
|
"userb"
|
||||||
@@ -246,12 +244,18 @@ Tag of the target outbound.
|
|||||||
|
|
||||||
#### mode
|
#### mode
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
`and` or `or`
|
`and` or `or`
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
|
Included default rules.
|
||||||
|
|
||||||
|
#### invert
|
||||||
|
|
||||||
|
Invert match result.
|
||||||
|
|
||||||
|
#### outbound
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
Included default rules.
|
Tag of the target outbound.
|
||||||
|
|||||||
@@ -9,9 +9,7 @@
|
|||||||
"mixed-in"
|
"mixed-in"
|
||||||
],
|
],
|
||||||
"ip_version": 6,
|
"ip_version": 6,
|
||||||
"network": [
|
"network": "tcp",
|
||||||
"tcp"
|
|
||||||
],
|
|
||||||
"auth_user": [
|
"auth_user": [
|
||||||
"usera",
|
"usera",
|
||||||
"userb"
|
"userb"
|
||||||
@@ -244,12 +242,18 @@
|
|||||||
|
|
||||||
#### mode
|
#### mode
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
`and` 或 `or`
|
`and` 或 `or`
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
|
包括的默认规则。
|
||||||
|
|
||||||
|
#### invert
|
||||||
|
|
||||||
|
反选匹配结果。
|
||||||
|
|
||||||
|
#### outbound
|
||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
包括的默认规则。
|
目标出站的标签。
|
||||||
|
|||||||
@@ -10,8 +10,7 @@
|
|||||||
"protocol": "smux",
|
"protocol": "smux",
|
||||||
"max_connections": 4,
|
"max_connections": 4,
|
||||||
"min_streams": 4,
|
"min_streams": 4,
|
||||||
"max_streams": 0,
|
"max_streams": 0
|
||||||
"padding": false
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -29,7 +28,6 @@ Multiplex protocol.
|
|||||||
|----------|------------------------------------|
|
|----------|------------------------------------|
|
||||||
| smux | https://github.com/xtaci/smux |
|
| smux | https://github.com/xtaci/smux |
|
||||||
| yamux | https://github.com/hashicorp/yamux |
|
| yamux | https://github.com/hashicorp/yamux |
|
||||||
| h2mux | https://golang.org/x/net/http2 |
|
|
||||||
|
|
||||||
SMux is used by default.
|
SMux is used by default.
|
||||||
|
|
||||||
@@ -50,12 +48,3 @@ Conflict with `max_streams`.
|
|||||||
Maximum multiplexed streams in a connection before opening a new connection.
|
Maximum multiplexed streams in a connection before opening a new connection.
|
||||||
|
|
||||||
Conflict with `max_connections` and `min_streams`.
|
Conflict with `max_connections` and `min_streams`.
|
||||||
|
|
||||||
#### padding
|
|
||||||
|
|
||||||
!!! info
|
|
||||||
|
|
||||||
Requires sing-box server version 1.3-beta9 or later.
|
|
||||||
|
|
||||||
Enable padding.
|
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
|-------|------------------------------------|
|
|-------|------------------------------------|
|
||||||
| smux | https://github.com/xtaci/smux |
|
| smux | https://github.com/xtaci/smux |
|
||||||
| yamux | https://github.com/hashicorp/yamux |
|
| yamux | https://github.com/hashicorp/yamux |
|
||||||
| h2mux | https://golang.org/x/net/http2 |
|
|
||||||
|
|
||||||
默认使用 SMux。
|
默认使用 SMux。
|
||||||
|
|
||||||
@@ -48,13 +47,4 @@
|
|||||||
|
|
||||||
在打开新连接之前,连接中的最大多路复用流数量。
|
在打开新连接之前,连接中的最大多路复用流数量。
|
||||||
|
|
||||||
与 `max_connections` 和 `min_streams` 冲突。
|
与 `max_connections` 和 `min_streams` 冲突。
|
||||||
|
|
||||||
#### padding
|
|
||||||
|
|
||||||
!!! info
|
|
||||||
|
|
||||||
需要 sing-box 服务器版本 1.3-beta9 或更高。
|
|
||||||
|
|
||||||
启用填充。
|
|
||||||
|
|
||||||
@@ -8,4 +8,3 @@ Configuration examples for sing-box.
|
|||||||
* [Shadowsocks](./shadowsocks)
|
* [Shadowsocks](./shadowsocks)
|
||||||
* [ShadowTLS](./shadowtls)
|
* [ShadowTLS](./shadowtls)
|
||||||
* [Clash API](./clash-api)
|
* [Clash API](./clash-api)
|
||||||
* [WireGuard Direct](./wireguard-direct)
|
|
||||||
|
|||||||
@@ -8,4 +8,3 @@ sing-box 的配置示例。
|
|||||||
* [Shadowsocks](./shadowsocks)
|
* [Shadowsocks](./shadowsocks)
|
||||||
* [ShadowTLS](./shadowtls)
|
* [ShadowTLS](./shadowtls)
|
||||||
* [Clash API](./clash-api)
|
* [Clash API](./clash-api)
|
||||||
* [WireGuard Direct](./wireguard-direct)
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
#### Install
|
#### Install
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone -b main https://github.com/SagerNet/sing-box
|
git clone https://github.com/SagerNet/sing-box
|
||||||
cd sing-box
|
cd sing-box
|
||||||
./release/local/install_go.sh # skip if you have golang already installed
|
./release/local/install_go.sh # skip if you have go1.19 already installed
|
||||||
./release/local/install.sh
|
./release/local/install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
#### 安装
|
#### 安装
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone -b main https://github.com/SagerNet/sing-box
|
git clone https://github.com/SagerNet/sing-box
|
||||||
cd sing-box
|
cd sing-box
|
||||||
./release/local/install_go.sh # 如果已安装 golang 则跳过
|
./release/local/install_go.sh # 如果已安装 go1.19 则跳过
|
||||||
./release/local/install.sh
|
./release/local/install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -23,10 +23,7 @@
|
|||||||
"disable_cache": true
|
"disable_cache": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"outbound": "any",
|
"domain": "mydomain.com",
|
||||||
"server": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"geosite": "cn",
|
"geosite": "cn",
|
||||||
"server": "local"
|
"server": "local"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
# WireGuard Direct
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"tag": "google",
|
|
||||||
"address": "tls://8.8.8.8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tag": "local",
|
|
||||||
"address": "223.5.5.5",
|
|
||||||
"detour": "direct"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"geoip": "cn",
|
|
||||||
"server": "direct"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"reverse_mapping": true
|
|
||||||
},
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "tun",
|
|
||||||
"tag": "tun",
|
|
||||||
"inet4_address": "172.19.0.1/30",
|
|
||||||
"auto_route": true,
|
|
||||||
"sniff": true,
|
|
||||||
"stack": "system"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"type": "wireguard",
|
|
||||||
"tag": "wg",
|
|
||||||
"server": "127.0.0.1",
|
|
||||||
"server_port": 2345,
|
|
||||||
"local_address": [
|
|
||||||
"172.19.0.1/128"
|
|
||||||
],
|
|
||||||
"private_key": "KLTnpPY03pig/WC3zR8U7VWmpANHPFh2/4pwICGJ5Fk=",
|
|
||||||
"peer_public_key": "uvNabcamf6Rs0vzmcw99jsjTJbxo6eWGOykSY66zsUk="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "dns",
|
|
||||||
"tag": "dns"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "direct",
|
|
||||||
"tag": "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "block",
|
|
||||||
"tag": "block"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"ip_rules": [
|
|
||||||
{
|
|
||||||
"port": 53,
|
|
||||||
"action": "return"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"geoip": "cn",
|
|
||||||
"geosite": "cn",
|
|
||||||
"action": "return"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"action": "direct",
|
|
||||||
"outbound": "wg"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"protocol": "dns",
|
|
||||||
"outbound": "dns"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"geoip": "cn",
|
|
||||||
"geosite": "cn",
|
|
||||||
"outbound": "direct"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"auto_detect_interface": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# FakeIP
|
|
||||||
|
|
||||||
FakeIP refers to a type of behavior in a program that simultaneously hijacks both DNS and connection requests. It
|
|
||||||
responds to DNS requests with virtual results and restores mapping when accepting connections.
|
|
||||||
|
|
||||||
#### Advantage
|
|
||||||
|
|
||||||
* Retrieve the requested domain in places like IP routing (L3) where traffic detection is not possible to assist with routing.
|
|
||||||
* Decrease an RTT on the first TCP request to a domain (the most common reason).
|
|
||||||
|
|
||||||
#### Limitation
|
|
||||||
|
|
||||||
* Its mechanism breaks applications that depend on returning correct remote addresses.
|
|
||||||
* Only A and AAAA (IP) requests are supported, which may break applications that rely on other requests.
|
|
||||||
|
|
||||||
#### Recommendation
|
|
||||||
|
|
||||||
* Do not use if you do not need L3 routing.
|
|
||||||
* If using tun, make sure FakeIP ranges is included in the tun's routes.
|
|
||||||
* Enable `experimental.clash_api.store_fakeip` to persist FakeIP records, or use `dns.rules.rewrite_ttl` to avoid losing records after program restart in DNS cached environments.
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# FakeIP
|
|
||||||
|
|
||||||
FakeIP 是指同时劫持 DNS 和连接请求的程序中的一种行为。它通过虚拟结果响应 DNS 请求,在接受连接时恢复映射。
|
|
||||||
|
|
||||||
#### 优点
|
|
||||||
|
|
||||||
* 在像 L3 路由这样无法进行流量探测的地方检索所请求的域名,以协助路由。
|
|
||||||
* 减少对一个域的第一个 TCP 请求的 RTT(这是最常见的原因)。
|
|
||||||
|
|
||||||
#### 限制
|
|
||||||
|
|
||||||
* 它的机制会破坏依赖于返回正确远程地址的应用程序。
|
|
||||||
* 仅支持 A 和 AAAA(IP)请求,这可能会破坏依赖于其他请求的应用程序。
|
|
||||||
|
|
||||||
#### 建议
|
|
||||||
|
|
||||||
* 如果不需要 L3 路由,请勿使用。
|
|
||||||
* 如果使用 tun,请确保 tun 路由中包含 FakeIP 地址范围。
|
|
||||||
* 启用 `experimental.clash_api.store_fakeip` 以持久化 FakeIP 记录,或者使用 `dns.rules.rewrite_ttl` 避免程序重启后在 DNS 被缓存的环境中丢失记录。
|
|
||||||
@@ -11,6 +11,12 @@ it doesn't fit, because it compromises performance or design clarity, or because
|
|||||||
If it bothers you that sing-box is missing feature X, please forgive us and investigate the features that sing-box does
|
If it bothers you that sing-box is missing feature X, please forgive us and investigate the features that sing-box does
|
||||||
have. You might find that they compensate in interesting ways for the lack of X.
|
have. You might find that they compensate in interesting ways for the lack of X.
|
||||||
|
|
||||||
|
#### Fake IP
|
||||||
|
|
||||||
|
Fake IP (also called Fake DNS) is an invasive and imperfect DNS solution that breaks expected behavior, causes DNS leaks
|
||||||
|
and makes many software unusable. It is recommended by some software that lacks DNS processing and caching, but sing-box
|
||||||
|
does not need this.
|
||||||
|
|
||||||
#### Naive outbound
|
#### Naive outbound
|
||||||
|
|
||||||
NaïveProxy's main function is chromium's network stack, and it makes no sense to implement only its transport protocol.
|
NaïveProxy's main function is chromium's network stack, and it makes no sense to implement only its transport protocol.
|
||||||
|
|||||||
@@ -9,6 +9,11 @@
|
|||||||
|
|
||||||
如果 sing-box 缺少功能 X 让您感到困扰,请原谅我们并调查 sing-box 确实有的功能。 您可能会发现它们以有趣的方式弥补了 X 的缺失。
|
如果 sing-box 缺少功能 X 让您感到困扰,请原谅我们并调查 sing-box 确实有的功能。 您可能会发现它们以有趣的方式弥补了 X 的缺失。
|
||||||
|
|
||||||
|
#### Fake IP
|
||||||
|
|
||||||
|
Fake IP(也称 Fake DNS)是一种侵入性和不完善的 DNS 解决方案,它打破了预期的行为,导致 DNS 泄漏并使许多软件无法使用。
|
||||||
|
一些缺乏 DNS 处理和缓存的软件推荐使用它,但 sing-box 不需要。
|
||||||
|
|
||||||
#### Naive 出站
|
#### Naive 出站
|
||||||
|
|
||||||
NaïveProxy 的主要功能是 chromium 的网络栈,仅实现它的传输协议是舍本逐末的。
|
NaïveProxy 的主要功能是 chromium 的网络栈,仅实现它的传输协议是舍本逐末的。
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ the public internet.
|
|||||||
|
|
||||||
`auto-route` cannot automatically hijack DNS requests when Android's `Private DNS` enabled or `strict_route` disabled.
|
`auto-route` cannot automatically hijack DNS requests when Android's `Private DNS` enabled or `strict_route` disabled.
|
||||||
|
|
||||||
|
##### on Linux
|
||||||
|
|
||||||
|
`auto-route` cannot automatically hijack DNS requests with `systemd-resolved` enabled and `strict_route` disabled.
|
||||||
|
|
||||||
#### System proxy
|
#### System proxy
|
||||||
|
|
||||||
##### on Linux
|
##### on Linux
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user