Compare commits
22 Commits
v1.3.0
...
v1.3-beta9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bec606ee88 | ||
|
|
8545e41b2f | ||
|
|
d8810b6e8f | ||
|
|
f568bb9fe3 | ||
|
|
ccb872a41e | ||
|
|
9c287094e2 | ||
|
|
f61c5600e0 | ||
|
|
2e98777f82 | ||
|
|
73e72e9aec | ||
|
|
922acced94 | ||
|
|
988d7331c6 | ||
|
|
c0d6dde95b | ||
|
|
4dbf95875b | ||
|
|
81c4312be8 | ||
|
|
cc94dfaa4b | ||
|
|
c6fc411164 | ||
|
|
be00e19162 | ||
|
|
eb57cbc4ad | ||
|
|
f98cfdf5e4 | ||
|
|
cbf0099681 | ||
|
|
a86afa0e5b | ||
|
|
b5d2062359 |
@@ -14,7 +14,6 @@ builds:
|
|||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
- with_dhcp
|
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_reality_server
|
- with_reality_server
|
||||||
@@ -49,7 +48,6 @@ builds:
|
|||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
- with_dhcp
|
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
|
|||||||
@@ -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_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api,with_acme \
|
&& go build -v -trimpath -tags with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api,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
|
||||||
|
|||||||
8
Makefile
8
Makefile
@@ -1,6 +1,6 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api
|
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
@@ -22,7 +22,7 @@ install:
|
|||||||
fmt:
|
fmt:
|
||||||
@gofumpt -l -w .
|
@gofumpt -l -w .
|
||||||
@gofmt -s -w .
|
@gofmt -s -w .
|
||||||
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
|
@gci write --custom-order -s "standard,prefix(github.com/sagernet/),default" .
|
||||||
|
|
||||||
fmt_install:
|
fmt_install:
|
||||||
go install -v mvdan.cc/gofumpt@latest
|
go install -v mvdan.cc/gofumpt@latest
|
||||||
@@ -48,14 +48,14 @@ proto_install:
|
|||||||
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||||
|
|
||||||
snapshot:
|
snapshot:
|
||||||
go run ./cmd/internal/build goreleaser release --clean --snapshot || exit 1
|
go run ./cmd/internal/build goreleaser release --rm-dist --snapshot || exit 1
|
||||||
mkdir dist/release
|
mkdir dist/release
|
||||||
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
|
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
|
||||||
ghr --delete --draft --prerelease -p 1 nightly dist/release
|
ghr --delete --draft --prerelease -p 1 nightly dist/release
|
||||||
rm -r dist
|
rm -r dist
|
||||||
|
|
||||||
release:
|
release:
|
||||||
go run ./cmd/internal/build goreleaser release --clean --skip-publish || exit 1
|
go run ./cmd/internal/build goreleaser release --rm-dist --skip-publish || exit 1
|
||||||
mkdir dist/release
|
mkdir dist/release
|
||||||
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
|
mv dist/*.tar.gz dist/*.zip dist/*.deb dist/*.rpm dist/release
|
||||||
ghr --delete --draft --prerelease -p 3 $(shell git describe --tags) dist/release
|
ghr --delete --draft --prerelease -p 3 $(shell git describe --tags) dist/release
|
||||||
|
|||||||
@@ -21,8 +21,3 @@ type FakeIPStorage interface {
|
|||||||
FakeIPLoad(address netip.Addr) (string, bool)
|
FakeIPLoad(address netip.Addr) (string, bool)
|
||||||
FakeIPReset() error
|
FakeIPReset() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type FakeIPTransport interface {
|
|
||||||
dns.Transport
|
|
||||||
Store() FakeIPStore
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ type InboundContext struct {
|
|||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
ProcessInfo *process.Info
|
ProcessInfo *process.Info
|
||||||
FakeIP bool
|
|
||||||
|
|
||||||
// dns cache
|
// dns cache
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,8 +14,12 @@ type Outbound interface {
|
|||||||
Type() string
|
Type() string
|
||||||
Tag() string
|
Tag() string
|
||||||
Network() []string
|
Network() []string
|
||||||
Dependencies() []string
|
|
||||||
N.Dialer
|
N.Dialer
|
||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IPOutbound interface {
|
||||||
|
Outbound
|
||||||
|
NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) (tun.DirectDestination, error)
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ type Router interface {
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
NatRequired(outbound string) bool
|
||||||
|
|
||||||
GeoIPReader() *geoip.Reader
|
GeoIPReader() *geoip.Reader
|
||||||
LoadGeosite(code string) (Rule, error)
|
LoadGeosite(code string) (Rule, error)
|
||||||
@@ -42,7 +45,9 @@ type Router interface {
|
|||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
|
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
|
IPRules() []IPRule
|
||||||
|
|
||||||
TimeService
|
TimeService
|
||||||
|
|
||||||
@@ -51,8 +56,6 @@ type Router interface {
|
|||||||
|
|
||||||
V2RayServer() V2RayServer
|
V2RayServer() V2RayServer
|
||||||
SetV2RayServer(server V2RayServer)
|
SetV2RayServer(server V2RayServer)
|
||||||
|
|
||||||
ResetNetwork() error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type routerContextKey struct{}
|
type routerContextKey struct{}
|
||||||
@@ -84,6 +87,11 @@ type DNSRule interface {
|
|||||||
RewriteTTL() *uint32
|
RewriteTTL() *uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IPRule interface {
|
||||||
|
Rule
|
||||||
|
Action() tun.ActionType
|
||||||
|
}
|
||||||
|
|
||||||
type InterfaceUpdateListener interface {
|
type InterfaceUpdateListener interface {
|
||||||
InterfaceUpdated() error
|
InterfaceUpdated() error
|
||||||
}
|
}
|
||||||
|
|||||||
17
box.go
17
box.go
@@ -217,9 +217,20 @@ func (s *Box) preStart() error {
|
|||||||
return E.Cause(err, "pre-starting ", serviceName)
|
return E.Cause(err, "pre-starting ", serviceName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := s.startOutbounds()
|
for i, out := range s.outbounds {
|
||||||
if err != nil {
|
var tag string
|
||||||
return err
|
if out.Tag() == "" {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
tag = out.Tag()
|
||||||
|
}
|
||||||
|
if starter, isStarter := out.(common.Starter); isStarter {
|
||||||
|
s.logger.Trace("initializing outbound/", out.Type(), "[", tag, "]")
|
||||||
|
err := starter.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return s.router.Start()
|
return s.router.Start()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
package box
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Box) startOutbounds() error {
|
|
||||||
outboundTags := make(map[adapter.Outbound]string)
|
|
||||||
outbounds := make(map[string]adapter.Outbound)
|
|
||||||
for i, outboundToStart := range s.outbounds {
|
|
||||||
var outboundTag string
|
|
||||||
if outboundToStart.Tag() == "" {
|
|
||||||
outboundTag = F.ToString(i)
|
|
||||||
} else {
|
|
||||||
outboundTag = outboundToStart.Tag()
|
|
||||||
}
|
|
||||||
if _, exists := outbounds[outboundTag]; exists {
|
|
||||||
return E.New("outbound tag ", outboundTag, " duplicated")
|
|
||||||
}
|
|
||||||
outboundTags[outboundToStart] = outboundTag
|
|
||||||
outbounds[outboundTag] = outboundToStart
|
|
||||||
}
|
|
||||||
started := make(map[string]bool)
|
|
||||||
for {
|
|
||||||
canContinue := false
|
|
||||||
startOne:
|
|
||||||
for _, outboundToStart := range s.outbounds {
|
|
||||||
outboundTag := outboundTags[outboundToStart]
|
|
||||||
if started[outboundTag] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dependencies := outboundToStart.Dependencies()
|
|
||||||
for _, dependency := range dependencies {
|
|
||||||
if !started[dependency] {
|
|
||||||
continue startOne
|
|
||||||
}
|
|
||||||
}
|
|
||||||
started[outboundTag] = true
|
|
||||||
canContinue = true
|
|
||||||
if starter, isStarter := outboundToStart.(common.Starter); isStarter {
|
|
||||||
s.logger.Trace("initializing outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
|
||||||
err := starter.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(started) == len(s.outbounds) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if canContinue {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
currentOutbound := common.Find(s.outbounds, func(it adapter.Outbound) bool {
|
|
||||||
return !started[outboundTags[it]]
|
|
||||||
})
|
|
||||||
var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error
|
|
||||||
lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {
|
|
||||||
problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
|
|
||||||
return !started[it]
|
|
||||||
})
|
|
||||||
if common.Contains(oTree, problemOutboundTag) {
|
|
||||||
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
|
|
||||||
}
|
|
||||||
problemOutbound := outbounds[problemOutboundTag]
|
|
||||||
if problemOutbound == nil {
|
|
||||||
return E.New("dependency[", problemOutbound, "] not found for outbound[", outboundTags[oCurrent], "]")
|
|
||||||
}
|
|
||||||
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
|
|
||||||
}
|
|
||||||
return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/build"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
@@ -12,10 +11,6 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
build_shared.FindSDK()
|
build_shared.FindSDK()
|
||||||
|
|
||||||
if os.Getenv("build.Default.GOPATH") == "" {
|
|
||||||
os.Setenv("GOPATH", build.Default.GOPATH)
|
|
||||||
}
|
|
||||||
|
|
||||||
command := exec.Command(os.Args[1], os.Args[2:]...)
|
command := exec.Command(os.Args[1], os.Args[2:]...)
|
||||||
command.Stdout = os.Stdout
|
command.Stdout = os.Stdout
|
||||||
command.Stderr = os.Stderr
|
command.Stderr = os.Stderr
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
_ "github.com/sagernet/gomobile/event/key"
|
_ "github.com/sagernet/gomobile/event/key"
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
||||||
@@ -39,24 +38,18 @@ func main() {
|
|||||||
var (
|
var (
|
||||||
sharedFlags []string
|
sharedFlags []string
|
||||||
debugFlags []string
|
debugFlags []string
|
||||||
sharedTags []string
|
|
||||||
iosTags []string
|
|
||||||
debugTags []string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
sharedFlags = append(sharedFlags, "-trimpath")
|
sharedFlags = append(sharedFlags, "-trimpath")
|
||||||
sharedFlags = append(sharedFlags, "-ldflags")
|
sharedFlags = append(sharedFlags, "-ldflags")
|
||||||
|
|
||||||
currentTag, err := build_shared.ReadTag()
|
currentTag, err := build_shared.ReadTag()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
currentTag = "unknown"
|
currentTag = "unknown"
|
||||||
}
|
}
|
||||||
sharedFlags = append(sharedFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
sharedFlags = append(sharedFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||||
debugFlags = append(debugFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
debugFlags = append(debugFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
|
|
||||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
|
||||||
debugTags = append(debugTags, "debug")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAndroid() {
|
func buildAndroid() {
|
||||||
@@ -77,9 +70,9 @@ func buildAndroid() {
|
|||||||
|
|
||||||
args = append(args, "-tags")
|
args = append(args, "-tags")
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, strings.Join(sharedTags, ","))
|
args = append(args, "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api")
|
||||||
} else {
|
} else {
|
||||||
args = append(args, strings.Join(append(sharedTags, debugTags...), ","))
|
args = append(args, "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,debug")
|
||||||
}
|
}
|
||||||
args = append(args, "./experimental/libbox")
|
args = append(args, "./experimental/libbox")
|
||||||
|
|
||||||
@@ -116,12 +109,11 @@ func buildiOS() {
|
|||||||
args = append(args, debugFlags...)
|
args = append(args, debugFlags...)
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, iosTags...)
|
|
||||||
args = append(args, "-tags")
|
args = append(args, "-tags")
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
args = append(args, strings.Join(tags, ","))
|
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack")
|
||||||
} else {
|
} else {
|
||||||
args = append(args, strings.Join(append(tags, debugTags...), ","))
|
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack,debug")
|
||||||
}
|
}
|
||||||
args = append(args, "./experimental/libbox")
|
args = append(args, "./experimental/libbox")
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -43,7 +42,7 @@ func (c *PacketConn) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *PacketConn) Upstream() any {
|
func (c *PacketConn) Upstream() any {
|
||||||
return bufio.NewPacketConn(c.PacketConn)
|
return c.PacketConn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PacketConn) ReaderReplaceable() bool {
|
func (c *PacketConn) ReaderReplaceable() bool {
|
||||||
|
|||||||
@@ -1,21 +1,554 @@
|
|||||||
package mux
|
package mux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-mux"
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/x/list"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewClientWithOptions(dialer N.Dialer, options option.MultiplexOptions) (*Client, error) {
|
var _ N.Dialer = (*Client)(nil)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
access sync.Mutex
|
||||||
|
connections list.List[abstractSession]
|
||||||
|
ctx context.Context
|
||||||
|
dialer N.Dialer
|
||||||
|
protocol Protocol
|
||||||
|
maxConnections int
|
||||||
|
minStreams 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) {
|
||||||
|
return &Client{
|
||||||
|
ctx: ctx,
|
||||||
|
dialer: dialer,
|
||||||
|
protocol: protocol,
|
||||||
|
maxConnections: maxConnections,
|
||||||
|
minStreams: minStreams,
|
||||||
|
maxStreams: maxStreams,
|
||||||
|
paddingEnabled: paddingEnabled,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientWithOptions(ctx context.Context, dialer N.Dialer, options option.MultiplexOptions) (*Client, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return mux.NewClient(mux.Options{
|
if options.MaxConnections == 0 && options.MaxStreams == 0 {
|
||||||
Dialer: dialer,
|
options.MinStreams = 8
|
||||||
Protocol: options.Protocol,
|
}
|
||||||
MaxConnections: options.MaxConnections,
|
protocol, err := ParseProtocol(options.Protocol)
|
||||||
MinStreams: options.MinStreams,
|
if err != nil {
|
||||||
MaxStreams: options.MaxStreams,
|
return nil, err
|
||||||
Padding: options.Padding,
|
}
|
||||||
})
|
return NewClient(ctx, dialer, protocol, options.MaxConnections, options.MinStreams, options.MaxStreams, options.Padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
switch N.NetworkName(network) {
|
||||||
|
case N.NetworkTCP:
|
||||||
|
stream, err := c.openStream()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ClientConn{Conn: stream, destination: destination}, nil
|
||||||
|
case N.NetworkUDP:
|
||||||
|
stream, err := c.openStream()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bufio.NewUnbindPacketConn(&ClientPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}), nil
|
||||||
|
default:
|
||||||
|
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
stream, err := c.openStream()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ClientPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) openStream() (net.Conn, error) {
|
||||||
|
var (
|
||||||
|
session abstractSession
|
||||||
|
stream net.Conn
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for attempts := 0; attempts < 2; attempts++ {
|
||||||
|
session, err = c.offer()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stream, err = session.Open()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &wrapStream{stream}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) offer() (abstractSession, error) {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
|
||||||
|
sessions := make([]abstractSession, 0, c.maxConnections)
|
||||||
|
for element := c.connections.Front(); element != nil; {
|
||||||
|
if element.Value.IsClosed() {
|
||||||
|
nextElement := element.Next()
|
||||||
|
c.connections.Remove(element)
|
||||||
|
element = nextElement
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sessions = append(sessions, element.Value)
|
||||||
|
element = element.Next()
|
||||||
|
}
|
||||||
|
session := common.MinBy(common.Filter(sessions, abstractSession.CanTakeNewRequest), abstractSession.NumStreams)
|
||||||
|
if session == nil {
|
||||||
|
return c.offerNew()
|
||||||
|
}
|
||||||
|
numStreams := session.NumStreams()
|
||||||
|
if numStreams == 0 {
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
if c.maxConnections > 0 {
|
||||||
|
if len(sessions) >= c.maxConnections || numStreams < c.minStreams {
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if c.maxStreams > 0 && numStreams < c.maxStreams {
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.offerNew()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) offerNew() (abstractSession, error) {
|
||||||
|
conn, err := c.dialer.DialContext(c.ctx, N.NetworkTCP, Destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var version byte
|
||||||
|
if c.paddingEnabled {
|
||||||
|
version = Version1
|
||||||
|
} else {
|
||||||
|
version = Version0
|
||||||
|
}
|
||||||
|
conn = newProtocolConn(conn, Request{
|
||||||
|
Version: version,
|
||||||
|
Protocol: c.protocol,
|
||||||
|
PaddingEnabled: c.paddingEnabled,
|
||||||
|
})
|
||||||
|
if c.paddingEnabled {
|
||||||
|
conn = newPaddingConn(conn)
|
||||||
|
}
|
||||||
|
session, err := c.protocol.newClient(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.connections.PushBack(session)
|
||||||
|
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 {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
|
for _, session := range c.connections.Array() {
|
||||||
|
session.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientConn struct {
|
||||||
|
net.Conn
|
||||||
|
destination M.Socksaddr
|
||||||
|
requestWrite bool
|
||||||
|
responseRead bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) readResponse() error {
|
||||||
|
response, err := ReadStreamResponse(c.Conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.Status == statusError {
|
||||||
|
return E.New("remote error: ", response.Message)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) Read(b []byte) (n int, err error) {
|
||||||
|
if !c.responseRead {
|
||||||
|
err = c.readResponse()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.responseRead = true
|
||||||
|
}
|
||||||
|
return c.Conn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) Write(b []byte) (n int, err error) {
|
||||||
|
if c.requestWrite {
|
||||||
|
return c.Conn.Write(b)
|
||||||
|
}
|
||||||
|
request := StreamRequest{
|
||||||
|
Network: N.NetworkTCP,
|
||||||
|
Destination: c.destination,
|
||||||
|
}
|
||||||
|
_buffer := buf.StackNewSize(streamRequestLen(request) + len(b))
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
EncodeStreamRequest(request, buffer)
|
||||||
|
buffer.Write(b)
|
||||||
|
_, err = c.Conn.Write(buffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.requestWrite = true
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
if !c.requestWrite {
|
||||||
|
return bufio.ReadFrom0(c, r)
|
||||||
|
}
|
||||||
|
return bufio.Copy(c.Conn, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
if !c.responseRead {
|
||||||
|
return bufio.WriteTo0(c, w)
|
||||||
|
}
|
||||||
|
return bufio.Copy(w, c.Conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) LocalAddr() net.Addr {
|
||||||
|
return c.Conn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) RemoteAddr() net.Addr {
|
||||||
|
return c.destination.TCPAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) ReaderReplaceable() bool {
|
||||||
|
return c.responseRead
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) WriterReplaceable() bool {
|
||||||
|
return c.requestWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) NeedAdditionalReadDeadline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConn) Upstream() any {
|
||||||
|
return c.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientPacketConn struct {
|
||||||
|
N.ExtendedConn
|
||||||
|
destination M.Socksaddr
|
||||||
|
requestWrite bool
|
||||||
|
responseRead bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) readResponse() error {
|
||||||
|
response, err := ReadStreamResponse(c.ExtendedConn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.Status == statusError {
|
||||||
|
return E.New("remote error: ", response.Message)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) Read(b []byte) (n int, err error) {
|
||||||
|
if !c.responseRead {
|
||||||
|
err = c.readResponse()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.responseRead = true
|
||||||
|
}
|
||||||
|
var length uint16
|
||||||
|
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(b) < int(length) {
|
||||||
|
return 0, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
return io.ReadFull(c.ExtendedConn, b[:length])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) writeRequest(payload []byte) (n int, err error) {
|
||||||
|
request := StreamRequest{
|
||||||
|
Network: N.NetworkUDP,
|
||||||
|
Destination: c.destination,
|
||||||
|
}
|
||||||
|
rLen := streamRequestLen(request)
|
||||||
|
if len(payload) > 0 {
|
||||||
|
rLen += 2 + len(payload)
|
||||||
|
}
|
||||||
|
_buffer := buf.StackNewSize(rLen)
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
EncodeStreamRequest(request, buffer)
|
||||||
|
if len(payload) > 0 {
|
||||||
|
common.Must(
|
||||||
|
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
|
||||||
|
common.Error(buffer.Write(payload)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_, err = c.ExtendedConn.Write(buffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.requestWrite = true
|
||||||
|
return len(payload), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) Write(b []byte) (n int, err error) {
|
||||||
|
if !c.requestWrite {
|
||||||
|
return c.writeRequest(b)
|
||||||
|
}
|
||||||
|
err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(b)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return c.ExtendedConn.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) ReadBuffer(buffer *buf.Buffer) (err error) {
|
||||||
|
if !c.responseRead {
|
||||||
|
err = c.readResponse()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.responseRead = true
|
||||||
|
}
|
||||||
|
var length uint16
|
||||||
|
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
|
if !c.requestWrite {
|
||||||
|
defer buffer.Release()
|
||||||
|
return common.Error(c.writeRequest(buffer.Bytes()))
|
||||||
|
}
|
||||||
|
bLen := buffer.Len()
|
||||||
|
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(bLen))
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) FrontHeadroom() int {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
err = c.ReadBuffer(buffer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
return c.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) LocalAddr() net.Addr {
|
||||||
|
return c.ExtendedConn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) RemoteAddr() net.Addr {
|
||||||
|
return c.destination.UDPAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) NeedAdditionalReadDeadline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketConn) Upstream() any {
|
||||||
|
return c.ExtendedConn
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ N.NetPacketConn = (*ClientPacketAddrConn)(nil)
|
||||||
|
|
||||||
|
type ClientPacketAddrConn struct {
|
||||||
|
N.ExtendedConn
|
||||||
|
destination M.Socksaddr
|
||||||
|
requestWrite bool
|
||||||
|
responseRead bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) readResponse() error {
|
||||||
|
response, err := ReadStreamResponse(c.ExtendedConn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if response.Status == statusError {
|
||||||
|
return E.New("remote error: ", response.Message)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
|
if !c.responseRead {
|
||||||
|
err = c.readResponse()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.responseRead = true
|
||||||
|
}
|
||||||
|
destination, err := M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if destination.IsFqdn() {
|
||||||
|
addr = destination
|
||||||
|
} else {
|
||||||
|
addr = destination.UDPAddr()
|
||||||
|
}
|
||||||
|
var length uint16
|
||||||
|
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cap(p) < int(length) {
|
||||||
|
return 0, nil, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
n, err = io.ReadFull(c.ExtendedConn, p[:length])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) writeRequest(payload []byte, destination M.Socksaddr) (n int, err error) {
|
||||||
|
request := StreamRequest{
|
||||||
|
Network: N.NetworkUDP,
|
||||||
|
Destination: c.destination,
|
||||||
|
PacketAddr: true,
|
||||||
|
}
|
||||||
|
rLen := streamRequestLen(request)
|
||||||
|
if len(payload) > 0 {
|
||||||
|
rLen += M.SocksaddrSerializer.AddrPortLen(destination) + 2 + len(payload)
|
||||||
|
}
|
||||||
|
_buffer := buf.StackNewSize(rLen)
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
EncodeStreamRequest(request, buffer)
|
||||||
|
if len(payload) > 0 {
|
||||||
|
common.Must(
|
||||||
|
M.SocksaddrSerializer.WriteAddrPort(buffer, destination),
|
||||||
|
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
|
||||||
|
common.Error(buffer.Write(payload)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_, err = c.ExtendedConn.Write(buffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.requestWrite = true
|
||||||
|
return len(payload), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
if !c.requestWrite {
|
||||||
|
return c.writeRequest(p, M.SocksaddrFromNet(addr))
|
||||||
|
}
|
||||||
|
err = M.SocksaddrSerializer.WriteAddrPort(c.ExtendedConn, M.SocksaddrFromNet(addr))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(p)))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return c.ExtendedConn.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
if !c.responseRead {
|
||||||
|
err = c.readResponse()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.responseRead = true
|
||||||
|
}
|
||||||
|
destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var length uint16
|
||||||
|
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
if !c.requestWrite {
|
||||||
|
defer buffer.Release()
|
||||||
|
return common.Error(c.writeRequest(buffer.Bytes(), destination))
|
||||||
|
}
|
||||||
|
bLen := buffer.Len()
|
||||||
|
header := buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination) + 2))
|
||||||
|
common.Must(
|
||||||
|
M.SocksaddrSerializer.WriteAddrPort(header, destination),
|
||||||
|
binary.Write(header, binary.BigEndian, uint16(bLen)),
|
||||||
|
)
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) LocalAddr() net.Addr {
|
||||||
|
return c.ExtendedConn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) FrontHeadroom() int {
|
||||||
|
return 2 + M.MaxSocksaddrLength
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) NeedAdditionalReadDeadline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientPacketAddrConn) Upstream() any {
|
||||||
|
return c.ExtendedConn
|
||||||
}
|
}
|
||||||
|
|||||||
235
common/mux/h2mux.go
Normal file
235
common/mux/h2mux.go
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
240
common/mux/padding.go
Normal file
240
common/mux/padding.go
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
@@ -1,14 +1,299 @@
|
|||||||
package mux
|
package mux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sagernet/sing-mux"
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
"github.com/sagernet/smux"
|
||||||
|
|
||||||
|
"github.com/hashicorp/yamux"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
var Destination = M.Socksaddr{
|
||||||
Client = mux.Client
|
Fqdn: "sp.mux.sing-box.arpa",
|
||||||
|
Port: 444,
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProtocolSMux Protocol = iota
|
||||||
|
ProtocolYAMux
|
||||||
|
ProtocolH2Mux
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type Protocol byte
|
||||||
Destination = mux.Destination
|
|
||||||
HandleConnection = mux.HandleConnection
|
func ParseProtocol(name string) (Protocol, error) {
|
||||||
|
switch name {
|
||||||
|
case "", "smux":
|
||||||
|
return ProtocolSMux, nil
|
||||||
|
case "yamux":
|
||||||
|
return ProtocolYAMux, nil
|
||||||
|
case "h2mux":
|
||||||
|
return ProtocolH2Mux, nil
|
||||||
|
default:
|
||||||
|
return ProtocolSMux, E.New("unknown multiplex protocol: ", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Protocol) newServer(conn net.Conn) (abstractSession, error) {
|
||||||
|
switch p {
|
||||||
|
case ProtocolSMux:
|
||||||
|
session, err := smux.Server(conn, smuxConfig())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &smuxSession{session}, nil
|
||||||
|
case ProtocolYAMux:
|
||||||
|
session, err := yamux.Server(conn, yaMuxConfig())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &yamuxSession{session}, nil
|
||||||
|
case ProtocolH2Mux:
|
||||||
|
return NewH2MuxServer(conn), nil
|
||||||
|
default:
|
||||||
|
panic("unknown protocol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Protocol) newClient(conn net.Conn) (abstractSession, error) {
|
||||||
|
switch p {
|
||||||
|
case ProtocolSMux:
|
||||||
|
session, err := smux.Client(conn, smuxConfig())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &smuxSession{session}, nil
|
||||||
|
case ProtocolYAMux:
|
||||||
|
session, err := yamux.Client(conn, yaMuxConfig())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &yamuxSession{session}, nil
|
||||||
|
case ProtocolH2Mux:
|
||||||
|
return NewH2MuxClient(conn)
|
||||||
|
default:
|
||||||
|
panic("unknown protocol")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func smuxConfig() *smux.Config {
|
||||||
|
config := smux.DefaultConfig()
|
||||||
|
config.KeepAliveDisabled = true
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func yaMuxConfig() *yamux.Config {
|
||||||
|
config := yamux.DefaultConfig()
|
||||||
|
config.LogOutput = io.Discard
|
||||||
|
config.StreamCloseTimeout = C.TCPTimeout
|
||||||
|
config.StreamOpenTimeout = C.TCPTimeout
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Protocol) String() string {
|
||||||
|
switch p {
|
||||||
|
case ProtocolSMux:
|
||||||
|
return "smux"
|
||||||
|
case ProtocolYAMux:
|
||||||
|
return "yamux"
|
||||||
|
case ProtocolH2Mux:
|
||||||
|
return "h2mux"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
Version0 = iota
|
||||||
|
Version1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
Version byte
|
||||||
|
Protocol Protocol
|
||||||
|
PaddingEnabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadRequest(reader io.Reader) (*Request, error) {
|
||||||
|
version, err := rw.ReadByte(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if version < Version0 || version > Version1 {
|
||||||
|
return nil, E.New("unsupported version: ", version)
|
||||||
|
}
|
||||||
|
protocol, err := rw.ReadByte(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var paddingEnabled bool
|
||||||
|
if version == Version1 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func EncodeRequest(request Request, payload []byte) *buf.Buffer {
|
||||||
|
var requestLen int
|
||||||
|
requestLen += 2
|
||||||
|
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 (
|
||||||
|
flagUDP = 1
|
||||||
|
flagAddr = 2
|
||||||
|
statusSuccess = 0
|
||||||
|
statusError = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type StreamRequest struct {
|
||||||
|
Network string
|
||||||
|
Destination M.Socksaddr
|
||||||
|
PacketAddr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadStreamRequest(reader io.Reader) (*StreamRequest, error) {
|
||||||
|
var flags uint16
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &flags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
destination, err := M.SocksaddrSerializer.ReadAddrPort(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var network string
|
||||||
|
var udpAddr bool
|
||||||
|
if flags&flagUDP == 0 {
|
||||||
|
network = N.NetworkTCP
|
||||||
|
} else {
|
||||||
|
network = N.NetworkUDP
|
||||||
|
udpAddr = flags&flagAddr != 0
|
||||||
|
}
|
||||||
|
return &StreamRequest{network, destination, udpAddr}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func streamRequestLen(request StreamRequest) int {
|
||||||
|
var rLen int
|
||||||
|
rLen += 1 // version
|
||||||
|
rLen += 2 // flags
|
||||||
|
rLen += M.SocksaddrSerializer.AddrPortLen(request.Destination)
|
||||||
|
return rLen
|
||||||
|
}
|
||||||
|
|
||||||
|
func EncodeStreamRequest(request StreamRequest, buffer *buf.Buffer) {
|
||||||
|
destination := request.Destination
|
||||||
|
var flags uint16
|
||||||
|
if request.Network == N.NetworkUDP {
|
||||||
|
flags |= flagUDP
|
||||||
|
}
|
||||||
|
if request.PacketAddr {
|
||||||
|
flags |= flagAddr
|
||||||
|
if !destination.IsValid() {
|
||||||
|
destination = Destination
|
||||||
|
}
|
||||||
|
}
|
||||||
|
common.Must(
|
||||||
|
binary.Write(buffer, binary.BigEndian, flags),
|
||||||
|
M.SocksaddrSerializer.WriteAddrPort(buffer, destination),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamResponse struct {
|
||||||
|
Status uint8
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadStreamResponse(reader io.Reader) (*StreamResponse, error) {
|
||||||
|
var response StreamResponse
|
||||||
|
status, err := rw.ReadByte(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response.Status = status
|
||||||
|
if status == statusError {
|
||||||
|
response.Message, err = rw.ReadVString(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrapStream struct {
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapStream) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = w.Conn.Read(p)
|
||||||
|
err = wrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapStream) Write(p []byte) (n int, err error) {
|
||||||
|
n, err = w.Conn.Write(p)
|
||||||
|
err = wrapError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapStream) WriteIsThreadUnsafe() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapStream) Upstream() any {
|
||||||
|
return w.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapError(err error) error {
|
||||||
|
switch err {
|
||||||
|
case yamux.ErrStreamClosed:
|
||||||
|
return io.EOF
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
272
common/mux/service.go
Normal file
272
common/mux/service.go
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
"github.com/sagernet/sing/common/task"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
request, err := ReadRequest(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if request.PaddingEnabled {
|
||||||
|
conn = newPaddingConn(conn)
|
||||||
|
}
|
||||||
|
session, err := request.Protocol.newServer(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var group task.Group
|
||||||
|
group.Append0(func(ctx context.Context) error {
|
||||||
|
var stream net.Conn
|
||||||
|
for {
|
||||||
|
stream, err = session.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go newConnection(ctx, router, errorHandler, logger, stream, metadata)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
group.Cleanup(func() {
|
||||||
|
session.Close()
|
||||||
|
})
|
||||||
|
return group.Run(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, stream net.Conn, metadata adapter.InboundContext) {
|
||||||
|
stream = &wrapStream{stream}
|
||||||
|
request, err := ReadStreamRequest(stream)
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorContext(ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
metadata.Destination = request.Destination
|
||||||
|
if request.Network == N.NetworkTCP {
|
||||||
|
logger.InfoContext(ctx, "inbound multiplex connection to ", metadata.Destination)
|
||||||
|
hErr := router.RouteConnection(ctx, &ServerConn{ExtendedConn: bufio.NewExtendedConn(stream)}, metadata)
|
||||||
|
stream.Close()
|
||||||
|
if hErr != nil {
|
||||||
|
errorHandler.NewError(ctx, hErr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var packetConn N.PacketConn
|
||||||
|
if !request.PacketAddr {
|
||||||
|
logger.InfoContext(ctx, "inbound multiplex packet connection to ", metadata.Destination)
|
||||||
|
packetConn = &ServerPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: request.Destination}
|
||||||
|
} else {
|
||||||
|
logger.InfoContext(ctx, "inbound multiplex packet connection")
|
||||||
|
packetConn = &ServerPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream)}
|
||||||
|
}
|
||||||
|
hErr := router.RoutePacketConnection(ctx, packetConn, metadata)
|
||||||
|
stream.Close()
|
||||||
|
if hErr != nil {
|
||||||
|
errorHandler.NewError(ctx, hErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ N.HandshakeConn = (*ServerConn)(nil)
|
||||||
|
|
||||||
|
type ServerConn struct {
|
||||||
|
N.ExtendedConn
|
||||||
|
responseWrite bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) HandshakeFailure(err error) error {
|
||||||
|
errMessage := err.Error()
|
||||||
|
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
common.Must(
|
||||||
|
buffer.WriteByte(statusError),
|
||||||
|
rw.WriteVString(_buffer, errMessage),
|
||||||
|
)
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) Write(b []byte) (n int, err error) {
|
||||||
|
if c.responseWrite {
|
||||||
|
return c.ExtendedConn.Write(b)
|
||||||
|
}
|
||||||
|
_buffer := buf.StackNewSize(1 + len(b))
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
common.Must(
|
||||||
|
buffer.WriteByte(statusSuccess),
|
||||||
|
common.Error(buffer.Write(b)),
|
||||||
|
)
|
||||||
|
_, err = c.ExtendedConn.Write(buffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.responseWrite = true
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
|
if c.responseWrite {
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
buffer.ExtendHeader(1)[0] = statusSuccess
|
||||||
|
c.responseWrite = true
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) FrontHeadroom() int {
|
||||||
|
if !c.responseWrite {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) NeedAdditionalReadDeadline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerConn) Upstream() any {
|
||||||
|
return c.ExtendedConn
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ N.HandshakeConn = (*ServerPacketConn)(nil)
|
||||||
|
_ N.PacketConn = (*ServerPacketConn)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerPacketConn struct {
|
||||||
|
N.ExtendedConn
|
||||||
|
destination M.Socksaddr
|
||||||
|
responseWrite bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketConn) HandshakeFailure(err error) error {
|
||||||
|
errMessage := err.Error()
|
||||||
|
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
common.Must(
|
||||||
|
buffer.WriteByte(statusError),
|
||||||
|
rw.WriteVString(_buffer, errMessage),
|
||||||
|
)
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
var length uint16
|
||||||
|
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
destination = c.destination
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
pLen := buffer.Len()
|
||||||
|
common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen)))
|
||||||
|
if !c.responseWrite {
|
||||||
|
buffer.ExtendHeader(1)[0] = statusSuccess
|
||||||
|
c.responseWrite = true
|
||||||
|
}
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketConn) NeedAdditionalReadDeadline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketConn) Upstream() any {
|
||||||
|
return c.ExtendedConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketConn) FrontHeadroom() int {
|
||||||
|
if !c.responseWrite {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ N.HandshakeConn = (*ServerPacketAddrConn)(nil)
|
||||||
|
_ N.PacketConn = (*ServerPacketAddrConn)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerPacketAddrConn struct {
|
||||||
|
N.ExtendedConn
|
||||||
|
responseWrite bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketAddrConn) HandshakeFailure(err error) error {
|
||||||
|
errMessage := err.Error()
|
||||||
|
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
|
common.Must(
|
||||||
|
buffer.WriteByte(statusError),
|
||||||
|
rw.WriteVString(_buffer, errMessage),
|
||||||
|
)
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var length uint16
|
||||||
|
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
pLen := buffer.Len()
|
||||||
|
common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen)))
|
||||||
|
common.Must(M.SocksaddrSerializer.WriteAddrPort(buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination))), destination))
|
||||||
|
if !c.responseWrite {
|
||||||
|
buffer.ExtendHeader(1)[0] = statusSuccess
|
||||||
|
c.responseWrite = true
|
||||||
|
}
|
||||||
|
return c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketAddrConn) NeedAdditionalReadDeadline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketAddrConn) Upstream() any {
|
||||||
|
return c.ExtendedConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServerPacketAddrConn) FrontHeadroom() int {
|
||||||
|
if !c.responseWrite {
|
||||||
|
return 3 + M.MaxSocksaddrLength
|
||||||
|
}
|
||||||
|
return 2 + M.MaxSocksaddrLength
|
||||||
|
}
|
||||||
111
common/mux/session.go
Normal file
111
common/mux/session.go
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/smux"
|
||||||
|
|
||||||
|
"github.com/hashicorp/yamux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type abstractSession interface {
|
||||||
|
Open() (net.Conn, error)
|
||||||
|
Accept() (net.Conn, error)
|
||||||
|
NumStreams() int
|
||||||
|
Close() error
|
||||||
|
IsClosed() bool
|
||||||
|
CanTakeNewRequest() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ abstractSession = (*smuxSession)(nil)
|
||||||
|
|
||||||
|
type smuxSession struct {
|
||||||
|
*smux.Session
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *smuxSession) Open() (net.Conn, error) {
|
||||||
|
return s.OpenStream()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *smuxSession) Accept() (net.Conn, error) {
|
||||||
|
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 {
|
||||||
|
net.Conn
|
||||||
|
request Request
|
||||||
|
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) {
|
||||||
|
if c.protocolWritten {
|
||||||
|
return c.Conn.Write(p)
|
||||||
|
}
|
||||||
|
buffer := EncodeRequest(c.request, p)
|
||||||
|
n, err = c.Conn.Write(buffer.Bytes())
|
||||||
|
buffer.Release()
|
||||||
|
if err == nil {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
c.protocolWritten = true
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *protocolConn) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
if !c.protocolWritten {
|
||||||
|
return bufio.ReadFrom0(c, r)
|
||||||
|
}
|
||||||
|
return bufio.Copy(c.Conn, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *protocolConn) Upstream() any {
|
||||||
|
return c.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type vectorisedProtocolConn struct {
|
||||||
|
protocolConn
|
||||||
|
writer N.VectorisedWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *vectorisedProtocolConn) WriteVectorised(buffers []*buf.Buffer) error {
|
||||||
|
if c.protocolWritten {
|
||||||
|
return c.writer.WriteVectorised(buffers)
|
||||||
|
}
|
||||||
|
c.protocolWritten = true
|
||||||
|
buffer := EncodeRequest(c.request, nil)
|
||||||
|
return c.writer.WriteVectorised(append([]*buf.Buffer{buffer}, buffers...))
|
||||||
|
}
|
||||||
@@ -111,16 +111,6 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(uConfig.NextProtos) > 0 {
|
|
||||||
for _, extension := range uConn.Extensions {
|
|
||||||
if alpnExtension, isALPN := extension.(*utls.ALPNExtension); isALPN {
|
|
||||||
alpnExtension.AlpnProtocols = uConfig.NextProtos
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hello := uConn.HandshakeState.Hello
|
hello := uConn.HandshakeState.Hello
|
||||||
hello.SessionId = make([]byte, 32)
|
hello.SessionId = make([]byte, 32)
|
||||||
copy(hello.Raw[39:], hello.SessionId)
|
copy(hello.Raw[39:], hello.SessionId)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
package tls
|
package tls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
@@ -48,7 +47,7 @@ func (e *UTLSClientConfig) Config() (*STDConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, e.config.NextProtos}, nil
|
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
||||||
@@ -88,31 +87,6 @@ func (c *utlsConnWrapper) Upstream() any {
|
|||||||
return c.UConn
|
return c.UConn
|
||||||
}
|
}
|
||||||
|
|
||||||
type utlsALPNWrapper struct {
|
|
||||||
utlsConnWrapper
|
|
||||||
nextProtocols []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error {
|
|
||||||
if len(c.nextProtocols) > 0 {
|
|
||||||
err := c.BuildHandshakeState()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, extension := range c.Extensions {
|
|
||||||
if alpnExtension, isALPN := extension.(*utls.ALPNExtension); isALPN {
|
|
||||||
alpnExtension.AlpnProtocols = c.nextProtocols
|
|
||||||
err = c.BuildHandshakeState()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.UConn.HandshakeContext(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
|
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
|
|||||||
@@ -1,95 +1,3 @@
|
|||||||
#### 1.3.0
|
|
||||||
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
Important changes since 1.2:
|
|
||||||
|
|
||||||
* Add [FakeIP](/configuration/dns/fakeip) support **1**
|
|
||||||
* Improve multiplex **2**
|
|
||||||
* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support
|
|
||||||
* Add `rewrite_ttl` DNS rule action
|
|
||||||
* Add `store_fakeip` Clash API option
|
|
||||||
* Add multi-peer support for [WireGuard](/configuration/outbound/wireguard#peers) outbound
|
|
||||||
* Add loopback detect
|
|
||||||
* 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
|
|
||||||
* Perform URLTest recheck after network changes
|
|
||||||
* Fix `system` tun stack for ios
|
|
||||||
* Fix network monitor for android/ios
|
|
||||||
* Update VLESS and XUDP protocol
|
|
||||||
* Make splice work with traffic statistics systems like Clash API
|
|
||||||
* Significantly reduces memory usage of idle connections
|
|
||||||
* Improve DNS caching
|
|
||||||
* Add `independent_cache` [option](/configuration/dns#independent_cache) for DNS
|
|
||||||
* Reimplemented shadowsocks client
|
|
||||||
* Add multiplex support for VLESS outbound
|
|
||||||
* Automatically add Windows firewall rules in order for the system tun stack to work
|
|
||||||
* Fix TLS 1.2 support for shadow-tls client
|
|
||||||
* Add `cache_id` [option](/configuration/experimental#cache_id) for Clash cache file
|
|
||||||
* Fix `local` DNS transport for Android
|
|
||||||
|
|
||||||
*1*:
|
|
||||||
|
|
||||||
See [FAQ](/faq/fakeip) for more information.
|
|
||||||
|
|
||||||
*2*:
|
|
||||||
|
|
||||||
Added new `h2mux` multiplex protocol and `padding` multiplex option, see [Multiplex](/configuration/shared/multiplex).
|
|
||||||
|
|
||||||
#### 1.3-rc2
|
|
||||||
|
|
||||||
* Fix `local` DNS transport for Android
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
#### 1.3-rc1
|
|
||||||
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
#### 1.3-beta14
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.3-beta13
|
|
||||||
|
|
||||||
* Fix resolving fakeip domains **1**
|
|
||||||
* Deprecate L3 routing
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
If the destination address of the connection is obtained from fakeip, dns rules with server type fakeip will be skipped.
|
|
||||||
|
|
||||||
#### 1.3-beta12
|
|
||||||
|
|
||||||
* Automatically add Windows firewall rules in order for the system tun stack to work
|
|
||||||
* Fix TLS 1.2 support for shadow-tls client
|
|
||||||
* Add `cache_id` [option](/configuration/experimental#cache_id) for Clash cache file
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.3-beta11
|
|
||||||
|
|
||||||
* Fix bugs and update dependencies
|
|
||||||
|
|
||||||
#### 1.3-beta10
|
|
||||||
|
|
||||||
* Improve direct copy **1**
|
|
||||||
* Improve DNS caching
|
|
||||||
* Add `independent_cache` [option](/configuration/dns#independent_cache) for DNS
|
|
||||||
* Reimplemented shadowsocks client **2**
|
|
||||||
* Add multiplex support for VLESS outbound
|
|
||||||
* Set TCP keepalive for WireGuard gVisor TCP connections
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
* Make splice work with traffic statistics systems like Clash API
|
|
||||||
* Significantly reduces memory usage of idle connections
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
Improved performance and reduced memory usage.
|
|
||||||
|
|
||||||
#### 1.3-beta9
|
#### 1.3-beta9
|
||||||
|
|
||||||
* Improve multiplex **1**
|
* Improve multiplex **1**
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_expire": false,
|
"disable_expire": false,
|
||||||
"independent_cache": false,
|
|
||||||
"reverse_mapping": false,
|
"reverse_mapping": false,
|
||||||
"fakeip": {}
|
"fakeip": {}
|
||||||
}
|
}
|
||||||
@@ -49,10 +48,6 @@ Disable dns cache.
|
|||||||
|
|
||||||
Disable dns cache expire.
|
Disable dns cache expire.
|
||||||
|
|
||||||
#### independent_cache
|
|
||||||
|
|
||||||
Make each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance.
|
|
||||||
|
|
||||||
#### reverse_mapping
|
#### reverse_mapping
|
||||||
|
|
||||||
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.
|
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
"strategy": "",
|
"strategy": "",
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"disable_expire": false,
|
"disable_expire": false,
|
||||||
"independent_cache": false,
|
|
||||||
"reverse_mapping": false,
|
"reverse_mapping": false,
|
||||||
"fakeip": {}
|
"fakeip": {}
|
||||||
}
|
}
|
||||||
@@ -48,10 +47,6 @@
|
|||||||
|
|
||||||
禁用 DNS 缓存过期。
|
禁用 DNS 缓存过期。
|
||||||
|
|
||||||
#### independent_cache
|
|
||||||
|
|
||||||
使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。
|
|
||||||
|
|
||||||
#### reverse_mapping
|
#### reverse_mapping
|
||||||
|
|
||||||
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
|
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
|
||||||
|
|||||||
@@ -30,18 +30,18 @@ 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](/configuration/dns/fakeip) | `fakeip` |
|
| [FakeIP](./fakeip) | `fakeip` |
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
@@ -94,4 +94,4 @@ Take no effect if override by other settings.
|
|||||||
|
|
||||||
Tag of an outbound for connecting to the dns server.
|
Tag of an outbound for connecting to the dns server.
|
||||||
|
|
||||||
Default outbound will be used if empty.
|
Default outbound will be used if empty.
|
||||||
@@ -30,18 +30,18 @@ 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](/configuration/dns/fakeip) | `fakeip` |
|
| [FakeIP](./fakeip) | `fakeip` |
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,13 @@
|
|||||||
"experimental": {
|
"experimental": {
|
||||||
"clash_api": {
|
"clash_api": {
|
||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "",
|
"external_ui": "folder",
|
||||||
"external_ui_download_url": "",
|
"external_ui_download_url": "",
|
||||||
"external_ui_download_detour": "",
|
"external_ui_download_detour": "",
|
||||||
"secret": "",
|
"secret": "",
|
||||||
"default_mode": "",
|
"default_mode": "rule",
|
||||||
"store_selected": false,
|
"store_selected": false,
|
||||||
"cache_file": "",
|
"cache_file": "cache.db"
|
||||||
"cache_id": ""
|
|
||||||
},
|
},
|
||||||
"v2ray_api": {
|
"v2ray_api": {
|
||||||
"listen": "127.0.0.1:8080",
|
"listen": "127.0.0.1:8080",
|
||||||
@@ -92,12 +91,6 @@ Store selected outbound for the `Selector` outbound in cache file.
|
|||||||
|
|
||||||
Cache file path, `cache.db` will be used if empty.
|
Cache file path, `cache.db` will be used if empty.
|
||||||
|
|
||||||
#### cache_id
|
|
||||||
|
|
||||||
Cache ID.
|
|
||||||
|
|
||||||
If not empty, `store_selected` will use a separate store keyed by it.
|
|
||||||
|
|
||||||
### V2Ray API Fields
|
### V2Ray API Fields
|
||||||
|
|
||||||
!!! error ""
|
!!! error ""
|
||||||
|
|||||||
@@ -7,14 +7,13 @@
|
|||||||
"experimental": {
|
"experimental": {
|
||||||
"clash_api": {
|
"clash_api": {
|
||||||
"external_controller": "127.0.0.1:9090",
|
"external_controller": "127.0.0.1:9090",
|
||||||
"external_ui": "",
|
"external_ui": "folder",
|
||||||
"external_ui_download_url": "",
|
"external_ui_download_url": "",
|
||||||
"external_ui_download_detour": "",
|
"external_ui_download_detour": "",
|
||||||
"secret": "",
|
"secret": "",
|
||||||
"default_mode": "",
|
"default_mode": "rule",
|
||||||
"store_selected": false,
|
"store_selected": false,
|
||||||
"cache_file": "",
|
"cache_file": "cache.db"
|
||||||
"cache_id": ""
|
|
||||||
},
|
},
|
||||||
"v2ray_api": {
|
"v2ray_api": {
|
||||||
"listen": "127.0.0.1:8080",
|
"listen": "127.0.0.1:8080",
|
||||||
@@ -90,12 +89,6 @@ Clash 中的默认模式,默认使用 `rule`。
|
|||||||
|
|
||||||
缓存文件路径,默认使用`cache.db`。
|
缓存文件路径,默认使用`cache.db`。
|
||||||
|
|
||||||
#### cache_id
|
|
||||||
|
|
||||||
缓存 ID。
|
|
||||||
|
|
||||||
如果不为空,`store_selected` 将会使用以此为键的独立存储。
|
|
||||||
|
|
||||||
### V2Ray API 字段
|
### V2Ray API 字段
|
||||||
|
|
||||||
!!! error ""
|
!!! error ""
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"route": {
|
"route": {
|
||||||
"geoip": {},
|
"geoip": {},
|
||||||
"geosite": {},
|
"geosite": {},
|
||||||
|
"ip_rules": [],
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"final": "",
|
"final": "",
|
||||||
"auto_detect_interface": false,
|
"auto_detect_interface": false,
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
|------------|------------------------------------|
|
|------------|------------------------------------|
|
||||||
| `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
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ Multiplex protocol.
|
|||||||
| yamux | https://github.com/hashicorp/yamux |
|
| yamux | https://github.com/hashicorp/yamux |
|
||||||
| h2mux | https://golang.org/x/net/http2 |
|
| h2mux | https://golang.org/x/net/http2 |
|
||||||
|
|
||||||
h2mux is used by default.
|
SMux is used by default.
|
||||||
|
|
||||||
#### max_connections
|
#### max_connections
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
| yamux | https://github.com/hashicorp/yamux |
|
| yamux | https://github.com/hashicorp/yamux |
|
||||||
| h2mux | https://golang.org/x/net/http2 |
|
| h2mux | https://golang.org/x/net/http2 |
|
||||||
|
|
||||||
默认使用 h2mux。
|
默认使用 SMux。
|
||||||
|
|
||||||
#### max_connections
|
#### max_connections
|
||||||
|
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"tag": "google",
|
|
||||||
"address": "tls://8.8.8.8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tag": "local",
|
|
||||||
"address": "223.5.5.5",
|
|
||||||
"detour": "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tag": "remote",
|
|
||||||
"address": "fakeip"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tag": "block",
|
|
||||||
"address": "rcode://success"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"geosite": "category-ads-all",
|
|
||||||
"server": "block",
|
|
||||||
"disable_cache": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"outbound": "any",
|
|
||||||
"server": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"geosite": "cn",
|
|
||||||
"server": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"query_type": [
|
|
||||||
"A",
|
|
||||||
"AAAA"
|
|
||||||
],
|
|
||||||
"server": "remote"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fakeip": {
|
|
||||||
"enabled": true,
|
|
||||||
"inet4_range": "198.18.0.0/15",
|
|
||||||
"inet6_range": "fc00::/18"
|
|
||||||
},
|
|
||||||
"independent_cache": true,
|
|
||||||
"strategy": "ipv4_only"
|
|
||||||
},
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "tun",
|
|
||||||
"inet4_address": "172.19.0.1/30",
|
|
||||||
"auto_route": true,
|
|
||||||
"sniff": true,
|
|
||||||
"domain_strategy": "ipv4_only" // remove this line if you want to resolve the domain remotely (if the server is not sing-box, UDP may not work due to wrong behavior).
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"type": "shadowsocks",
|
|
||||||
"tag": "proxy",
|
|
||||||
"server": "mydomain.com",
|
|
||||||
"server_port": 8080,
|
|
||||||
"method": "2022-blake3-aes-128-gcm",
|
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "direct",
|
|
||||||
"tag": "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "block",
|
|
||||||
"tag": "block"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "dns",
|
|
||||||
"tag": "dns-out"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"protocol": "dns",
|
|
||||||
"outbound": "dns-out"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"geosite": "cn",
|
|
||||||
"geoip": [
|
|
||||||
"private",
|
|
||||||
"cn"
|
|
||||||
],
|
|
||||||
"outbound": "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"geosite": "category-ads-all",
|
|
||||||
"outbound": "block"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"auto_detect_interface": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"tag": "google",
|
|
||||||
"address": "tls://8.8.8.8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tag": "local",
|
|
||||||
"address": "223.5.5.5",
|
|
||||||
"detour": "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tag": "remote",
|
|
||||||
"address": "fakeip"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tag": "block",
|
|
||||||
"address": "rcode://success"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"geosite": "category-ads-all",
|
|
||||||
"server": "block",
|
|
||||||
"disable_cache": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"outbound": "any",
|
|
||||||
"server": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"geosite": "cn",
|
|
||||||
"server": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"query_type": [
|
|
||||||
"A",
|
|
||||||
"AAAA"
|
|
||||||
],
|
|
||||||
"server": "remote"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"fakeip": {
|
|
||||||
"enabled": true,
|
|
||||||
"inet4_range": "198.18.0.0/15",
|
|
||||||
"inet6_range": "fc00::/18"
|
|
||||||
},
|
|
||||||
"independent_cache": true,
|
|
||||||
"strategy": "ipv4_only"
|
|
||||||
},
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "tun",
|
|
||||||
"inet4_address": "172.19.0.1/30",
|
|
||||||
"auto_route": true,
|
|
||||||
"sniff": true,
|
|
||||||
"domain_strategy": "ipv4_only" // 如果您想在远程解析域,删除此行 (如果服务器程序不为 sing-box,可能由于错误的行为导致 UDP 无法使用)。
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"type": "shadowsocks",
|
|
||||||
"tag": "proxy",
|
|
||||||
"server": "mydomain.com",
|
|
||||||
"server_port": 8080,
|
|
||||||
"method": "2022-blake3-aes-128-gcm",
|
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "direct",
|
|
||||||
"tag": "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "block",
|
|
||||||
"tag": "block"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "dns",
|
|
||||||
"tag": "dns-out"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"protocol": "dns",
|
|
||||||
"outbound": "dns-out"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"geosite": "cn",
|
|
||||||
"geoip": [
|
|
||||||
"private",
|
|
||||||
"cn"
|
|
||||||
],
|
|
||||||
"outbound": "direct"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"geosite": "category-ads-all",
|
|
||||||
"outbound": "block"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"auto_detect_interface": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -9,4 +9,3 @@ Configuration examples for sing-box.
|
|||||||
* [ShadowTLS](./shadowtls)
|
* [ShadowTLS](./shadowtls)
|
||||||
* [Clash API](./clash-api)
|
* [Clash API](./clash-api)
|
||||||
* [WireGuard Direct](./wireguard-direct)
|
* [WireGuard Direct](./wireguard-direct)
|
||||||
* [FakeIP](./fakeip)
|
|
||||||
|
|||||||
@@ -9,4 +9,3 @@ sing-box 的配置示例。
|
|||||||
* [ShadowTLS](./shadowtls)
|
* [ShadowTLS](./shadowtls)
|
||||||
* [Clash API](./clash-api)
|
* [Clash API](./clash-api)
|
||||||
* [WireGuard Direct](./wireguard-direct)
|
* [WireGuard Direct](./wireguard-direct)
|
||||||
* [FakeIP](./fakeip)
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ responds to DNS requests with virtual results and restores mapping when acceptin
|
|||||||
|
|
||||||
#### Advantage
|
#### 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
|
#### Limitation
|
||||||
|
|
||||||
@@ -14,6 +15,6 @@ responds to DNS requests with virtual results and restores mapping when acceptin
|
|||||||
|
|
||||||
#### Recommendation
|
#### Recommendation
|
||||||
|
|
||||||
* Enable `dns.independent_cache` unless you always resolve FakeIP domains remotely.
|
* Do not use if you do not need L3 routing.
|
||||||
* If using tun, make sure FakeIP ranges is included in the tun's routes.
|
* 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.
|
* 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.
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ FakeIP 是指同时劫持 DNS 和连接请求的程序中的一种行为。它
|
|||||||
|
|
||||||
#### 优点
|
#### 优点
|
||||||
|
|
||||||
*
|
* 在像 L3 路由这样无法进行流量探测的地方检索所请求的域名,以协助路由。
|
||||||
|
* 减少对一个域的第一个 TCP 请求的 RTT(这是最常见的原因)。
|
||||||
|
|
||||||
#### 限制
|
#### 限制
|
||||||
|
|
||||||
@@ -13,6 +14,6 @@ FakeIP 是指同时劫持 DNS 和连接请求的程序中的一种行为。它
|
|||||||
|
|
||||||
#### 建议
|
#### 建议
|
||||||
|
|
||||||
* 启用 `dns.independent_cache` 除非您始终远程解析 FakeIP 域。
|
* 如果不需要 L3 路由,请勿使用。
|
||||||
* 如果使用 tun,请确保 tun 路由中包含 FakeIP 地址范围。
|
* 如果使用 tun,请确保 tun 路由中包含 FakeIP 地址范围。
|
||||||
* 启用 `experimental.clash_api.store_fakeip` 以持久化 FakeIP 记录,或者使用 `dns.rules.rewrite_ttl` 避免程序重启后在 DNS 被缓存的环境中丢失记录。
|
* 启用 `experimental.clash_api.store_fakeip` 以持久化 FakeIP 记录,或者使用 `dns.rules.rewrite_ttl` 避免程序重启后在 DNS 被缓存的环境中丢失记录。
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ Experimental iOS client for sing-box.
|
|||||||
|
|
||||||
#### Note
|
#### Note
|
||||||
|
|
||||||
* User Agent in remote profile request is `SFI/$version ($version_code; sing-box $sing_box_version)`
|
* User Agent in remote profile request is `SFA/$version ($version_code; sing-box $sing_box_version)`
|
||||||
* Crash logs is located in `Settings` -> `View Service Log`
|
* Crash logs is located in `Settings` -> `View Service Log`
|
||||||
|
|
||||||
#### Privacy policy
|
#### Privacy policy
|
||||||
|
|||||||
@@ -14,11 +14,10 @@ var bucketSelected = []byte("selected")
|
|||||||
var _ adapter.ClashCacheFile = (*CacheFile)(nil)
|
var _ adapter.ClashCacheFile = (*CacheFile)(nil)
|
||||||
|
|
||||||
type CacheFile struct {
|
type CacheFile struct {
|
||||||
DB *bbolt.DB
|
DB *bbolt.DB
|
||||||
cacheID []byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Open(path string, cacheID string) (*CacheFile, error) {
|
func Open(path string) (*CacheFile, error) {
|
||||||
const fileMode = 0o666
|
const fileMode = 0o666
|
||||||
options := bbolt.Options{Timeout: time.Second}
|
options := bbolt.Options{Timeout: time.Second}
|
||||||
db, err := bbolt.Open(path, fileMode, &options)
|
db, err := bbolt.Open(path, fileMode, &options)
|
||||||
@@ -32,39 +31,13 @@ func Open(path string, cacheID string) (*CacheFile, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var cacheIDBytes []byte
|
return &CacheFile{db}, nil
|
||||||
if cacheID != "" {
|
|
||||||
cacheIDBytes = append([]byte{0}, []byte(cacheID)...)
|
|
||||||
}
|
|
||||||
return &CacheFile{db, cacheIDBytes}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) bucket(t *bbolt.Tx, key []byte) *bbolt.Bucket {
|
|
||||||
if c.cacheID == nil {
|
|
||||||
return t.Bucket(key)
|
|
||||||
}
|
|
||||||
bucket := t.Bucket(c.cacheID)
|
|
||||||
if bucket == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return bucket.Bucket(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) createBucket(t *bbolt.Tx, key []byte) (*bbolt.Bucket, error) {
|
|
||||||
if c.cacheID == nil {
|
|
||||||
return t.CreateBucketIfNotExists(key)
|
|
||||||
}
|
|
||||||
bucket, err := t.CreateBucketIfNotExists(c.cacheID)
|
|
||||||
if bucket == nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return bucket.CreateBucketIfNotExists(key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CacheFile) LoadSelected(group string) string {
|
func (c *CacheFile) LoadSelected(group string) string {
|
||||||
var selected string
|
var selected string
|
||||||
c.DB.View(func(t *bbolt.Tx) error {
|
c.DB.View(func(t *bbolt.Tx) error {
|
||||||
bucket := c.bucket(t, bucketSelected)
|
bucket := t.Bucket(bucketSelected)
|
||||||
if bucket == nil {
|
if bucket == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -79,7 +52,7 @@ func (c *CacheFile) LoadSelected(group string) string {
|
|||||||
|
|
||||||
func (c *CacheFile) StoreSelected(group, selected string) error {
|
func (c *CacheFile) StoreSelected(group, selected string) error {
|
||||||
return c.DB.Batch(func(t *bbolt.Tx) error {
|
return c.DB.Batch(func(t *bbolt.Tx) error {
|
||||||
bucket, err := c.createBucket(t, bucketSelected)
|
bucket, err := t.CreateBucketIfNotExists(bucketSelected)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
"github.com/sagernet/sing-box/common/json"
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||||
"github.com/sagernet/websocket"
|
"github.com/sagernet/websocket"
|
||||||
@@ -15,10 +14,10 @@ import (
|
|||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
func connectionRouter(router adapter.Router, trafficManager *trafficontrol.Manager) http.Handler {
|
func connectionRouter(trafficManager *trafficontrol.Manager) http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Get("/", getConnections(trafficManager))
|
r.Get("/", getConnections(trafficManager))
|
||||||
r.Delete("/", closeAllConnections(router, trafficManager))
|
r.Delete("/", closeAllConnections(trafficManager))
|
||||||
r.Delete("/{id}", closeConnection(trafficManager))
|
r.Delete("/{id}", closeConnection(trafficManager))
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@@ -87,13 +86,12 @@ func closeConnection(trafficManager *trafficontrol.Manager) func(w http.Response
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeAllConnections(router adapter.Router, trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
|
func closeAllConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
snapshot := trafficManager.Snapshot()
|
snapshot := trafficManager.Snapshot()
|
||||||
for _, c := range snapshot.Connections {
|
for _, c := range snapshot.Connections {
|
||||||
c.Close()
|
c.Close()
|
||||||
}
|
}
|
||||||
router.ResetNetwork()
|
|
||||||
render.NoContent(w, r)
|
render.NoContent(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ func getProxies(server *Server, router adapter.Router) func(w http.ResponseWrite
|
|||||||
defaultTag = allProxies[0]
|
defaultTag = allProxies[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.SliceStable(allProxies, func(i, j int) bool {
|
sort.Slice(allProxies, func(i, j int) bool {
|
||||||
return allProxies[i] == defaultTag
|
return allProxies[i] == defaultTag
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ type Server struct {
|
|||||||
storeSelected bool
|
storeSelected bool
|
||||||
storeFakeIP bool
|
storeFakeIP bool
|
||||||
cacheFilePath string
|
cacheFilePath string
|
||||||
cacheID string
|
|
||||||
cacheFile adapter.ClashCacheFile
|
cacheFile adapter.ClashCacheFile
|
||||||
|
|
||||||
externalUI string
|
externalUI string
|
||||||
@@ -89,7 +88,6 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
|
|||||||
cachePath = filemanager.BasePath(ctx, cachePath)
|
cachePath = filemanager.BasePath(ctx, cachePath)
|
||||||
}
|
}
|
||||||
server.cacheFilePath = cachePath
|
server.cacheFilePath = cachePath
|
||||||
server.cacheID = options.CacheID
|
|
||||||
}
|
}
|
||||||
cors := cors.New(cors.Options{
|
cors := cors.New(cors.Options{
|
||||||
AllowedOrigins: []string{"*"},
|
AllowedOrigins: []string{"*"},
|
||||||
@@ -107,7 +105,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
|
|||||||
r.Mount("/configs", configRouter(server, logFactory, server.logger))
|
r.Mount("/configs", configRouter(server, logFactory, server.logger))
|
||||||
r.Mount("/proxies", proxyRouter(server, router))
|
r.Mount("/proxies", proxyRouter(server, router))
|
||||||
r.Mount("/rules", ruleRouter(router))
|
r.Mount("/rules", ruleRouter(router))
|
||||||
r.Mount("/connections", connectionRouter(router, trafficManager))
|
r.Mount("/connections", connectionRouter(trafficManager))
|
||||||
r.Mount("/providers/proxies", proxyProviderRouter())
|
r.Mount("/providers/proxies", proxyProviderRouter())
|
||||||
r.Mount("/providers/rules", ruleProviderRouter())
|
r.Mount("/providers/rules", ruleProviderRouter())
|
||||||
r.Mount("/script", scriptRouter())
|
r.Mount("/script", scriptRouter())
|
||||||
@@ -132,7 +130,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
|
|||||||
|
|
||||||
func (s *Server) PreStart() error {
|
func (s *Server) PreStart() error {
|
||||||
if s.cacheFilePath != "" {
|
if s.cacheFilePath != "" {
|
||||||
cacheFile, err := cachefile.Open(s.cacheFilePath, s.cacheID)
|
cacheFile, err := cachefile.Open(s.cacheFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "open cache file")
|
return E.Cause(err, "open cache file")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/experimental/trackerconn"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
"github.com/sagernet/sing/common/atomic"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
@@ -115,13 +115,13 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad
|
|||||||
download := new(atomic.Int64)
|
download := new(atomic.Int64)
|
||||||
|
|
||||||
t := &tcpTracker{
|
t := &tcpTracker{
|
||||||
ExtendedConn: bufio.NewCounterConn(conn, []N.CountFunc{func(n int64) {
|
ExtendedConn: trackerconn.NewHook(conn, func(n int64) {
|
||||||
upload.Add(n)
|
upload.Add(n)
|
||||||
manager.PushUploaded(n)
|
manager.PushUploaded(n)
|
||||||
}}, []N.CountFunc{func(n int64) {
|
}, func(n int64) {
|
||||||
download.Add(n)
|
download.Add(n)
|
||||||
manager.PushDownloaded(n)
|
manager.PushDownloaded(n)
|
||||||
}}),
|
}),
|
||||||
manager: manager,
|
manager: manager,
|
||||||
trackerInfo: &trackerInfo{
|
trackerInfo: &trackerInfo{
|
||||||
UUID: uuid,
|
UUID: uuid,
|
||||||
@@ -202,13 +202,13 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route
|
|||||||
download := new(atomic.Int64)
|
download := new(atomic.Int64)
|
||||||
|
|
||||||
ut := &udpTracker{
|
ut := &udpTracker{
|
||||||
PacketConn: bufio.NewCounterPacketConn(conn, []N.CountFunc{func(n int64) {
|
PacketConn: trackerconn.NewHookPacket(conn, func(n int64) {
|
||||||
upload.Add(n)
|
upload.Add(n)
|
||||||
manager.PushUploaded(n)
|
manager.PushUploaded(n)
|
||||||
}}, []N.CountFunc{func(n int64) {
|
}, func(n int64) {
|
||||||
download.Add(n)
|
download.Add(n)
|
||||||
manager.PushDownloaded(n)
|
manager.PushDownloaded(n)
|
||||||
}}),
|
}),
|
||||||
manager: manager,
|
manager: manager,
|
||||||
trackerInfo: &trackerInfo{
|
trackerInfo: &trackerInfo{
|
||||||
UUID: uuid,
|
UUID: uuid,
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/netip"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/task"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LocalDNSTransport interface {
|
|
||||||
Raw() bool
|
|
||||||
Lookup(ctx *ExchangeContext, network string, domain string) error
|
|
||||||
Exchange(ctx *ExchangeContext, message []byte) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterLocalDNSTransport(transport LocalDNSTransport) {
|
|
||||||
if transport == nil {
|
|
||||||
dns.RegisterTransport([]string{"local"}, dns.CreateLocalTransport)
|
|
||||||
} else {
|
|
||||||
dns.RegisterTransport([]string{"local"}, func(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) {
|
|
||||||
return &platformLocalDNSTransport{
|
|
||||||
iif: transport,
|
|
||||||
}, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ dns.Transport = (*platformLocalDNSTransport)(nil)
|
|
||||||
|
|
||||||
type platformLocalDNSTransport struct {
|
|
||||||
iif LocalDNSTransport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *platformLocalDNSTransport) Name() string {
|
|
||||||
return "local"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *platformLocalDNSTransport) Start() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *platformLocalDNSTransport) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *platformLocalDNSTransport) Raw() bool {
|
|
||||||
return p.iif.Raw()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *platformLocalDNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
messageBytes, err := message.Pack()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
response := &ExchangeContext{
|
|
||||||
context: ctx,
|
|
||||||
}
|
|
||||||
var responseMessage *mDNS.Msg
|
|
||||||
return responseMessage, task.Run(ctx, func() error {
|
|
||||||
err = p.iif.Exchange(response, messageBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if response.error != nil {
|
|
||||||
return response.error
|
|
||||||
}
|
|
||||||
responseMessage = &response.message
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *platformLocalDNSTransport) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) {
|
|
||||||
var network string
|
|
||||||
switch strategy {
|
|
||||||
case dns.DomainStrategyUseIPv4:
|
|
||||||
network = "ip4"
|
|
||||||
case dns.DomainStrategyPreferIPv6:
|
|
||||||
network = "ip6"
|
|
||||||
default:
|
|
||||||
network = "ip"
|
|
||||||
}
|
|
||||||
response := &ExchangeContext{
|
|
||||||
context: ctx,
|
|
||||||
}
|
|
||||||
var responseAddr []netip.Addr
|
|
||||||
return responseAddr, task.Run(ctx, func() error {
|
|
||||||
err := p.iif.Lookup(response, network, domain)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if response.error != nil {
|
|
||||||
return response.error
|
|
||||||
}
|
|
||||||
switch strategy {
|
|
||||||
case dns.DomainStrategyUseIPv4:
|
|
||||||
responseAddr = common.Filter(response.addresses, func(it netip.Addr) bool {
|
|
||||||
return it.Is4()
|
|
||||||
})
|
|
||||||
case dns.DomainStrategyPreferIPv6:
|
|
||||||
responseAddr = common.Filter(response.addresses, func(it netip.Addr) bool {
|
|
||||||
return it.Is6()
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
responseAddr = response.addresses
|
|
||||||
}
|
|
||||||
/*if len(responseAddr) == 0 {
|
|
||||||
response.error = dns.RCodeSuccess
|
|
||||||
}*/
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type Func interface {
|
|
||||||
Invoke() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExchangeContext struct {
|
|
||||||
context context.Context
|
|
||||||
message mDNS.Msg
|
|
||||||
addresses []netip.Addr
|
|
||||||
error error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ExchangeContext) OnCancel(callback Func) {
|
|
||||||
go func() {
|
|
||||||
<-c.context.Done()
|
|
||||||
callback.Invoke()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ExchangeContext) Success(result string) {
|
|
||||||
c.addresses = common.Map(common.Filter(strings.Split(result, "\n"), func(it string) bool {
|
|
||||||
return !common.IsEmpty(it)
|
|
||||||
}), func(it string) netip.Addr {
|
|
||||||
return M.ParseSocksaddrHostPort(it, 0).Unwrap().Addr
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ExchangeContext) RawSuccess(result []byte) {
|
|
||||||
err := c.message.Unpack(result)
|
|
||||||
if err != nil {
|
|
||||||
c.error = E.Cause(err, "parse response")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ExchangeContext) ErrorCode(code int32) {
|
|
||||||
c.error = dns.RCodeError(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ExchangeContext) ErrnoCode(code int32) {
|
|
||||||
c.error = syscall.Errno(code)
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,6 @@ type PlatformInterface interface {
|
|||||||
CloseDefaultInterfaceMonitor(listener InterfaceUpdateListener) error
|
CloseDefaultInterfaceMonitor(listener InterfaceUpdateListener) error
|
||||||
UsePlatformInterfaceGetter() bool
|
UsePlatformInterfaceGetter() bool
|
||||||
GetInterfaces() (NetworkInterfaceIterator, error)
|
GetInterfaces() (NetworkInterfaceIterator, error)
|
||||||
UnderNetworkExtension() bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TunInterface interface {
|
type TunInterface interface {
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ type Interface interface {
|
|||||||
CreateDefaultInterfaceMonitor(errorHandler E.Handler) tun.DefaultInterfaceMonitor
|
CreateDefaultInterfaceMonitor(errorHandler E.Handler) tun.DefaultInterfaceMonitor
|
||||||
UsePlatformInterfaceGetter() bool
|
UsePlatformInterfaceGetter() bool
|
||||||
Interfaces() ([]NetworkInterface, error)
|
Interfaces() ([]NetworkInterface, error)
|
||||||
UnderNetworkExtension() bool
|
|
||||||
process.Searcher
|
process.Searcher
|
||||||
io.Writer
|
io.Writer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,3 @@ func (w *platformInterfaceWrapper) Interfaces() ([]platform.NetworkInterface, er
|
|||||||
}
|
}
|
||||||
return interfaces, nil
|
return interfaces, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) UnderNetworkExtension() bool {
|
|
||||||
return w.iif.UnderNetworkExtension()
|
|
||||||
}
|
|
||||||
|
|||||||
108
experimental/trackerconn/conn.go
Normal file
108
experimental/trackerconn/conn.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package trackerconn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/atomic"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(conn net.Conn, readCounter []*atomic.Int64, writeCounter []*atomic.Int64) *Conn {
|
||||||
|
return &Conn{bufio.NewExtendedConn(conn), readCounter, writeCounter}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHook(conn net.Conn, readCounter func(n int64), writeCounter func(n int64)) *HookConn {
|
||||||
|
return &HookConn{bufio.NewExtendedConn(conn), readCounter, writeCounter}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
N.ExtendedConn
|
||||||
|
readCounter []*atomic.Int64
|
||||||
|
writeCounter []*atomic.Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = c.ExtendedConn.Read(p)
|
||||||
|
for _, counter := range c.readCounter {
|
||||||
|
counter.Add(int64(n))
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) ReadBuffer(buffer *buf.Buffer) error {
|
||||||
|
err := c.ExtendedConn.ReadBuffer(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, counter := range c.readCounter {
|
||||||
|
counter.Add(int64(buffer.Len()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Write(p []byte) (n int, err error) {
|
||||||
|
n, err = c.ExtendedConn.Write(p)
|
||||||
|
for _, counter := range c.writeCounter {
|
||||||
|
counter.Add(int64(n))
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
|
dataLen := int64(buffer.Len())
|
||||||
|
err := c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, counter := range c.writeCounter {
|
||||||
|
counter.Add(dataLen)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Upstream() any {
|
||||||
|
return c.ExtendedConn
|
||||||
|
}
|
||||||
|
|
||||||
|
type HookConn struct {
|
||||||
|
N.ExtendedConn
|
||||||
|
readCounter func(n int64)
|
||||||
|
writeCounter func(n int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HookConn) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = c.ExtendedConn.Read(p)
|
||||||
|
c.readCounter(int64(n))
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HookConn) ReadBuffer(buffer *buf.Buffer) error {
|
||||||
|
err := c.ExtendedConn.ReadBuffer(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.readCounter(int64(buffer.Len()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HookConn) Write(p []byte) (n int, err error) {
|
||||||
|
n, err = c.ExtendedConn.Write(p)
|
||||||
|
c.writeCounter(int64(n))
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HookConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
|
dataLen := int64(buffer.Len())
|
||||||
|
err := c.ExtendedConn.WriteBuffer(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.writeCounter(dataLen)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HookConn) Upstream() any {
|
||||||
|
return c.ExtendedConn
|
||||||
|
}
|
||||||
76
experimental/trackerconn/packet_conn.go
Normal file
76
experimental/trackerconn/packet_conn.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package trackerconn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing/common/atomic"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPacket(conn N.PacketConn, readCounter []*atomic.Int64, writeCounter []*atomic.Int64) *PacketConn {
|
||||||
|
return &PacketConn{conn, readCounter, writeCounter}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHookPacket(conn N.PacketConn, readCounter func(n int64), writeCounter func(n int64)) *HookPacketConn {
|
||||||
|
return &HookPacketConn{conn, readCounter, writeCounter}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PacketConn struct {
|
||||||
|
N.PacketConn
|
||||||
|
readCounter []*atomic.Int64
|
||||||
|
writeCounter []*atomic.Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
destination, err = c.PacketConn.ReadPacket(buffer)
|
||||||
|
if err == nil {
|
||||||
|
for _, counter := range c.readCounter {
|
||||||
|
counter.Add(int64(buffer.Len()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
dataLen := int64(buffer.Len())
|
||||||
|
err := c.PacketConn.WritePacket(buffer, destination)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, counter := range c.writeCounter {
|
||||||
|
counter.Add(dataLen)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Upstream() any {
|
||||||
|
return c.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
type HookPacketConn struct {
|
||||||
|
N.PacketConn
|
||||||
|
readCounter func(n int64)
|
||||||
|
writeCounter func(n int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HookPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
destination, err = c.PacketConn.ReadPacket(buffer)
|
||||||
|
if err == nil {
|
||||||
|
c.readCounter(int64(buffer.Len()))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HookPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
dataLen := int64(buffer.Len())
|
||||||
|
err := c.PacketConn.WritePacket(buffer, destination)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.writeCounter(dataLen)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *HookPacketConn) Upstream() any {
|
||||||
|
return c.PacketConn
|
||||||
|
}
|
||||||
@@ -10,9 +10,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/experimental/trackerconn"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
"github.com/sagernet/sing/common/atomic"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
@@ -83,7 +83,7 @@ func (s *StatsService) RoutedConnection(inbound string, outbound string, user st
|
|||||||
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
||||||
}
|
}
|
||||||
s.access.Unlock()
|
s.access.Unlock()
|
||||||
return bufio.NewInt64CounterConn(conn, readCounter, writeCounter)
|
return trackerconn.New(conn, readCounter, writeCounter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn {
|
func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn {
|
||||||
@@ -109,7 +109,7 @@ func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, u
|
|||||||
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
|
||||||
}
|
}
|
||||||
s.access.Unlock()
|
s.access.Unlock()
|
||||||
return bufio.NewInt64CounterPacketConn(conn, readCounter, writeCounter)
|
return trackerconn.NewPacket(conn, readCounter, writeCounter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
|
func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {
|
||||||
|
|||||||
79
go.mod
79
go.mod
@@ -4,8 +4,8 @@ go 1.18
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
berty.tech/go-libtor v1.0.385
|
berty.tech/go-libtor v1.0.385
|
||||||
github.com/Dreamacro/clash v1.16.0
|
github.com/Dreamacro/clash v1.15.0
|
||||||
github.com/caddyserver/certmagic v0.18.2
|
github.com/caddyserver/certmagic v0.17.2
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/dustin/go-humanize v1.0.1
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
@@ -13,87 +13,82 @@ require (
|
|||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/go-chi/render v1.0.2
|
github.com/go-chi/render v1.0.2
|
||||||
github.com/gofrs/uuid/v5 v5.0.0
|
github.com/gofrs/uuid/v5 v5.0.0
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df
|
github.com/hashicorp/yamux v0.1.1
|
||||||
|
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/mholt/acmez v1.2.0
|
github.com/mholt/acmez v1.1.0
|
||||||
github.com/miekg/dns v1.1.55
|
github.com/miekg/dns v1.1.53
|
||||||
github.com/ooni/go-libtor v1.1.8
|
github.com/ooni/go-libtor v1.1.7
|
||||||
github.com/oschwald/maxminddb-golang v1.11.0
|
github.com/oschwald/maxminddb-golang v1.10.0
|
||||||
github.com/pires/go-proxyproto v0.7.0
|
github.com/pires/go-proxyproto v0.7.0
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0
|
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0
|
||||||
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035
|
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035
|
||||||
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2
|
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
|
||||||
github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02
|
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||||
github.com/sagernet/sing v0.2.7
|
github.com/sagernet/sing v0.2.4
|
||||||
github.com/sagernet/sing-dns v0.1.6
|
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc
|
||||||
github.com/sagernet/sing-mux v0.1.0
|
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.2
|
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.1.1
|
github.com/sagernet/sing-tun v0.1.5-0.20230422121432-209ec123ca7b
|
||||||
github.com/sagernet/sing-shadowtls v0.1.2
|
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3
|
||||||
github.com/sagernet/sing-tun v0.1.8
|
|
||||||
github.com/sagernet/sing-vmess v0.1.6
|
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
||||||
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77
|
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77
|
||||||
github.com/spf13/cobra v1.7.0
|
github.com/spf13/cobra v1.7.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.2
|
||||||
go.etcd.io/bbolt v1.3.7
|
go.etcd.io/bbolt v1.3.7
|
||||||
go.uber.org/zap v1.24.0
|
go.uber.org/zap v1.24.0
|
||||||
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
|
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
|
||||||
golang.org/x/crypto v0.10.0
|
golang.org/x/crypto v0.8.0
|
||||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
|
||||||
golang.org/x/net v0.11.0
|
golang.org/x/net v0.9.0
|
||||||
golang.org/x/sys v0.9.0
|
golang.org/x/sys v0.7.0
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
|
||||||
google.golang.org/grpc v1.56.1
|
google.golang.org/grpc v1.54.0
|
||||||
google.golang.org/protobuf v1.30.0
|
google.golang.org/protobuf v1.30.0
|
||||||
|
gvisor.dev/gvisor v0.0.0-20230415003630-3981d5d5e523
|
||||||
)
|
)
|
||||||
|
|
||||||
//replace github.com/sagernet/sing => ../sing
|
//replace github.com/sagernet/sing => ../sing
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Dreamacro/protobytes v0.0.0-20230324064118-87bc784139cd // indirect
|
|
||||||
github.com/ajg/form v1.5.1 // indirect
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||||
github.com/cloudflare/circl v1.2.1-0.20221019164342-6ab4dfed8f3c // indirect
|
github.com/cloudflare/circl v1.2.1-0.20221019164342-6ab4dfed8f3c // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
|
github.com/google/btree v1.0.1 // indirect
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.15.15 // indirect
|
github.com/klauspost/compress v1.15.15 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
github.com/klauspost/cpuid/v2 v2.1.1 // indirect
|
||||||
github.com/libdns/libdns v0.2.1 // indirect
|
github.com/libdns/libdns v0.2.1 // indirect
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
|
github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
github.com/quic-go/qtls-go1-19 v0.2.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
github.com/quic-go/qtls-go1-20 v0.1.0 // indirect
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
||||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||||
go.uber.org/atomic v1.11.0 // indirect
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
golang.org/x/mod v0.11.0 // indirect
|
golang.org/x/mod v0.8.0 // indirect
|
||||||
golang.org/x/text v0.10.0 // indirect
|
golang.org/x/text v0.9.0 // indirect
|
||||||
golang.org/x/time v0.3.0 // indirect
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||||
golang.org/x/tools v0.10.0 // indirect
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/blake3 v1.2.1 // indirect
|
lukechampine.com/blake3 v1.1.7 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
183
go.sum
183
go.sum
@@ -1,16 +1,14 @@
|
|||||||
berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw=
|
berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw=
|
||||||
berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw=
|
berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw=
|
||||||
github.com/Dreamacro/clash v1.16.0 h1:Ve+8mvtsCv7ySa6f53/GOW6Tho0AFp5RNFsgQZFKcZs=
|
github.com/Dreamacro/clash v1.15.0 h1:mlpD950VEggXZBNahV66hyKDRxcczkj3vymoAt78KyE=
|
||||||
github.com/Dreamacro/clash v1.16.0/go.mod h1:ZM3UI2gqqUN7UL7L/F9aTHODTByya6sbC/WivQpaoJk=
|
github.com/Dreamacro/clash v1.15.0/go.mod h1:WNH69bN11LiAdgdSr4hpkEuXVMfBbWyhEKMCTx9BtNE=
|
||||||
github.com/Dreamacro/protobytes v0.0.0-20230324064118-87bc784139cd h1:ygk7IF14j4ep4H2ZyeDe3IEoMZF8JdbX851RVVa/4D8=
|
|
||||||
github.com/Dreamacro/protobytes v0.0.0-20230324064118-87bc784139cd/go.mod h1:QvmEZ/h6KXszPOr2wUFl7Zn3hfFNYdfbXwPVDTyZs6k=
|
|
||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
github.com/caddyserver/certmagic v0.18.2 h1:Nj2+M+A2Ho9IF6n1wUSbra4mX1X6ALzWpul9HooprHA=
|
github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE=
|
||||||
github.com/caddyserver/certmagic v0.18.2/go.mod h1:cLsgYXecH1iVUPjDXw15/1SKjZk/TK+aFfQk5FnugGQ=
|
github.com/caddyserver/certmagic v0.17.2/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
@@ -33,18 +31,17 @@ github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
|||||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
|
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
|
||||||
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||||
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
||||||
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||||
@@ -54,15 +51,16 @@ github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbg
|
|||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df h1:pF1MMIzEJzJ/MyI4bXYXVYyN8CJgoQ2PPKT2z3O/Cl4=
|
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16 h1:+aAGyK41KRn8jbF2Q7PLL0Sxwg6dShGcQSeCC7nZQ8E=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20230612134759-b20c9ba983df/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4=
|
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16/go.mod h1:IKrnDWs3/Mqq5n0lI+RxA2sB7MvN/vbMBP3ehXg65UI=
|
||||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||||
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@@ -70,35 +68,34 @@ github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
|
|||||||
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
github.com/mholt/acmez v1.1.0 h1:IQ9CGHKOHokorxnffsqDvmmE30mDenO1lptYZ1AYkHY=
|
||||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
github.com/mholt/acmez v1.1.0/go.mod h1:zwo5+fbLLTowAX8o8ETfQzbDtwGEXnPhkmGdKIP+bgs=
|
||||||
github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
|
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
|
||||||
github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
||||||
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
|
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
|
||||||
github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w=
|
github.com/ooni/go-libtor v1.1.7 h1:ooVcdEPBqDox5OfeXAfXIeQFCbqMLJVfIpO+Irr7N9A=
|
||||||
github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI=
|
github.com/ooni/go-libtor v1.1.7/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI=
|
||||||
github.com/oschwald/maxminddb-golang v1.11.0 h1:aSXMqYR/EPNjGE8epgqwDay+P30hCBZIveY0WZbAWh0=
|
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
|
||||||
github.com/oschwald/maxminddb-golang v1.11.0/go.mod h1:YmVI+H0zh3ySFR3w+oz8PCfglAFj3PuCmui13+P9zDg=
|
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
|
||||||
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
||||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||||
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||||
github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U=
|
github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U=
|
||||||
github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc=
|
github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc=
|
||||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk=
|
||||||
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
|
||||||
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt1Jtp5vW2ohNvstvQffTOQ/s5vENuGXzdA+TM=
|
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt1Jtp5vW2ohNvstvQffTOQ/s5vENuGXzdA+TM=
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I=
|
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I=
|
||||||
@@ -106,32 +103,26 @@ github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c
|
|||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||||
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035 h1:KttYh6bBhIw8Y6/Ljn7CGwC3CKZn788rzMJmeAKjY+8=
|
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035 h1:KttYh6bBhIw8Y6/Ljn7CGwC3CKZn788rzMJmeAKjY+8=
|
||||||
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU=
|
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU=
|
||||||
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2 h1:dnkKrzapqtAwjTSWt6hdPrARORfoYvuUczynvRLrueo=
|
|
||||||
github.com/sagernet/gvisor v0.0.0-20230627031050-1ab0276e0dd2/go.mod h1:1JUiV7nGuf++YFm9eWZ8q2lrwHmhcUGzptMl/vL1+LA=
|
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02 h1:9S+L1n/4hbe1pCLNTZnnddSNseQda8tuSm/+uRy6p8s=
|
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 h1:tztuJB+giOWNRKQEBVY2oI3PsheTooMdh+/yxemYQYY=
|
||||||
github.com/sagernet/quic-go v0.0.0-20230615020047-10f05c797c02/go.mod h1:rth94YcHJfkC4mG03JTXmv7mJsDc8MOIIqQrCtoaV4U=
|
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32/go.mod h1:QMCkxXAC3CvBgDZVIJp43NWTuwGBScCzMLVLynjERL8=
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
||||||
github.com/sagernet/sing v0.2.7 h1:cOy0FfPS8q7m0aJ51wS7LRQAGc9wF+fWhHtBDj99wy8=
|
github.com/sagernet/sing v0.2.4 h1:gC8BR5sglbJZX23RtMyFa8EETP9YEUADhfbEzU1yVbo=
|
||||||
github.com/sagernet/sing v0.2.7/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
github.com/sagernet/sing v0.2.4/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
|
||||||
github.com/sagernet/sing-dns v0.1.6 h1:qQRxmtUjCYkRLyzkTHpBvZQeAMeKpZwYsBOcs4U7gns=
|
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc h1:hmbuqKv48SAjiKPoqtJGvS5pEHVPZjTHq9CPwQY2cZ4=
|
||||||
github.com/sagernet/sing-dns v0.1.6/go.mod h1:DY3LYJXIsM/kezUIJxf2TwBzqTTQAepi2KgazkDfahA=
|
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc/go.mod h1:ZKuuqgsHRxDahYrzgSgy4vIAGGuKPlIf4hLcNzYzLkY=
|
||||||
github.com/sagernet/sing-mux v0.1.0 h1:xihlDRNs1J+hYwmvW9/ZmaghjDx7O0Y5dty0pOLQGB4=
|
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507 h1:bAHZCdWqJkb8LEW98+YsMVDXGRMUVjka8IC+St6ot88=
|
||||||
github.com/sagernet/sing-mux v0.1.0/go.mod h1:i3jKjV4pRTFTV/ly5V3oa2JMPy0SAZ5X8X4tDU9Hw94=
|
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507/go.mod h1:UJjvQGw0lyYaDGIDvUraL16fwaAEH1WFw1Y6sUcMPog=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.2 h1:ezSdVhrmIcwDXmCZF3bOJVMuVtTQWpda+1Op+Ie2TA4=
|
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b h1:ouW/6IDCrxkBe19YSbdCd7buHix7b+UZ6BM4Zz74XF4=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.2/go.mod h1:JIBWG6a7orB2HxBxYElViQFLUQxFVG7DuqIj8gD7uCQ=
|
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b/go.mod h1:oG8bPerYI6cZ74KquY3DvA7ynECyrILPBnce6wtBqeI=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.1.1 h1:/cZteeSFXyHKg0uOparIFNj8hHrV8F2rRzTm8arpBTs=
|
github.com/sagernet/sing-tun v0.1.5-0.20230422121432-209ec123ca7b h1:9NsciSJGwzdkXwVvT2c2g+RvkTVkANeBLr2l+soJ7LM=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.1.1/go.mod h1:p18C731ogLED66ZgC1SNYMOXAOxJIRwcTSUk73q/rsc=
|
github.com/sagernet/sing-tun v0.1.5-0.20230422121432-209ec123ca7b/go.mod h1:DD7Ce2Gt0GFc6I/1+Uw4D/aUlBsGqrQsC52CMK/V818=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.2 h1:wkPf4gF+cmaP0cIbArpyq+mc6GcwbMx60CssmmhEQ0s=
|
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3 h1:BHOnxrbC929JonuKqFdJ7ZbDp7zs4oTlH5KFvKtWu9U=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.2/go.mod h1:rTxhbSY8jGWZOWjdeOe1vP3E+hkgen8aRA2p7YccM88=
|
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3/go.mod h1:yKrAr+dqZd64DxBXCHWrYicp+n4qbqO73mtwv3dck8U=
|
||||||
github.com/sagernet/sing-tun v0.1.8 h1:nc5mAdsYVB5TAv0UI0DaZJrd+sOv+HVf6V5B6XfJgSI=
|
|
||||||
github.com/sagernet/sing-tun v0.1.8/go.mod h1:Vj0AcDoneVIYMx8QujpXs2NQJ5Byc0FPhnRj5V3RGdo=
|
|
||||||
github.com/sagernet/sing-vmess v0.1.6 h1:u9VhPNMP0u1vaEjWRWitJQ4KKYPhTF0rorpAlQZcFBg=
|
|
||||||
github.com/sagernet/sing-vmess v0.1.6/go.mod h1:XYXpk405G+kxRMNfREhROJsBxh1ccHy1v/fWSV5lx38=
|
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE=
|
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE=
|
||||||
@@ -142,76 +133,98 @@ github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+V
|
|||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo=
|
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0=
|
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0=
|
||||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
|
|
||||||
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
|
|
||||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
|
||||||
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||||
|
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||||
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 h1:nJAwRlGWZZDOD+6wni9KVUNHMpHko/OnRwsrCYeAzPo=
|
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 h1:nJAwRlGWZZDOD+6wni9KVUNHMpHko/OnRwsrCYeAzPo=
|
||||||
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y=
|
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35/go.mod h1:TQvodOM+hJTioNQJilmLXu08JNb8i+ccq418+KWu1/Y=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
|
||||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
|
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde h1:ybF7AMzIUikL9x4LgwEmzhXtzRpKNqngme1VGDWz+Nk=
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ=
|
||||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
|
||||||
google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ=
|
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
|
||||||
google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s=
|
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
||||||
|
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
@@ -223,5 +236,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
|
gvisor.dev/gvisor v0.0.0-20230415003630-3981d5d5e523 h1:zUQYeyyPLnSR6yMvLSOmLH37xDWCZ7BqlpE69fE5K3Q=
|
||||||
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
gvisor.dev/gvisor v0.0.0-20230415003630-3981d5d5e523/go.mod h1:pzr6sy8gDLfVmDAg8OYrlKvGEHw5C3PGTiBXBTCx76Q=
|
||||||
|
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
||||||
|
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ type Hysteria struct {
|
|||||||
xplusKey []byte
|
xplusKey []byte
|
||||||
sendBPS uint64
|
sendBPS uint64
|
||||||
recvBPS uint64
|
recvBPS uint64
|
||||||
listener *quic.Listener
|
listener quic.Listener
|
||||||
udpAccess sync.RWMutex
|
udpAccess sync.RWMutex
|
||||||
udpSessionId uint32
|
udpSessionId uint32
|
||||||
udpSessions map[uint32]chan *hysteria.UDPMessage
|
udpSessions map[uint32]chan *hysteria.UDPMessage
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ import (
|
|||||||
"github.com/sagernet/sing/common/ranges"
|
"github.com/sagernet/sing/common/ranges"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.Inbound = (*Tun)(nil)
|
var (
|
||||||
|
_ adapter.Inbound = (*Tun)(nil)
|
||||||
|
_ tun.Router = (*Tun)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
type Tun struct {
|
type Tun struct {
|
||||||
tag string
|
tag string
|
||||||
@@ -158,6 +161,10 @@ func (t *Tun) Start() error {
|
|||||||
}
|
}
|
||||||
t.logger.Trace("creating stack")
|
t.logger.Trace("creating stack")
|
||||||
t.tunIf = tunInterface
|
t.tunIf = tunInterface
|
||||||
|
var tunRouter tun.Router
|
||||||
|
if len(t.router.IPRules()) > 0 {
|
||||||
|
tunRouter = t
|
||||||
|
}
|
||||||
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
|
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
|
||||||
Context: t.ctx,
|
Context: t.ctx,
|
||||||
Tun: tunInterface,
|
Tun: tunInterface,
|
||||||
@@ -167,6 +174,7 @@ func (t *Tun) Start() error {
|
|||||||
Inet6Address: t.tunOptions.Inet6Address,
|
Inet6Address: t.tunOptions.Inet6Address,
|
||||||
EndpointIndependentNat: t.endpointIndependentNat,
|
EndpointIndependentNat: t.endpointIndependentNat,
|
||||||
UDPTimeout: t.udpTimeout,
|
UDPTimeout: t.udpTimeout,
|
||||||
|
Router: tunRouter,
|
||||||
Handler: t,
|
Handler: t,
|
||||||
Logger: t.logger,
|
Logger: t.logger,
|
||||||
ForwarderBindInterface: t.platformInterface != nil,
|
ForwarderBindInterface: t.platformInterface != nil,
|
||||||
@@ -191,6 +199,21 @@ func (t *Tun) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tun) RouteConnection(session tun.RouteSession, conn tun.RouteContext) tun.RouteAction {
|
||||||
|
ctx := log.ContextWithNewID(t.ctx)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
metadata.Inbound = t.tag
|
||||||
|
metadata.InboundType = C.TypeTun
|
||||||
|
metadata.IPVersion = session.IPVersion
|
||||||
|
metadata.Network = tun.NetworkName(session.Network)
|
||||||
|
metadata.Source = M.SocksaddrFromNetIP(session.Source)
|
||||||
|
metadata.Destination = M.SocksaddrFromNetIP(session.Destination)
|
||||||
|
metadata.InboundOptions = t.inboundOptions
|
||||||
|
t.logger.DebugContext(ctx, "incoming connection from ", metadata.Source)
|
||||||
|
t.logger.DebugContext(ctx, "incoming connection to ", metadata.Destination)
|
||||||
|
return t.router.RouteIPConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error {
|
func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error {
|
||||||
ctx = log.ContextWithNewID(ctx)
|
ctx = log.ContextWithNewID(ctx)
|
||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ nav:
|
|||||||
- configuration/route/index.md
|
- configuration/route/index.md
|
||||||
- GeoIP: configuration/route/geoip.md
|
- GeoIP: configuration/route/geoip.md
|
||||||
- Geosite: configuration/route/geosite.md
|
- Geosite: configuration/route/geosite.md
|
||||||
|
- IP Route Rule: configuration/route/ip-rule.md
|
||||||
- Route Rule: configuration/route/rule.md
|
- Route Rule: configuration/route/rule.md
|
||||||
- Protocol Sniff: configuration/route/sniff.md
|
- Protocol Sniff: configuration/route/sniff.md
|
||||||
- Experimental:
|
- Experimental:
|
||||||
@@ -114,7 +115,6 @@ nav:
|
|||||||
- ShadowTLS: examples/shadowtls.md
|
- ShadowTLS: examples/shadowtls.md
|
||||||
- Clash API: examples/clash-api.md
|
- Clash API: examples/clash-api.md
|
||||||
- WireGuard Direct: examples/wireguard-direct.md
|
- WireGuard Direct: examples/wireguard-direct.md
|
||||||
- FakeIP: examples/fakeip.md
|
|
||||||
- Contributing:
|
- Contributing:
|
||||||
- contributing/index.md
|
- contributing/index.md
|
||||||
- Developing:
|
- Developing:
|
||||||
@@ -147,7 +147,6 @@ extra:
|
|||||||
link: https://github.com/SagerNet/sing-box
|
link: https://github.com/SagerNet/sing-box
|
||||||
generator: false
|
generator: false
|
||||||
plugins:
|
plugins:
|
||||||
- search
|
|
||||||
- i18n:
|
- i18n:
|
||||||
default_language: en
|
default_language: en
|
||||||
languages:
|
languages:
|
||||||
@@ -174,6 +173,7 @@ plugins:
|
|||||||
DNS Rule: DNS 规则
|
DNS Rule: DNS 规则
|
||||||
|
|
||||||
Route: 路由
|
Route: 路由
|
||||||
|
IP Route Rule: IP 路由规则
|
||||||
Route Rule: 路由规则
|
Route Rule: 路由规则
|
||||||
Protocol Sniff: 协议探测
|
Protocol Sniff: 协议探测
|
||||||
|
|
||||||
@@ -192,4 +192,4 @@ plugins:
|
|||||||
Known Issues: 已知问题
|
Known Issues: 已知问题
|
||||||
Examples: 示例
|
Examples: 示例
|
||||||
Linux Server Installation: Linux 服务器安装
|
Linux Server Installation: Linux 服务器安装
|
||||||
DNS Hijack: DNS 劫持
|
DNS Hijack: DNS 劫持
|
||||||
@@ -10,7 +10,6 @@ type ClashAPIOptions struct {
|
|||||||
StoreSelected bool `json:"store_selected,omitempty"`
|
StoreSelected bool `json:"store_selected,omitempty"`
|
||||||
StoreFakeIP bool `json:"store_fakeip,omitempty"`
|
StoreFakeIP bool `json:"store_fakeip,omitempty"`
|
||||||
CacheFile string `json:"cache_file,omitempty"`
|
CacheFile string `json:"cache_file,omitempty"`
|
||||||
CacheID string `json:"cache_id,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type SelectorOutboundOptions struct {
|
type SelectorOutboundOptions struct {
|
||||||
|
|||||||
@@ -20,10 +20,9 @@ type DNSServerOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DNSClientOptions struct {
|
type DNSClientOptions struct {
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
DisableExpire bool `json:"disable_expire,omitempty"`
|
DisableExpire bool `json:"disable_expire,omitempty"`
|
||||||
IndependentCache bool `json:"independent_cache,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSFakeIPOptions struct {
|
type DNSFakeIPOptions struct {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package option
|
|||||||
type RouteOptions struct {
|
type RouteOptions struct {
|
||||||
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
|
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
|
||||||
Geosite *GeositeOptions `json:"geosite,omitempty"`
|
Geosite *GeositeOptions `json:"geosite,omitempty"`
|
||||||
|
IPRules []IPRule `json:"ip_rules,omitempty"`
|
||||||
Rules []Rule `json:"rules,omitempty"`
|
Rules []Rule `json:"rules,omitempty"`
|
||||||
Final string `json:"final,omitempty"`
|
Final string `json:"final,omitempty"`
|
||||||
FindProcess bool `json:"find_process,omitempty"`
|
FindProcess bool `json:"find_process,omitempty"`
|
||||||
|
|||||||
120
option/rule_ip.go
Normal file
120
option/rule_ip.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _IPRule struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
DefaultOptions DefaultIPRule `json:"-"`
|
||||||
|
LogicalOptions LogicalIPRule `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPRule _IPRule
|
||||||
|
|
||||||
|
func (r IPRule) MarshalJSON() ([]byte, error) {
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
r.Type = ""
|
||||||
|
v = r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
return MarshallObjects((_IPRule)(r), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IPRule) UnmarshalJSON(bytes []byte) error {
|
||||||
|
err := json.Unmarshal(bytes, (*_IPRule)(r))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case "", C.RuleTypeDefault:
|
||||||
|
r.Type = C.RuleTypeDefault
|
||||||
|
v = &r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = &r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
err = UnmarshallExcluded(bytes, (*_IPRule)(r), v)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "ip route rule")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultIPRule struct {
|
||||||
|
Inbound Listable[string] `json:"inbound,omitempty"`
|
||||||
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
|
Network Listable[string] `json:"network,omitempty"`
|
||||||
|
Domain Listable[string] `json:"domain,omitempty"`
|
||||||
|
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
|
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
|
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
||||||
|
Geosite Listable[string] `json:"geosite,omitempty"`
|
||||||
|
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
||||||
|
GeoIP Listable[string] `json:"geoip,omitempty"`
|
||||||
|
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
|
IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
|
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
||||||
|
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
||||||
|
Port Listable[uint16] `json:"port,omitempty"`
|
||||||
|
PortRange Listable[string] `json:"port_range,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Action RouteAction `json:"action,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RouteAction tun.ActionType
|
||||||
|
|
||||||
|
func (a RouteAction) MarshalJSON() ([]byte, error) {
|
||||||
|
typeName, err := tun.ActionTypeName(tun.ActionType(a))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(typeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RouteAction) UnmarshalJSON(bytes []byte) error {
|
||||||
|
var value string
|
||||||
|
err := json.Unmarshal(bytes, &value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
actionType, err := tun.ParseActionType(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*a = RouteAction(actionType)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r DefaultIPRule) IsValid() bool {
|
||||||
|
var defaultValue DefaultIPRule
|
||||||
|
defaultValue.Invert = r.Invert
|
||||||
|
defaultValue.Outbound = r.Outbound
|
||||||
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogicalIPRule struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Rules []DefaultIPRule `json:"rules,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Action RouteAction `json:"action,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r LogicalIPRule) IsValid() bool {
|
||||||
|
return len(r.Rules) > 0 && common.All(r.Rules, DefaultIPRule.IsValid)
|
||||||
|
}
|
||||||
@@ -20,7 +20,6 @@ type VLESSOutboundOptions struct {
|
|||||||
Flow string `json:"flow,omitempty"`
|
Flow string `json:"flow,omitempty"`
|
||||||
Network NetworkList `json:"network,omitempty"`
|
Network NetworkList `json:"network,omitempty"`
|
||||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||||
Multiplex *MultiplexOptions `json:"multiplex,omitempty"`
|
|
||||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||||
PacketEncoding *string `json:"packet_encoding,omitempty"`
|
PacketEncoding *string `json:"packet_encoding,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type WireGuardOutboundOptions struct {
|
|||||||
Workers int `json:"workers,omitempty"`
|
Workers int `json:"workers,omitempty"`
|
||||||
MTU uint32 `json:"mtu,omitempty"`
|
MTU uint32 `json:"mtu,omitempty"`
|
||||||
Network NetworkList `json:"network,omitempty"`
|
Network NetworkList `json:"network,omitempty"`
|
||||||
|
IPRewrite bool `json:"ip_rewrite,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WireGuardPeer struct {
|
type WireGuardPeer struct {
|
||||||
|
|||||||
@@ -11,7 +11,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"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"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"
|
||||||
@@ -21,12 +20,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type myOutboundAdapter struct {
|
type myOutboundAdapter struct {
|
||||||
protocol string
|
protocol string
|
||||||
network []string
|
network []string
|
||||||
router adapter.Router
|
router adapter.Router
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
tag string
|
tag string
|
||||||
dependencies []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *myOutboundAdapter) Type() string {
|
func (a *myOutboundAdapter) Type() string {
|
||||||
@@ -41,21 +39,10 @@ func (a *myOutboundAdapter) Network() []string {
|
|||||||
return a.network
|
return a.network
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *myOutboundAdapter) Dependencies() []string {
|
|
||||||
return a.dependencies
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *myOutboundAdapter) NewError(ctx context.Context, err error) {
|
func (a *myOutboundAdapter) NewError(ctx context.Context, err error) {
|
||||||
NewError(a.logger, ctx, err)
|
NewError(a.logger, ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func withDialerDependency(options option.DialerOptions) []string {
|
|
||||||
if options.Detour != "" {
|
|
||||||
return []string{options.Detour}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
|
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
ctx = adapter.WithContext(ctx, &metadata)
|
ctx = adapter.WithContext(ctx, &metadata)
|
||||||
var outConn net.Conn
|
var outConn net.Conn
|
||||||
|
|||||||
@@ -40,12 +40,11 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti
|
|||||||
options.UDPFragmentDefault = true
|
options.UDPFragmentDefault = true
|
||||||
outbound := &Direct{
|
outbound := &Direct{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeDirect,
|
protocol: C.TypeDirect,
|
||||||
network: []string{N.NetworkTCP, N.NetworkUDP},
|
network: []string{N.NetworkTCP, N.NetworkUDP},
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: withDialerDependency(options.DialerOptions),
|
|
||||||
},
|
},
|
||||||
domainStrategy: dns.DomainStrategy(options.DomainStrategy),
|
domainStrategy: dns.DomainStrategy(options.DomainStrategy),
|
||||||
fallbackDelay: time.Duration(options.FallbackDelay),
|
fallbackDelay: time.Duration(options.FallbackDelay),
|
||||||
|
|||||||
144
outbound/dns.go
144
outbound/dns.go
@@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"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/canceler"
|
"github.com/sagernet/sing/common/canceler"
|
||||||
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"
|
||||||
@@ -102,142 +101,29 @@ func (d *DNS) handleConnection(ctx context.Context, conn net.Conn, metadata adap
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
var reader N.PacketReader = conn
|
|
||||||
var counters []N.CountFunc
|
|
||||||
var cachedPackets []*N.PacketBuffer
|
|
||||||
for {
|
|
||||||
reader, counters = N.UnwrapCountPacketReader(reader, counters)
|
|
||||||
if cachedReader, isCached := reader.(N.CachedPacketReader); isCached {
|
|
||||||
packet := cachedReader.ReadCachedPacket()
|
|
||||||
if packet != nil {
|
|
||||||
cachedPackets = append(cachedPackets, packet)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if readWaiter, created := bufio.CreatePacketReadWaiter(reader); created {
|
|
||||||
return d.newPacketConnection(ctx, conn, readWaiter, counters, cachedPackets, metadata)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ctx = adapter.WithContext(ctx, &metadata)
|
ctx = adapter.WithContext(ctx, &metadata)
|
||||||
fastClose, cancel := common.ContextWithCancelCause(ctx)
|
fastClose, cancel := common.ContextWithCancelCause(ctx)
|
||||||
timeout := canceler.New(fastClose, cancel, C.DNSTimeout)
|
timeout := canceler.New(fastClose, cancel, C.DNSTimeout)
|
||||||
var group task.Group
|
var group task.Group
|
||||||
group.Append0(func(ctx context.Context) error {
|
group.Append0(func(ctx context.Context) error {
|
||||||
|
_buffer := buf.StackNewSize(dns.FixedPacketSize)
|
||||||
|
defer common.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
defer buffer.Release()
|
||||||
for {
|
for {
|
||||||
var message mDNS.Msg
|
|
||||||
var destination M.Socksaddr
|
|
||||||
var err error
|
|
||||||
if len(cachedPackets) > 0 {
|
|
||||||
packet := cachedPackets[0]
|
|
||||||
cachedPackets = cachedPackets[1:]
|
|
||||||
for _, counter := range counters {
|
|
||||||
counter(int64(packet.Buffer.Len()))
|
|
||||||
}
|
|
||||||
err = message.Unpack(packet.Buffer.Bytes())
|
|
||||||
packet.Buffer.Release()
|
|
||||||
if err != nil {
|
|
||||||
cancel(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
destination = packet.Destination
|
|
||||||
} else {
|
|
||||||
buffer := buf.NewPacket()
|
|
||||||
destination, err = conn.ReadPacket(buffer)
|
|
||||||
if err != nil {
|
|
||||||
buffer.Release()
|
|
||||||
cancel(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, counter := range counters {
|
|
||||||
counter(int64(buffer.Len()))
|
|
||||||
}
|
|
||||||
err = message.Unpack(buffer.Bytes())
|
|
||||||
buffer.Release()
|
|
||||||
if err != nil {
|
|
||||||
cancel(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
timeout.Update()
|
|
||||||
}
|
|
||||||
metadataInQuery := metadata
|
|
||||||
go func() error {
|
|
||||||
response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message)
|
|
||||||
if err != nil {
|
|
||||||
cancel(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
timeout.Update()
|
|
||||||
responseBuffer := buf.NewPacket()
|
|
||||||
n, err := response.PackBuffer(responseBuffer.FreeBytes())
|
|
||||||
if err != nil {
|
|
||||||
cancel(err)
|
|
||||||
responseBuffer.Release()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
responseBuffer.Truncate(len(n))
|
|
||||||
err = conn.WritePacket(responseBuffer, destination)
|
|
||||||
if err != nil {
|
|
||||||
cancel(err)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
group.Cleanup(func() {
|
|
||||||
conn.Close()
|
|
||||||
})
|
|
||||||
return group.Run(fastClose)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWaiter N.PacketReadWaiter, readCounters []N.CountFunc, cached []*N.PacketBuffer, metadata adapter.InboundContext) error {
|
|
||||||
ctx = adapter.WithContext(ctx, &metadata)
|
|
||||||
fastClose, cancel := common.ContextWithCancelCause(ctx)
|
|
||||||
timeout := canceler.New(fastClose, cancel, C.DNSTimeout)
|
|
||||||
var group task.Group
|
|
||||||
group.Append0(func(ctx context.Context) error {
|
|
||||||
var buffer *buf.Buffer
|
|
||||||
readWaiter.InitializeReadWaiter(func() *buf.Buffer {
|
|
||||||
buffer = buf.NewSize(dns.FixedPacketSize)
|
|
||||||
buffer.FullReset()
|
buffer.FullReset()
|
||||||
return buffer
|
destination, err := conn.ReadPacket(buffer)
|
||||||
})
|
if err != nil {
|
||||||
defer readWaiter.InitializeReadWaiter(nil)
|
cancel(err)
|
||||||
for {
|
return err
|
||||||
var message mDNS.Msg
|
|
||||||
var destination M.Socksaddr
|
|
||||||
var err error
|
|
||||||
if len(cached) > 0 {
|
|
||||||
packet := cached[0]
|
|
||||||
cached = cached[1:]
|
|
||||||
for _, counter := range readCounters {
|
|
||||||
counter(int64(packet.Buffer.Len()))
|
|
||||||
}
|
|
||||||
err = message.Unpack(packet.Buffer.Bytes())
|
|
||||||
packet.Buffer.Release()
|
|
||||||
if err != nil {
|
|
||||||
cancel(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
destination = packet.Destination
|
|
||||||
} else {
|
|
||||||
destination, err = readWaiter.WaitReadPacket()
|
|
||||||
if err != nil {
|
|
||||||
buffer.Release()
|
|
||||||
cancel(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, counter := range readCounters {
|
|
||||||
counter(int64(buffer.Len()))
|
|
||||||
}
|
|
||||||
err = message.Unpack(buffer.Bytes())
|
|
||||||
buffer.Release()
|
|
||||||
if err != nil {
|
|
||||||
cancel(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
timeout.Update()
|
|
||||||
}
|
}
|
||||||
|
var message mDNS.Msg
|
||||||
|
err = message.Unpack(buffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
cancel(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
timeout.Update()
|
||||||
metadataInQuery := metadata
|
metadataInQuery := metadata
|
||||||
go func() error {
|
go func() error {
|
||||||
response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message)
|
response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message)
|
||||||
|
|||||||
@@ -39,12 +39,11 @@ func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, option
|
|||||||
}
|
}
|
||||||
return &HTTP{
|
return &HTTP{
|
||||||
myOutboundAdapter{
|
myOutboundAdapter{
|
||||||
protocol: C.TypeHTTP,
|
protocol: C.TypeHTTP,
|
||||||
network: []string{N.NetworkTCP},
|
network: []string{N.NetworkTCP},
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: withDialerDependency(options.DialerOptions),
|
|
||||||
},
|
},
|
||||||
sHTTP.NewClient(sHTTP.Options{
|
sHTTP.NewClient(sHTTP.Options{
|
||||||
Dialer: detour,
|
Dialer: detour,
|
||||||
|
|||||||
@@ -119,12 +119,11 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
}
|
}
|
||||||
return &Hysteria{
|
return &Hysteria{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeHysteria,
|
protocol: C.TypeHysteria,
|
||||||
network: options.Network.Build(),
|
network: options.Network.Build(),
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: withDialerDependency(options.DialerOptions),
|
|
||||||
},
|
},
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
dialer: dialer.New(router, options.DialerOptions),
|
dialer: dialer.New(router, options.DialerOptions),
|
||||||
@@ -151,7 +150,6 @@ func (h *Hysteria) offer(ctx context.Context) (quic.Connection, error) {
|
|||||||
if conn != nil && !common.Done(conn.Context()) {
|
if conn != nil && !common.Done(conn.Context()) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
common.Close(h.rawConn)
|
|
||||||
conn, err := h.offerNew(ctx)
|
conn, err := h.offerNew(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -178,7 +176,7 @@ func (h *Hysteria) offerNew(ctx context.Context) (quic.Connection, error) {
|
|||||||
packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey)
|
packetConn = hysteria.NewXPlusPacketConn(packetConn, h.xplusKey)
|
||||||
}
|
}
|
||||||
packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn}
|
packetConn = &hysteria.PacketConnWrapper{PacketConn: packetConn}
|
||||||
quicConn, err := quic.Dial(h.ctx, packetConn, udpConn.RemoteAddr(), h.tlsConfig, h.quicConfig)
|
quicConn, err := quic.Dial(packetConn, udpConn.RemoteAddr(), h.serverAddr.AddrString(), h.tlsConfig, h.quicConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
packetConn.Close()
|
packetConn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -262,19 +260,13 @@ func (h *Hysteria) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hysteria) open(ctx context.Context, reconnect bool) (quic.Connection, quic.Stream, error) {
|
func (h *Hysteria) open(ctx context.Context) (quic.Connection, quic.Stream, error) {
|
||||||
conn, err := h.offer(ctx)
|
conn, err := h.offer(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if nErr, ok := err.(net.Error); ok && !nErr.Temporary() && reconnect {
|
|
||||||
return h.open(ctx, false)
|
|
||||||
}
|
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
stream, err := conn.OpenStream()
|
stream, err := conn.OpenStream()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if nErr, ok := err.(net.Error); ok && !nErr.Temporary() && reconnect {
|
|
||||||
return h.open(ctx, false)
|
|
||||||
}
|
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return conn, &hysteria.StreamWrapper{Stream: stream}, nil
|
return conn, &hysteria.StreamWrapper{Stream: stream}, nil
|
||||||
@@ -284,7 +276,7 @@ func (h *Hysteria) DialContext(ctx context.Context, network string, destination
|
|||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkTCP:
|
case N.NetworkTCP:
|
||||||
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
||||||
_, stream, err := h.open(ctx, true)
|
_, stream, err := h.open(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -310,7 +302,7 @@ func (h *Hysteria) DialContext(ctx context.Context, network string, destination
|
|||||||
|
|
||||||
func (h *Hysteria) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (h *Hysteria) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||||
conn, stream, err := h.open(ctx, true)
|
conn, stream, err := h.open(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,11 +29,10 @@ type Selector struct {
|
|||||||
func NewSelector(router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (*Selector, error) {
|
func NewSelector(router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (*Selector, error) {
|
||||||
outbound := &Selector{
|
outbound := &Selector{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeSelector,
|
protocol: C.TypeSelector,
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: options.Outbounds,
|
|
||||||
},
|
},
|
||||||
tags: options.Outbounds,
|
tags: options.Outbounds,
|
||||||
defaultTag: options.Default,
|
defaultTag: options.Default,
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/transport/sip003"
|
"github.com/sagernet/sing-box/transport/sip003"
|
||||||
"github.com/sagernet/sing-shadowsocks2"
|
"github.com/sagernet/sing-shadowsocks"
|
||||||
|
"github.com/sagernet/sing-shadowsocks/shadowimpl"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"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"
|
||||||
@@ -33,20 +34,17 @@ type Shadowsocks struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (*Shadowsocks, error) {
|
func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (*Shadowsocks, error) {
|
||||||
method, err := shadowsocks.CreateMethod(ctx, options.Method, shadowsocks.MethodOptions{
|
method, err := shadowimpl.FetchMethod(options.Method, options.Password, router.TimeFunc())
|
||||||
Password: options.Password,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
outbound := &Shadowsocks{
|
outbound := &Shadowsocks{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeShadowsocks,
|
protocol: C.TypeShadowsocks,
|
||||||
network: options.Network.Build(),
|
network: options.Network.Build(),
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: withDialerDependency(options.DialerOptions),
|
|
||||||
},
|
},
|
||||||
dialer: dialer.New(router, options.DialerOptions),
|
dialer: dialer.New(router, options.DialerOptions),
|
||||||
method: method,
|
method: method,
|
||||||
@@ -60,7 +58,7 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
|
|||||||
}
|
}
|
||||||
uotOptions := common.PtrValueOrDefault(options.UDPOverTCPOptions)
|
uotOptions := common.PtrValueOrDefault(options.UDPOverTCPOptions)
|
||||||
if !uotOptions.Enabled {
|
if !uotOptions.Enabled {
|
||||||
outbound.multiplexDialer, err = mux.NewClientWithOptions((*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions))
|
outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,12 +39,11 @@ type ShadowsocksR struct {
|
|||||||
func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (*ShadowsocksR, error) {
|
func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (*ShadowsocksR, error) {
|
||||||
outbound := &ShadowsocksR{
|
outbound := &ShadowsocksR{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeShadowsocksR,
|
protocol: C.TypeShadowsocksR,
|
||||||
network: options.Network.Build(),
|
network: options.Network.Build(),
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: withDialerDependency(options.DialerOptions),
|
|
||||||
},
|
},
|
||||||
dialer: dialer.New(router, options.DialerOptions),
|
dialer: dialer.New(router, options.DialerOptions),
|
||||||
serverAddr: options.ServerOptions.Build(),
|
serverAddr: options.ServerOptions.Build(),
|
||||||
|
|||||||
@@ -27,12 +27,11 @@ type ShadowTLS struct {
|
|||||||
func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (*ShadowTLS, error) {
|
func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (*ShadowTLS, error) {
|
||||||
outbound := &ShadowTLS{
|
outbound := &ShadowTLS{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeShadowTLS,
|
protocol: C.TypeShadowTLS,
|
||||||
network: []string{N.NetworkTCP},
|
network: []string{N.NetworkTCP},
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: withDialerDependency(options.DialerOptions),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if options.TLS == nil || !options.TLS.Enabled {
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
|
|||||||
@@ -39,12 +39,11 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio
|
|||||||
}
|
}
|
||||||
outbound := &Socks{
|
outbound := &Socks{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeSocks,
|
protocol: C.TypeSocks,
|
||||||
network: options.Network.Build(),
|
network: options.Network.Build(),
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: withDialerDependency(options.DialerOptions),
|
|
||||||
},
|
},
|
||||||
client: socks.NewClient(dialer.New(router, options.DialerOptions), options.ServerOptions.Build(), version, options.Username, options.Password),
|
client: socks.NewClient(dialer.New(router, options.DialerOptions), options.ServerOptions.Build(), version, options.Username, options.Password),
|
||||||
resolve: version == socks.Version4,
|
resolve: version == socks.Version4,
|
||||||
|
|||||||
@@ -46,12 +46,11 @@ type SSH struct {
|
|||||||
func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (*SSH, error) {
|
func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (*SSH, error) {
|
||||||
outbound := &SSH{
|
outbound := &SSH{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeSSH,
|
protocol: C.TypeSSH,
|
||||||
network: []string{N.NetworkTCP},
|
network: []string{N.NetworkTCP},
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: withDialerDependency(options.DialerOptions),
|
|
||||||
},
|
},
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
dialer: dialer.New(router, options.DialerOptions),
|
dialer: dialer.New(router, options.DialerOptions),
|
||||||
|
|||||||
@@ -68,12 +68,11 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
|||||||
}
|
}
|
||||||
return &Tor{
|
return &Tor{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeTor,
|
protocol: C.TypeTor,
|
||||||
network: []string{N.NetworkTCP},
|
network: []string{N.NetworkTCP},
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: withDialerDependency(options.DialerOptions),
|
|
||||||
},
|
},
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
proxy: NewProxyListener(ctx, logger, dialer.New(router, options.DialerOptions)),
|
proxy: NewProxyListener(ctx, logger, dialer.New(router, options.DialerOptions)),
|
||||||
|
|||||||
@@ -35,12 +35,11 @@ type Trojan struct {
|
|||||||
func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (*Trojan, error) {
|
func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (*Trojan, error) {
|
||||||
outbound := &Trojan{
|
outbound := &Trojan{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeTrojan,
|
protocol: C.TypeTrojan,
|
||||||
network: options.Network.Build(),
|
network: options.Network.Build(),
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: withDialerDependency(options.DialerOptions),
|
|
||||||
},
|
},
|
||||||
dialer: dialer.New(router, options.DialerOptions),
|
dialer: dialer.New(router, options.DialerOptions),
|
||||||
serverAddr: options.ServerOptions.Build(),
|
serverAddr: options.ServerOptions.Build(),
|
||||||
@@ -59,7 +58,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
|
|||||||
return nil, E.Cause(err, "create client transport: ", options.Transport.Type)
|
return nil, E.Cause(err, "create client transport: ", options.Transport.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outbound.multiplexDialer, err = mux.NewClientWithOptions((*trojanDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
|
outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*trojanDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,11 +39,10 @@ type URLTest struct {
|
|||||||
func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) {
|
func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) {
|
||||||
outbound := &URLTest{
|
outbound := &URLTest{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeURLTest,
|
protocol: C.TypeURLTest,
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: options.Outbounds,
|
|
||||||
},
|
},
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
tags: options.Outbounds,
|
tags: options.Outbounds,
|
||||||
@@ -234,7 +233,7 @@ func (g *URLTestGroup) Fallback(used adapter.Outbound) []adapter.Outbound {
|
|||||||
outbounds = append(outbounds, detour)
|
outbounds = append(outbounds, detour)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.SliceStable(outbounds, func(i, j int) bool {
|
sort.Slice(outbounds, func(i, j int) bool {
|
||||||
oi := outbounds[i]
|
oi := outbounds[i]
|
||||||
oj := outbounds[j]
|
oj := outbounds[j]
|
||||||
hi := g.history.LoadURLTestHistory(RealTag(oi))
|
hi := g.history.LoadURLTestHistory(RealTag(oi))
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/mux"
|
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -25,25 +24,23 @@ var _ adapter.Outbound = (*VLESS)(nil)
|
|||||||
|
|
||||||
type VLESS struct {
|
type VLESS struct {
|
||||||
myOutboundAdapter
|
myOutboundAdapter
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
client *vless.Client
|
client *vless.Client
|
||||||
serverAddr M.Socksaddr
|
serverAddr M.Socksaddr
|
||||||
multiplexDialer *mux.Client
|
tlsConfig tls.Config
|
||||||
tlsConfig tls.Config
|
transport adapter.V2RayClientTransport
|
||||||
transport adapter.V2RayClientTransport
|
packetAddr bool
|
||||||
packetAddr bool
|
xudp bool
|
||||||
xudp bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (*VLESS, error) {
|
func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (*VLESS, error) {
|
||||||
outbound := &VLESS{
|
outbound := &VLESS{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeVLESS,
|
protocol: C.TypeVLESS,
|
||||||
network: options.Network.Build(),
|
network: options.Network.Build(),
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: withDialerDependency(options.DialerOptions),
|
|
||||||
},
|
},
|
||||||
dialer: dialer.New(router, options.DialerOptions),
|
dialer: dialer.New(router, options.DialerOptions),
|
||||||
serverAddr: options.ServerOptions.Build(),
|
serverAddr: options.ServerOptions.Build(),
|
||||||
@@ -78,65 +75,10 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
outbound.multiplexDialer, err = mux.NewClientWithOptions((*vlessDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return outbound, nil
|
return outbound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *VLESS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (h *VLESS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
if h.multiplexDialer == nil {
|
|
||||||
switch N.NetworkName(network) {
|
|
||||||
case N.NetworkTCP:
|
|
||||||
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
|
||||||
case N.NetworkUDP:
|
|
||||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
|
||||||
}
|
|
||||||
return (*vlessDialer)(h).DialContext(ctx, network, destination)
|
|
||||||
} else {
|
|
||||||
switch N.NetworkName(network) {
|
|
||||||
case N.NetworkTCP:
|
|
||||||
h.logger.InfoContext(ctx, "outbound multiplex connection to ", destination)
|
|
||||||
case N.NetworkUDP:
|
|
||||||
h.logger.InfoContext(ctx, "outbound multiplex packet connection to ", destination)
|
|
||||||
}
|
|
||||||
return h.multiplexDialer.DialContext(ctx, network, destination)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
|
||||||
if h.multiplexDialer == nil {
|
|
||||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
|
||||||
return (*vlessDialer)(h).ListenPacket(ctx, destination)
|
|
||||||
} else {
|
|
||||||
h.logger.InfoContext(ctx, "outbound multiplex packet connection to ", destination)
|
|
||||||
return h.multiplexDialer.ListenPacket(ctx, destination)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
|
||||||
return NewConnection(ctx, h, conn, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
|
||||||
return NewPacketConnection(ctx, h, conn, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VLESS) InterfaceUpdated() error {
|
|
||||||
if h.multiplexDialer != nil {
|
|
||||||
h.multiplexDialer.Reset()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VLESS) Close() error {
|
|
||||||
return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport)
|
|
||||||
}
|
|
||||||
|
|
||||||
type vlessDialer VLESS
|
|
||||||
|
|
||||||
func (h *vlessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
|
||||||
ctx, metadata := adapter.AppendContext(ctx)
|
ctx, metadata := adapter.AppendContext(ctx)
|
||||||
metadata.Outbound = h.tag
|
metadata.Outbound = h.tag
|
||||||
metadata.Destination = destination
|
metadata.Destination = destination
|
||||||
@@ -178,7 +120,7 @@ func (h *vlessDialer) DialContext(ctx context.Context, network string, destinati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||||
ctx, metadata := adapter.AppendContext(ctx)
|
ctx, metadata := adapter.AppendContext(ctx)
|
||||||
metadata.Outbound = h.tag
|
metadata.Outbound = h.tag
|
||||||
@@ -212,3 +154,15 @@ func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr)
|
|||||||
return h.client.DialEarlyPacketConn(conn, destination)
|
return h.client.DialEarlyPacketConn(conn, destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
return NewConnection(ctx, h, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
|
return NewPacketConnection(ctx, h, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *VLESS) Close() error {
|
||||||
|
return common.Close(h.transport)
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,12 +37,11 @@ type VMess struct {
|
|||||||
func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) {
|
func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) {
|
||||||
outbound := &VMess{
|
outbound := &VMess{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeVMess,
|
protocol: C.TypeVMess,
|
||||||
network: options.Network.Build(),
|
network: options.Network.Build(),
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: withDialerDependency(options.DialerOptions),
|
|
||||||
},
|
},
|
||||||
dialer: dialer.New(router, options.DialerOptions),
|
dialer: dialer.New(router, options.DialerOptions),
|
||||||
serverAddr: options.ServerOptions.Build(),
|
serverAddr: options.ServerOptions.Build(),
|
||||||
@@ -60,7 +59,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
return nil, E.Cause(err, "create client transport: ", options.Transport.Type)
|
return nil, E.Cause(err, "create client transport: ", options.Transport.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outbound.multiplexDialer, err = mux.NewClientWithOptions((*vmessDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
|
outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*vmessDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
@@ -26,7 +28,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ adapter.Outbound = (*WireGuard)(nil)
|
_ adapter.IPOutbound = (*WireGuard)(nil)
|
||||||
_ adapter.InterfaceUpdateListener = (*WireGuard)(nil)
|
_ adapter.InterfaceUpdateListener = (*WireGuard)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,18 +36,18 @@ type WireGuard struct {
|
|||||||
myOutboundAdapter
|
myOutboundAdapter
|
||||||
bind *wireguard.ClientBind
|
bind *wireguard.ClientBind
|
||||||
device *device.Device
|
device *device.Device
|
||||||
|
natDevice wireguard.NatDevice
|
||||||
tunDevice wireguard.Device
|
tunDevice wireguard.Device
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (*WireGuard, error) {
|
func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (*WireGuard, error) {
|
||||||
outbound := &WireGuard{
|
outbound := &WireGuard{
|
||||||
myOutboundAdapter: myOutboundAdapter{
|
myOutboundAdapter: myOutboundAdapter{
|
||||||
protocol: C.TypeWireGuard,
|
protocol: C.TypeWireGuard,
|
||||||
network: options.Network.Build(),
|
network: options.Network.Build(),
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
dependencies: withDialerDependency(options.DialerOptions),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
var reserved [3]uint8
|
var reserved [3]uint8
|
||||||
@@ -156,17 +158,25 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
if mtu == 0 {
|
if mtu == 0 {
|
||||||
mtu = 1408
|
mtu = 1408
|
||||||
}
|
}
|
||||||
var wireTunDevice wireguard.Device
|
var tunDevice wireguard.Device
|
||||||
var err error
|
var err error
|
||||||
if !options.SystemInterface && tun.WithGVisor {
|
if !options.SystemInterface && tun.WithGVisor {
|
||||||
wireTunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu)
|
tunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu, options.IPRewrite)
|
||||||
} else {
|
} else {
|
||||||
wireTunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, localPrefixes, mtu)
|
tunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, localPrefixes, mtu)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create WireGuard device")
|
return nil, E.Cause(err, "create WireGuard device")
|
||||||
}
|
}
|
||||||
wgDevice := device.NewDevice(wireTunDevice, outbound.bind, &device.Logger{
|
natDevice, isNatDevice := tunDevice.(wireguard.NatDevice)
|
||||||
|
if !isNatDevice && router.NatRequired(tag) {
|
||||||
|
natDevice = wireguard.NewNATDevice(tunDevice, options.IPRewrite)
|
||||||
|
}
|
||||||
|
deviceInput := tunDevice
|
||||||
|
if natDevice != nil {
|
||||||
|
deviceInput = natDevice
|
||||||
|
}
|
||||||
|
wgDevice := device.NewDevice(deviceInput, outbound.bind, &device.Logger{
|
||||||
Verbosef: func(format string, args ...interface{}) {
|
Verbosef: func(format string, args ...interface{}) {
|
||||||
logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
|
logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
|
||||||
},
|
},
|
||||||
@@ -182,7 +192,8 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
return nil, E.Cause(err, "setup wireguard")
|
return nil, E.Cause(err, "setup wireguard")
|
||||||
}
|
}
|
||||||
outbound.device = wgDevice
|
outbound.device = wgDevice
|
||||||
outbound.tunDevice = wireTunDevice
|
outbound.natDevice = natDevice
|
||||||
|
outbound.tunDevice = tunDevice
|
||||||
return outbound, nil
|
return outbound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +232,27 @@ func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn,
|
|||||||
return NewPacketConnection(ctx, w, conn, metadata)
|
return NewPacketConnection(ctx, w, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *WireGuard) NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata adapter.InboundContext) (tun.DirectDestination, error) {
|
||||||
|
if w.natDevice == nil {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
session := tun.RouteSession{
|
||||||
|
IPVersion: metadata.IPVersion,
|
||||||
|
Network: tun.NetworkFromName(metadata.Network),
|
||||||
|
Source: metadata.Source.AddrPort(),
|
||||||
|
Destination: metadata.Destination.AddrPort(),
|
||||||
|
}
|
||||||
|
switch session.Network {
|
||||||
|
case syscall.IPPROTO_TCP:
|
||||||
|
w.logger.InfoContext(ctx, "linked connection to ", metadata.Destination)
|
||||||
|
case syscall.IPPROTO_UDP:
|
||||||
|
w.logger.InfoContext(ctx, "linked packet connection to ", metadata.Destination)
|
||||||
|
default:
|
||||||
|
w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection to ", metadata.Destination.AddrString())
|
||||||
|
}
|
||||||
|
return w.natDevice.CreateDestination(session, conn), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (w *WireGuard) Start() error {
|
func (w *WireGuard) Start() error {
|
||||||
return w.tunDevice.Start()
|
return w.tunDevice.Start()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ type Router struct {
|
|||||||
outbounds []adapter.Outbound
|
outbounds []adapter.Outbound
|
||||||
outboundByTag map[string]adapter.Outbound
|
outboundByTag map[string]adapter.Outbound
|
||||||
rules []adapter.Rule
|
rules []adapter.Rule
|
||||||
|
ipRules []adapter.IPRule
|
||||||
defaultDetour string
|
defaultDetour string
|
||||||
defaultOutboundForConnection adapter.Outbound
|
defaultOutboundForConnection adapter.Outbound
|
||||||
defaultOutboundForPacketConnection adapter.Outbound
|
defaultOutboundForPacketConnection adapter.Outbound
|
||||||
@@ -98,6 +99,7 @@ func NewRouter(
|
|||||||
dnsLogger: logFactory.NewLogger("dns"),
|
dnsLogger: logFactory.NewLogger("dns"),
|
||||||
outboundByTag: make(map[string]adapter.Outbound),
|
outboundByTag: make(map[string]adapter.Outbound),
|
||||||
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
||||||
|
ipRules: make([]adapter.IPRule, 0, len(options.IPRules)),
|
||||||
dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)),
|
dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)),
|
||||||
needGeoIPDatabase: hasRule(options.Rules, isGeoIPRule) || hasDNSRule(dnsOptions.Rules, isGeoIPDNSRule),
|
needGeoIPDatabase: hasRule(options.Rules, isGeoIPRule) || hasDNSRule(dnsOptions.Rules, isGeoIPDNSRule),
|
||||||
needGeositeDatabase: hasRule(options.Rules, isGeositeRule) || hasDNSRule(dnsOptions.Rules, isGeositeDNSRule),
|
needGeositeDatabase: hasRule(options.Rules, isGeositeRule) || hasDNSRule(dnsOptions.Rules, isGeositeDNSRule),
|
||||||
@@ -111,12 +113,7 @@ func NewRouter(
|
|||||||
defaultMark: options.DefaultMark,
|
defaultMark: options.DefaultMark,
|
||||||
platformInterface: platformInterface,
|
platformInterface: platformInterface,
|
||||||
}
|
}
|
||||||
router.dnsClient = dns.NewClient(dns.ClientOptions{
|
router.dnsClient = dns.NewClient(dnsOptions.DNSClientOptions.DisableCache, dnsOptions.DNSClientOptions.DisableExpire, router.dnsLogger)
|
||||||
DisableCache: dnsOptions.DNSClientOptions.DisableCache,
|
|
||||||
DisableExpire: dnsOptions.DNSClientOptions.DisableExpire,
|
|
||||||
IndependentCache: dnsOptions.DNSClientOptions.IndependentCache,
|
|
||||||
Logger: router.dnsLogger,
|
|
||||||
})
|
|
||||||
for i, ruleOptions := range options.Rules {
|
for i, ruleOptions := range options.Rules {
|
||||||
routeRule, err := NewRule(router, router.logger, ruleOptions)
|
routeRule, err := NewRule(router, router.logger, ruleOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -124,6 +121,13 @@ func NewRouter(
|
|||||||
}
|
}
|
||||||
router.rules = append(router.rules, routeRule)
|
router.rules = append(router.rules, routeRule)
|
||||||
}
|
}
|
||||||
|
for i, ipRuleOptions := range options.IPRules {
|
||||||
|
ipRule, err := NewIPRule(router, router.logger, ipRuleOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse ip rule[", i, "]")
|
||||||
|
}
|
||||||
|
router.ipRules = append(router.ipRules, ipRule)
|
||||||
|
}
|
||||||
for i, dnsRuleOptions := range dnsOptions.Rules {
|
for i, dnsRuleOptions := range dnsOptions.Rules {
|
||||||
dnsRule, err := NewDNSRule(router, router.logger, dnsRuleOptions)
|
dnsRule, err := NewDNSRule(router, router.logger, dnsRuleOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -270,8 +274,7 @@ func NewRouter(
|
|||||||
router.networkMonitor = networkMonitor
|
router.networkMonitor = networkMonitor
|
||||||
networkMonitor.RegisterCallback(router.interfaceFinder.update)
|
networkMonitor.RegisterCallback(router.interfaceFinder.update)
|
||||||
interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, tun.DefaultInterfaceMonitorOptions{
|
interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, tun.DefaultInterfaceMonitorOptions{
|
||||||
OverrideAndroidVPN: options.OverrideAndroidVPN,
|
OverrideAndroidVPN: options.OverrideAndroidVPN,
|
||||||
UnderNetworkExtension: platformInterface != nil && platformInterface.UnderNetworkExtension(),
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.New("auto_detect_interface unsupported on current platform")
|
return nil, E.New("auto_detect_interface unsupported on current platform")
|
||||||
@@ -595,8 +598,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
switch metadata.Destination.Fqdn {
|
switch metadata.Destination.Fqdn {
|
||||||
case mux.Destination.Fqdn:
|
case mux.Destination.Fqdn:
|
||||||
r.logger.InfoContext(ctx, "inbound multiplex connection")
|
r.logger.InfoContext(ctx, "inbound multiplex connection")
|
||||||
handler := adapter.NewUpstreamHandler(metadata, r.RouteConnection, r.RoutePacketConnection, r)
|
return mux.NewConnection(ctx, r, r, r.logger, conn, metadata)
|
||||||
return mux.HandleConnection(ctx, handler, r.logger, conn, adapter.UpstreamMetadata(metadata))
|
|
||||||
case vmess.MuxDestination.Fqdn:
|
case vmess.MuxDestination.Fqdn:
|
||||||
r.logger.InfoContext(ctx, "inbound legacy multiplex connection")
|
r.logger.InfoContext(ctx, "inbound legacy multiplex connection")
|
||||||
return vmess.HandleMuxConnection(ctx, conn, adapter.NewUpstreamHandler(metadata, r.RouteConnection, r.RoutePacketConnection, r))
|
return vmess.HandleMuxConnection(ctx, conn, adapter.NewUpstreamHandler(metadata, r.RouteConnection, r.RoutePacketConnection, r))
|
||||||
@@ -629,7 +631,6 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
Fqdn: domain,
|
Fqdn: domain,
|
||||||
Port: metadata.Destination.Port,
|
Port: metadata.Destination.Port,
|
||||||
}
|
}
|
||||||
metadata.FakeIP = true
|
|
||||||
r.logger.DebugContext(ctx, "found fakeip domain: ", domain)
|
r.logger.DebugContext(ctx, "found fakeip domain: ", domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -739,7 +740,6 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
Fqdn: domain,
|
Fqdn: domain,
|
||||||
Port: metadata.Destination.Port,
|
Port: metadata.Destination.Port,
|
||||||
}
|
}
|
||||||
metadata.FakeIP = true
|
|
||||||
r.logger.DebugContext(ctx, "found fakeip domain: ", domain)
|
r.logger.DebugContext(ctx, "found fakeip domain: ", domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -922,6 +922,10 @@ func (r *Router) Rules() []adapter.Rule {
|
|||||||
return r.rules
|
return r.rules
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) IPRules() []adapter.IPRule {
|
||||||
|
return r.ipRules
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Router) NetworkMonitor() tun.NetworkUpdateMonitor {
|
func (r *Router) NetworkMonitor() tun.NetworkUpdateMonitor {
|
||||||
return r.networkMonitor
|
return r.networkMonitor
|
||||||
}
|
}
|
||||||
@@ -983,22 +987,9 @@ func (r *Router) notifyNetworkUpdate(int) error {
|
|||||||
r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()))
|
r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()))
|
||||||
}
|
}
|
||||||
|
|
||||||
conntrack.Close()
|
if conntrack.Enabled {
|
||||||
|
conntrack.Close()
|
||||||
for _, outbound := range r.outbounds {
|
}
|
||||||
listener, isListener := outbound.(adapter.InterfaceUpdateListener)
|
|
||||||
if isListener {
|
|
||||||
err := listener.InterfaceUpdated()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) ResetNetwork() error {
|
|
||||||
conntrack.Close()
|
|
||||||
|
|
||||||
for _, outbound := range r.outbounds {
|
for _, outbound := range r.outbounds {
|
||||||
listener, isListener := outbound.(adapter.InterfaceUpdateListener)
|
listener, isListener := outbound.(adapter.InterfaceUpdateListener)
|
||||||
|
|||||||
@@ -44,27 +44,22 @@ func (r *Router) matchDNS(ctx context.Context) (context.Context, dns.Transport,
|
|||||||
}
|
}
|
||||||
for i, rule := range r.dnsRules {
|
for i, rule := range r.dnsRules {
|
||||||
if rule.Match(metadata) {
|
if rule.Match(metadata) {
|
||||||
detour := rule.Outbound()
|
|
||||||
transport, loaded := r.transportMap[detour]
|
|
||||||
if !loaded {
|
|
||||||
r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, isFakeIP := transport.(adapter.FakeIPTransport); isFakeIP && metadata.FakeIP {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
|
|
||||||
if rule.DisableCache() {
|
if rule.DisableCache() {
|
||||||
ctx = dns.ContextWithDisableCache(ctx, true)
|
ctx = dns.ContextWithDisableCache(ctx, true)
|
||||||
}
|
}
|
||||||
if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil {
|
if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil {
|
||||||
ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL)
|
ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL)
|
||||||
}
|
}
|
||||||
if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded {
|
detour := rule.Outbound()
|
||||||
return ctx, transport, domainStrategy
|
r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
|
||||||
} else {
|
if transport, loaded := r.transportMap[detour]; loaded {
|
||||||
return ctx, transport, r.defaultDomainStrategy
|
if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded {
|
||||||
|
return ctx, transport, domainStrategy
|
||||||
|
} else {
|
||||||
|
return ctx, transport, r.defaultDomainStrategy
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if domainStrategy, dsLoaded := r.transportDomainStrategy[r.defaultTransport]; dsLoaded {
|
if domainStrategy, dsLoaded := r.transportDomainStrategy[r.defaultTransport]; dsLoaded {
|
||||||
@@ -78,31 +73,23 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
|
|||||||
if len(message.Question) > 0 {
|
if len(message.Question) > 0 {
|
||||||
r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String()))
|
r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String()))
|
||||||
}
|
}
|
||||||
var (
|
ctx, metadata := adapter.AppendContext(ctx)
|
||||||
response *mDNS.Msg
|
if len(message.Question) > 0 {
|
||||||
cached bool
|
metadata.QueryType = message.Question[0].Qtype
|
||||||
err error
|
switch metadata.QueryType {
|
||||||
)
|
case mDNS.TypeA:
|
||||||
response, cached = r.dnsClient.ExchangeCache(ctx, message)
|
metadata.IPVersion = 4
|
||||||
if !cached {
|
case mDNS.TypeAAAA:
|
||||||
ctx, metadata := adapter.AppendContext(ctx)
|
metadata.IPVersion = 6
|
||||||
if len(message.Question) > 0 {
|
|
||||||
metadata.QueryType = message.Question[0].Qtype
|
|
||||||
switch metadata.QueryType {
|
|
||||||
case mDNS.TypeA:
|
|
||||||
metadata.IPVersion = 4
|
|
||||||
case mDNS.TypeAAAA:
|
|
||||||
metadata.IPVersion = 6
|
|
||||||
}
|
|
||||||
metadata.Domain = fqdnToDomain(message.Question[0].Name)
|
|
||||||
}
|
|
||||||
ctx, transport, strategy := r.matchDNS(ctx)
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout)
|
|
||||||
defer cancel()
|
|
||||||
response, err = r.dnsClient.Exchange(ctx, transport, message, strategy)
|
|
||||||
if err != nil && len(message.Question) > 0 {
|
|
||||||
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String())))
|
|
||||||
}
|
}
|
||||||
|
metadata.Domain = fqdnToDomain(message.Question[0].Name)
|
||||||
|
}
|
||||||
|
ctx, transport, strategy := r.matchDNS(ctx)
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout)
|
||||||
|
defer cancel()
|
||||||
|
response, err := r.dnsClient.Exchange(ctx, transport, message, strategy)
|
||||||
|
if err != nil && len(message.Question) > 0 {
|
||||||
|
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String())))
|
||||||
}
|
}
|
||||||
if len(message.Question) > 0 && response != nil {
|
if len(message.Question) > 0 && response != nil {
|
||||||
LogDNSAnswers(r.dnsLogger, ctx, message.Question[0].Name, response.Answer)
|
LogDNSAnswers(r.dnsLogger, ctx, message.Question[0].Name, response.Answer)
|
||||||
|
|||||||
79
route/router_ip.go
Normal file
79
route/router_ip.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-dns"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *Router) RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata adapter.InboundContext) tun.RouteAction {
|
||||||
|
if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) {
|
||||||
|
domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr)
|
||||||
|
if !loaded {
|
||||||
|
r.logger.ErrorContext(ctx, "missing fakeip context")
|
||||||
|
return (*tun.ActionReturn)(nil)
|
||||||
|
}
|
||||||
|
metadata.Destination = M.Socksaddr{
|
||||||
|
Fqdn: domain,
|
||||||
|
Port: metadata.Destination.Port,
|
||||||
|
}
|
||||||
|
r.logger.DebugContext(ctx, "found fakeip domain: ", domain)
|
||||||
|
}
|
||||||
|
if r.dnsReverseMapping != nil && metadata.Domain == "" {
|
||||||
|
domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr)
|
||||||
|
if loaded {
|
||||||
|
metadata.Domain = domain
|
||||||
|
r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
|
||||||
|
addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy))
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorContext(ctx, err)
|
||||||
|
return (*tun.ActionReturn)(nil)
|
||||||
|
}
|
||||||
|
metadata.DestinationAddresses = addresses
|
||||||
|
r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]")
|
||||||
|
}
|
||||||
|
for i, rule := range r.ipRules {
|
||||||
|
if rule.Match(&metadata) {
|
||||||
|
if rule.Action() == tun.ActionTypeBlock {
|
||||||
|
r.logger.InfoContext(ctx, "match[", i, "] ", rule.String(), " => block")
|
||||||
|
return (*tun.ActionBlock)(nil)
|
||||||
|
}
|
||||||
|
detour := rule.Outbound()
|
||||||
|
r.logger.InfoContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
|
||||||
|
outbound, loaded := r.Outbound(detour)
|
||||||
|
if !loaded {
|
||||||
|
r.logger.ErrorContext(ctx, "outbound not found: ", detour)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ipOutbound, loaded := outbound.(adapter.IPOutbound)
|
||||||
|
if !loaded {
|
||||||
|
r.logger.ErrorContext(ctx, "outbound have no ip connection support: ", detour)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
destination, err := ipOutbound.NewIPConnection(ctx, conn, metadata)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorContext(ctx, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return &tun.ActionDirect{DirectDestination: destination}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (*tun.ActionReturn)(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) NatRequired(outbound string) bool {
|
||||||
|
for _, ipRule := range r.ipRules {
|
||||||
|
if ipRule.Outbound() == outbound {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
189
route/rule_ip.go
Normal file
189
route/rule_ip.go
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewIPRule(router adapter.Router, logger log.ContextLogger, options option.IPRule) (adapter.IPRule, error) {
|
||||||
|
switch options.Type {
|
||||||
|
case "", C.RuleTypeDefault:
|
||||||
|
if !options.DefaultOptions.IsValid() {
|
||||||
|
return nil, E.New("missing conditions")
|
||||||
|
}
|
||||||
|
if common.IsEmpty(options.DefaultOptions.Action) {
|
||||||
|
return nil, E.New("missing action")
|
||||||
|
}
|
||||||
|
return NewDefaultIPRule(router, logger, options.DefaultOptions)
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
if !options.LogicalOptions.IsValid() {
|
||||||
|
return nil, E.New("missing conditions")
|
||||||
|
}
|
||||||
|
if common.IsEmpty(options.DefaultOptions.Action) {
|
||||||
|
return nil, E.New("missing action")
|
||||||
|
}
|
||||||
|
return NewLogicalIPRule(router, logger, options.LogicalOptions)
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown rule type: ", options.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.IPRule = (*DefaultIPRule)(nil)
|
||||||
|
|
||||||
|
type DefaultIPRule struct {
|
||||||
|
abstractDefaultRule
|
||||||
|
action tun.ActionType
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultIPRule(router adapter.Router, logger log.ContextLogger, options option.DefaultIPRule) (*DefaultIPRule, error) {
|
||||||
|
rule := &DefaultIPRule{
|
||||||
|
abstractDefaultRule: abstractDefaultRule{
|
||||||
|
invert: options.Invert,
|
||||||
|
outbound: options.Outbound,
|
||||||
|
},
|
||||||
|
action: tun.ActionType(options.Action),
|
||||||
|
}
|
||||||
|
if len(options.Inbound) > 0 {
|
||||||
|
item := NewInboundRule(options.Inbound)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if options.IPVersion > 0 {
|
||||||
|
switch options.IPVersion {
|
||||||
|
case 4, 6:
|
||||||
|
item := NewIPVersionItem(options.IPVersion == 6)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
default:
|
||||||
|
return nil, E.New("invalid ip version: ", options.IPVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(options.Network) > 0 {
|
||||||
|
item := NewNetworkItem(options.Network)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 {
|
||||||
|
item := NewDomainItem(options.Domain, options.DomainSuffix)
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.DomainKeyword) > 0 {
|
||||||
|
item := NewDomainKeywordItem(options.DomainKeyword)
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.DomainRegex) > 0 {
|
||||||
|
item, err := NewDomainRegexItem(options.DomainRegex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "domain_regex")
|
||||||
|
}
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.Geosite) > 0 {
|
||||||
|
item := NewGeositeItem(router, logger, options.Geosite)
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.SourceGeoIP) > 0 {
|
||||||
|
item := NewGeoIPItem(router, logger, true, options.SourceGeoIP)
|
||||||
|
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.GeoIP) > 0 {
|
||||||
|
item := NewGeoIPItem(router, logger, false, options.GeoIP)
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.SourceIPCIDR) > 0 {
|
||||||
|
item, err := NewIPCIDRItem(true, options.SourceIPCIDR)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "source_ipcidr")
|
||||||
|
}
|
||||||
|
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.IPCIDR) > 0 {
|
||||||
|
item, err := NewIPCIDRItem(false, options.IPCIDR)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "ipcidr")
|
||||||
|
}
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.SourcePort) > 0 {
|
||||||
|
item := NewPortItem(true, options.SourcePort)
|
||||||
|
rule.sourcePortItems = append(rule.sourcePortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.SourcePortRange) > 0 {
|
||||||
|
item, err := NewPortRangeItem(true, options.SourcePortRange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "source_port_range")
|
||||||
|
}
|
||||||
|
rule.sourcePortItems = append(rule.sourcePortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.Port) > 0 {
|
||||||
|
item := NewPortItem(false, options.Port)
|
||||||
|
rule.destinationPortItems = append(rule.destinationPortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.PortRange) > 0 {
|
||||||
|
item, err := NewPortRangeItem(false, options.PortRange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "port_range")
|
||||||
|
}
|
||||||
|
rule.destinationPortItems = append(rule.destinationPortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultIPRule) Action() tun.ActionType {
|
||||||
|
return r.action
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.IPRule = (*LogicalIPRule)(nil)
|
||||||
|
|
||||||
|
type LogicalIPRule struct {
|
||||||
|
abstractLogicalRule
|
||||||
|
action tun.ActionType
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogicalIPRule(router adapter.Router, logger log.ContextLogger, options option.LogicalIPRule) (*LogicalIPRule, error) {
|
||||||
|
r := &LogicalIPRule{
|
||||||
|
abstractLogicalRule: abstractLogicalRule{
|
||||||
|
rules: make([]adapter.Rule, len(options.Rules)),
|
||||||
|
invert: options.Invert,
|
||||||
|
outbound: options.Outbound,
|
||||||
|
},
|
||||||
|
action: tun.ActionType(options.Action),
|
||||||
|
}
|
||||||
|
switch options.Mode {
|
||||||
|
case C.LogicalTypeAnd:
|
||||||
|
r.mode = C.LogicalTypeAnd
|
||||||
|
case C.LogicalTypeOr:
|
||||||
|
r.mode = C.LogicalTypeOr
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown logical mode: ", options.Mode)
|
||||||
|
}
|
||||||
|
for i, subRule := range options.Rules {
|
||||||
|
rule, err := NewDefaultIPRule(router, logger, subRule)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "sub rule[", i, "]")
|
||||||
|
}
|
||||||
|
r.rules[i] = rule
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LogicalIPRule) Action() tun.ActionType {
|
||||||
|
return r.action
|
||||||
|
}
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing-shadowsocks2/shadowstream"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestShadowsocksLegacy(t *testing.T) {
|
|
||||||
testShadowsocksLegacy(t, shadowstream.MethodList[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func testShadowsocksLegacy(t *testing.T, method string) {
|
|
||||||
startDockerContainer(t, DockerOptions{
|
|
||||||
Image: ImageShadowsocksLegacy,
|
|
||||||
Ports: []uint16{serverPort},
|
|
||||||
Env: []string{
|
|
||||||
"SS_MODULE=ss-server",
|
|
||||||
F.ToString("SS_CONFIG=-s 0.0.0.0 -u -p 10000 -m ", method, " -k FzcLbKs2dY9mhL"),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
startInstance(t, option.Options{
|
|
||||||
Inbounds: []option.Inbound{
|
|
||||||
{
|
|
||||||
Type: C.TypeMixed,
|
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
|
||||||
ListenOptions: option.ListenOptions{
|
|
||||||
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
|
||||||
ListenPort: clientPort,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Outbounds: []option.Outbound{
|
|
||||||
{
|
|
||||||
Type: C.TypeShadowsocks,
|
|
||||||
ShadowsocksOptions: option.ShadowsocksOutboundOptions{
|
|
||||||
ServerOptions: option.ServerOptions{
|
|
||||||
Server: "127.0.0.1",
|
|
||||||
ServerPort: serverPort,
|
|
||||||
},
|
|
||||||
Method: method,
|
|
||||||
Password: "FzcLbKs2dY9mhL",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
testSuitSimple(t, clientPort, testPort)
|
|
||||||
}
|
|
||||||
@@ -40,10 +40,6 @@ func TestShadowsocks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShadowsocksNone(t *testing.T) {
|
|
||||||
testShadowsocksSelf(t, "none", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShadowsocks2022(t *testing.T) {
|
func TestShadowsocks2022(t *testing.T) {
|
||||||
for _, method16 := range []string{
|
for _, method16 := range []string{
|
||||||
"2022-blake3-aes-128-gcm",
|
"2022-blake3-aes-128-gcm",
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
package fakeip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *NATPacketConn) CreatePacketReadWaiter() (N.PacketReadWaiter, bool) {
|
|
||||||
waiter, created := bufio.CreatePacketReadWaiter(c.PacketConn)
|
|
||||||
if !created {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return &waitNATPacketConn{c, waiter}, true
|
|
||||||
}
|
|
||||||
|
|
||||||
type waitNATPacketConn struct {
|
|
||||||
*NATPacketConn
|
|
||||||
waiter N.PacketReadWaiter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *waitNATPacketConn) InitializeReadWaiter(newBuffer func() *buf.Buffer) {
|
|
||||||
c.waiter.InitializeReadWaiter(newBuffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *waitNATPacketConn) WaitReadPacket() (destination M.Socksaddr, err error) {
|
|
||||||
destination, err = c.waiter.WaitReadPacket()
|
|
||||||
if socksaddrWithoutPort(destination) == c.origin {
|
|
||||||
destination = M.Socksaddr{
|
|
||||||
Addr: c.destination.Addr,
|
|
||||||
Fqdn: c.destination.Fqdn,
|
|
||||||
Port: destination.Port,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -14,16 +14,13 @@ import (
|
|||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var _ dns.Transport = (*Server)(nil)
|
||||||
_ dns.Transport = (*Transport)(nil)
|
|
||||||
_ adapter.FakeIPTransport = (*Transport)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
dns.RegisterTransport([]string{"fakeip"}, NewTransport)
|
dns.RegisterTransport([]string{"fakeip"}, NewTransport)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Transport struct {
|
type Server struct {
|
||||||
name string
|
name string
|
||||||
router adapter.Router
|
router adapter.Router
|
||||||
store adapter.FakeIPStore
|
store adapter.FakeIPStore
|
||||||
@@ -35,18 +32,18 @@ func NewTransport(name string, ctx context.Context, logger logger.ContextLogger,
|
|||||||
if router == nil {
|
if router == nil {
|
||||||
return nil, E.New("missing router in context")
|
return nil, E.New("missing router in context")
|
||||||
}
|
}
|
||||||
return &Transport{
|
return &Server{
|
||||||
name: name,
|
name: name,
|
||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Transport) Name() string {
|
func (s *Server) Name() string {
|
||||||
return s.name
|
return s.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Transport) Start() error {
|
func (s *Server) Start() error {
|
||||||
s.store = s.router.FakeIPStore()
|
s.store = s.router.FakeIPStore()
|
||||||
if s.store == nil {
|
if s.store == nil {
|
||||||
return E.New("fakeip not enabled")
|
return E.New("fakeip not enabled")
|
||||||
@@ -54,19 +51,19 @@ func (s *Transport) Start() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Transport) Close() error {
|
func (s *Server) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Transport) Raw() bool {
|
func (s *Server) Raw() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (s *Server) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Transport) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) {
|
func (s *Server) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) {
|
||||||
var addresses []netip.Addr
|
var addresses []netip.Addr
|
||||||
if strategy != dns.DomainStrategyUseIPv6 {
|
if strategy != dns.DomainStrategyUseIPv6 {
|
||||||
inet4Address, err := s.store.Create(domain, dns.DomainStrategyUseIPv4)
|
inet4Address, err := s.store.Create(domain, dns.DomainStrategyUseIPv4)
|
||||||
@@ -84,7 +81,3 @@ func (s *Transport) Lookup(ctx context.Context, domain string, strategy dns.Doma
|
|||||||
}
|
}
|
||||||
return addresses, nil
|
return addresses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Transport) Store() adapter.FakeIPStore {
|
|
||||||
return s.store
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -343,7 +343,7 @@ func WriteUDPMessage(conn quic.Connection, message UDPMessage) error {
|
|||||||
buffer := common.Dup(_buffer)
|
buffer := common.Dup(_buffer)
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
err := writeUDPMessage(conn, message, buffer)
|
err := writeUDPMessage(conn, message, buffer)
|
||||||
if errSize, ok := err.(quic.ErrMessageTooLarge); ok {
|
if errSize, ok := err.(quic.ErrMessageToLarge); ok {
|
||||||
// need to frag
|
// need to frag
|
||||||
message.MsgID = uint16(rand.Intn(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
|
message.MsgID = uint16(rand.Intn(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1
|
||||||
fragMsgs := FragUDPMessage(message, int(errSize))
|
fragMsgs := FragUDPMessage(message, int(errSize))
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ func ReadPacket(conn net.Conn, buffer *buf.Buffer) (M.Socksaddr, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, err = buffer.ReadFullFrom(conn, int(length))
|
_, err = buffer.ReadFullFrom(conn, int(length))
|
||||||
return destination.Unwrap(), err
|
return destination, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func WritePacket(conn net.Conn, buffer *buf.Buffer, destination M.Socksaddr) error {
|
func WritePacket(conn net.Conn, buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func (c *Client) offerNew() (quic.Connection, error) {
|
|||||||
}
|
}
|
||||||
var packetConn net.PacketConn
|
var packetConn net.PacketConn
|
||||||
packetConn = bufio.NewUnbindPacketConn(udpConn)
|
packetConn = bufio.NewUnbindPacketConn(udpConn)
|
||||||
quicConn, err := quic.Dial(c.ctx, packetConn, udpConn.RemoteAddr(), c.tlsConfig, c.quicConfig)
|
quicConn, err := quic.Dial(packetConn, udpConn.RemoteAddr(), c.serverAddr.AddrString(), c.tlsConfig, c.quicConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
packetConn.Close()
|
packetConn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ type Server struct {
|
|||||||
handler adapter.V2RayServerTransportHandler
|
handler adapter.V2RayServerTransportHandler
|
||||||
errorHandler E.Handler
|
errorHandler E.Handler
|
||||||
udpListener net.PacketConn
|
udpListener net.PacketConn
|
||||||
quicListener *quic.Listener
|
quicListener quic.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) {
|
func NewServer(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) {
|
||||||
|
|||||||
@@ -42,19 +42,11 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConn, err := tls.ClientHandshake(ctx, conn, tlsConfig)
|
return tls.ClientHandshake(ctx, conn, tlsConfig)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &deadConn{tlsConn}, nil
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
wsDialer.NetDialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
wsDialer.NetDialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &deadConn{conn}, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var uri url.URL
|
var uri url.URL
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package v2raywebsocket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type deadConn struct {
|
|
||||||
net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *deadConn) SetDeadline(t time.Time) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *deadConn) SetReadDeadline(t time.Time) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *deadConn) SetWriteDeadline(t time.Time) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -130,7 +130,7 @@ func WriteRequest(writer io.Writer, request Request, payload []byte) error {
|
|||||||
var addonsLen int
|
var addonsLen int
|
||||||
if request.Flow != "" {
|
if request.Flow != "" {
|
||||||
addonsLen += 1 // protobuf header
|
addonsLen += 1 // protobuf header
|
||||||
addonsLen += rw.UVariantLen(uint64(len(request.Flow)))
|
addonsLen += UvarintLen(uint64(len(request.Flow)))
|
||||||
addonsLen += len(request.Flow)
|
addonsLen += len(request.Flow)
|
||||||
requestLen += addonsLen
|
requestLen += addonsLen
|
||||||
}
|
}
|
||||||
@@ -150,8 +150,8 @@ func WriteRequest(writer io.Writer, request Request, payload []byte) error {
|
|||||||
)
|
)
|
||||||
if addonsLen > 0 {
|
if addonsLen > 0 {
|
||||||
common.Must(buffer.WriteByte(10))
|
common.Must(buffer.WriteByte(10))
|
||||||
binary.PutUvarint(buffer.Extend(rw.UVariantLen(uint64(len(request.Flow)))), uint64(len(request.Flow)))
|
binary.PutUvarint(buffer.Extend(UvarintLen(uint64(len(request.Flow)))), uint64(len(request.Flow)))
|
||||||
common.Must(common.Error(buffer.WriteString(request.Flow)))
|
common.Must(common.Error(buffer.Write([]byte(request.Flow))))
|
||||||
}
|
}
|
||||||
common.Must(
|
common.Must(
|
||||||
buffer.WriteByte(request.Command),
|
buffer.WriteByte(request.Command),
|
||||||
@@ -174,7 +174,7 @@ func EncodeRequest(request Request, buffer *buf.Buffer) {
|
|||||||
var addonsLen int
|
var addonsLen int
|
||||||
if request.Flow != "" {
|
if request.Flow != "" {
|
||||||
addonsLen += 1 // protobuf header
|
addonsLen += 1 // protobuf header
|
||||||
addonsLen += rw.UVariantLen(uint64(len(request.Flow)))
|
addonsLen += UvarintLen(uint64(len(request.Flow)))
|
||||||
addonsLen += len(request.Flow)
|
addonsLen += len(request.Flow)
|
||||||
requestLen += addonsLen
|
requestLen += addonsLen
|
||||||
}
|
}
|
||||||
@@ -189,8 +189,8 @@ func EncodeRequest(request Request, buffer *buf.Buffer) {
|
|||||||
)
|
)
|
||||||
if addonsLen > 0 {
|
if addonsLen > 0 {
|
||||||
common.Must(buffer.WriteByte(10))
|
common.Must(buffer.WriteByte(10))
|
||||||
binary.PutUvarint(buffer.Extend(rw.UVariantLen(uint64(len(request.Flow)))), uint64(len(request.Flow)))
|
binary.PutUvarint(buffer.Extend(UvarintLen(uint64(len(request.Flow)))), uint64(len(request.Flow)))
|
||||||
common.Must(common.Error(buffer.WriteString(request.Flow)))
|
common.Must(common.Error(buffer.Write([]byte(request.Flow))))
|
||||||
}
|
}
|
||||||
common.Must(
|
common.Must(
|
||||||
buffer.WriteByte(request.Command),
|
buffer.WriteByte(request.Command),
|
||||||
@@ -210,7 +210,7 @@ func RequestLen(request Request) int {
|
|||||||
var addonsLen int
|
var addonsLen int
|
||||||
if request.Flow != "" {
|
if request.Flow != "" {
|
||||||
addonsLen += 1 // protobuf header
|
addonsLen += 1 // protobuf header
|
||||||
addonsLen += rw.UVariantLen(uint64(len(request.Flow)))
|
addonsLen += UvarintLen(uint64(len(request.Flow)))
|
||||||
addonsLen += len(request.Flow)
|
addonsLen += len(request.Flow)
|
||||||
requestLen += addonsLen
|
requestLen += addonsLen
|
||||||
}
|
}
|
||||||
@@ -229,7 +229,7 @@ func WritePacketRequest(writer io.Writer, request Request, payload []byte) error
|
|||||||
var addonsLen int
|
var addonsLen int
|
||||||
/*if request.Flow != "" {
|
/*if request.Flow != "" {
|
||||||
addonsLen += 1 // protobuf header
|
addonsLen += 1 // protobuf header
|
||||||
addonsLen += rw.UVariantLen(uint64(len(request.Flow)))
|
addonsLen += UvarintLen(uint64(len(request.Flow)))
|
||||||
addonsLen += len(request.Flow)
|
addonsLen += len(request.Flow)
|
||||||
requestLen += addonsLen
|
requestLen += addonsLen
|
||||||
}*/
|
}*/
|
||||||
@@ -251,8 +251,8 @@ func WritePacketRequest(writer io.Writer, request Request, payload []byte) error
|
|||||||
|
|
||||||
if addonsLen > 0 {
|
if addonsLen > 0 {
|
||||||
common.Must(buffer.WriteByte(10))
|
common.Must(buffer.WriteByte(10))
|
||||||
binary.PutUvarint(buffer.Extend(rw.UVariantLen(uint64(len(request.Flow)))), uint64(len(request.Flow)))
|
binary.PutUvarint(buffer.Extend(UvarintLen(uint64(len(request.Flow)))), uint64(len(request.Flow)))
|
||||||
common.Must(common.Error(buffer.WriteString(request.Flow)))
|
common.Must(common.Error(buffer.Write([]byte(request.Flow))))
|
||||||
}
|
}
|
||||||
|
|
||||||
common.Must(
|
common.Must(
|
||||||
@@ -290,3 +290,8 @@ func ReadResponse(reader io.Reader) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UvarintLen(value uint64) int {
|
||||||
|
var buffer [binary.MaxVarintLen64]byte
|
||||||
|
return binary.PutUvarint(buffer[:], value)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,23 @@
|
|||||||
package wireguard
|
package wireguard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/wireguard-go/tun"
|
wgTun "github.com/sagernet/wireguard-go/tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Device interface {
|
type Device interface {
|
||||||
tun.Device
|
wgTun.Device
|
||||||
N.Dialer
|
N.Dialer
|
||||||
Start() error
|
Start() error
|
||||||
|
Inet4Address() netip.Addr
|
||||||
|
Inet6Address() netip.Addr
|
||||||
// NewEndpoint() (stack.LinkEndpoint, error)
|
// NewEndpoint() (stack.LinkEndpoint, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NatDevice interface {
|
||||||
|
Device
|
||||||
|
CreateDestination(session tun.RouteSession, conn tun.RouteContext) tun.DirectDestination
|
||||||
|
}
|
||||||
|
|||||||
76
transport/wireguard/device_nat.go
Normal file
76
transport/wireguard/device_nat.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package wireguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Device = (*natDeviceWrapper)(nil)
|
||||||
|
|
||||||
|
type natDeviceWrapper struct {
|
||||||
|
Device
|
||||||
|
outbound chan *buf.Buffer
|
||||||
|
mapping *tun.NatMapping
|
||||||
|
writer *tun.NatWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNATDevice(upstream Device, ipRewrite bool) NatDevice {
|
||||||
|
wrapper := &natDeviceWrapper{
|
||||||
|
Device: upstream,
|
||||||
|
outbound: make(chan *buf.Buffer, 256),
|
||||||
|
mapping: tun.NewNatMapping(ipRewrite),
|
||||||
|
}
|
||||||
|
if ipRewrite {
|
||||||
|
wrapper.writer = tun.NewNatWriter(upstream.Inet4Address(), upstream.Inet6Address())
|
||||||
|
}
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDeviceWrapper) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) {
|
||||||
|
select {
|
||||||
|
case packet := <-d.outbound:
|
||||||
|
defer packet.Release()
|
||||||
|
sizes[0] = copy(bufs[0][offset:], packet.Bytes())
|
||||||
|
return 1, nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return d.Device.Read(bufs, sizes, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDeviceWrapper) Write(bufs [][]byte, offset int) (count int, err error) {
|
||||||
|
packet := bufs[0][offset:]
|
||||||
|
handled, err := d.mapping.WritePacket(packet)
|
||||||
|
if handled {
|
||||||
|
return 1, err
|
||||||
|
}
|
||||||
|
return d.Device.Write(bufs, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDeviceWrapper) CreateDestination(session tun.RouteSession, conn tun.RouteContext) tun.DirectDestination {
|
||||||
|
d.mapping.CreateSession(session, conn)
|
||||||
|
return &natDestinationWrapper{d, session}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ tun.DirectDestination = (*natDestinationWrapper)(nil)
|
||||||
|
|
||||||
|
type natDestinationWrapper struct {
|
||||||
|
device *natDeviceWrapper
|
||||||
|
session tun.RouteSession
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDestinationWrapper) WritePacket(buffer *buf.Buffer) error {
|
||||||
|
if d.device.writer != nil {
|
||||||
|
d.device.writer.RewritePacket(buffer.Bytes())
|
||||||
|
}
|
||||||
|
d.device.outbound <- buffer
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDestinationWrapper) Close() error {
|
||||||
|
d.device.mapping.DeleteSession(d.session)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *natDestinationWrapper) Timeout() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
27
transport/wireguard/device_nat_gvisor.go
Normal file
27
transport/wireguard/device_nat_gvisor.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//go:build with_gvisor
|
||||||
|
|
||||||
|
package wireguard
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *natDestinationWrapper) WritePacketBuffer(buffer stack.PacketBufferPtr) error {
|
||||||
|
defer buffer.DecRef()
|
||||||
|
if d.device.writer != nil {
|
||||||
|
d.device.writer.RewritePacketBuffer(buffer)
|
||||||
|
}
|
||||||
|
var packetLen int
|
||||||
|
for _, slice := range buffer.AsSlices() {
|
||||||
|
packetLen += len(slice)
|
||||||
|
}
|
||||||
|
packet := buf.NewSize(packetLen)
|
||||||
|
for _, slice := range buffer.AsSlices() {
|
||||||
|
common.Must1(packet.Write(slice))
|
||||||
|
}
|
||||||
|
d.device.outbound <- packet
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -8,25 +8,26 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/gvisor/pkg/buffer"
|
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
|
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv4"
|
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv6"
|
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp"
|
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
|
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
|
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
wgTun "github.com/sagernet/wireguard-go/tun"
|
wgTun "github.com/sagernet/wireguard-go/tun"
|
||||||
|
|
||||||
|
"gvisor.dev/gvisor/pkg/bufferv2"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Device = (*StackDevice)(nil)
|
var _ NatDevice = (*StackDevice)(nil)
|
||||||
|
|
||||||
const defaultNIC tcpip.NICID = 1
|
const defaultNIC tcpip.NICID = 1
|
||||||
|
|
||||||
@@ -40,9 +41,11 @@ type StackDevice struct {
|
|||||||
dispatcher stack.NetworkDispatcher
|
dispatcher stack.NetworkDispatcher
|
||||||
addr4 tcpip.Address
|
addr4 tcpip.Address
|
||||||
addr6 tcpip.Address
|
addr6 tcpip.Address
|
||||||
|
mapping *tun.NatMapping
|
||||||
|
writer *tun.NatWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (*StackDevice, error) {
|
func NewStackDevice(localAddresses []netip.Prefix, mtu uint32, ipRewrite bool) (*StackDevice, error) {
|
||||||
ipStack := stack.New(stack.Options{
|
ipStack := stack.New(stack.Options{
|
||||||
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
|
||||||
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6},
|
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6},
|
||||||
@@ -55,13 +58,14 @@ func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (*StackDevice, er
|
|||||||
outbound: make(chan stack.PacketBufferPtr, 256),
|
outbound: make(chan stack.PacketBufferPtr, 256),
|
||||||
packetOutbound: make(chan *buf.Buffer, 256),
|
packetOutbound: make(chan *buf.Buffer, 256),
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
|
mapping: tun.NewNatMapping(ipRewrite),
|
||||||
}
|
}
|
||||||
err := ipStack.CreateNIC(defaultNIC, (*wireEndpoint)(tunDevice))
|
err := ipStack.CreateNIC(defaultNIC, (*wireEndpoint)(tunDevice))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.New(err.String())
|
return nil, E.New(err.String())
|
||||||
}
|
}
|
||||||
for _, prefix := range localAddresses {
|
for _, prefix := range localAddresses {
|
||||||
addr := tun.AddressFromAddr(prefix.Addr())
|
addr := tcpip.Address(prefix.Addr().AsSlice())
|
||||||
protoAddr := tcpip.ProtocolAddress{
|
protoAddr := tcpip.ProtocolAddress{
|
||||||
AddressWithPrefix: tcpip.AddressWithPrefix{
|
AddressWithPrefix: tcpip.AddressWithPrefix{
|
||||||
Address: addr,
|
Address: addr,
|
||||||
@@ -80,6 +84,9 @@ func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (*StackDevice, er
|
|||||||
return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", err.String())
|
return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", err.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ipRewrite {
|
||||||
|
tunDevice.writer = tun.NewNatWriter(tunDevice.Inet4Address(), tunDevice.Inet6Address())
|
||||||
|
}
|
||||||
sOpt := tcpip.TCPSACKEnabled(true)
|
sOpt := tcpip.TCPSACKEnabled(true)
|
||||||
ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt)
|
ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt)
|
||||||
cOpt := tcpip.CongestionControlOption("cubic")
|
cOpt := tcpip.CongestionControlOption("cubic")
|
||||||
@@ -97,7 +104,7 @@ func (w *StackDevice) DialContext(ctx context.Context, network string, destinati
|
|||||||
addr := tcpip.FullAddress{
|
addr := tcpip.FullAddress{
|
||||||
NIC: defaultNIC,
|
NIC: defaultNIC,
|
||||||
Port: destination.Port,
|
Port: destination.Port,
|
||||||
Addr: tun.AddressFromAddr(destination.Addr),
|
Addr: tcpip.Address(destination.Addr.AsSlice()),
|
||||||
}
|
}
|
||||||
bind := tcpip.FullAddress{
|
bind := tcpip.FullAddress{
|
||||||
NIC: defaultNIC,
|
NIC: defaultNIC,
|
||||||
@@ -112,7 +119,7 @@ func (w *StackDevice) DialContext(ctx context.Context, network string, destinati
|
|||||||
}
|
}
|
||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkTCP:
|
case N.NetworkTCP:
|
||||||
tcpConn, err := DialTCPWithBind(ctx, w.stack, bind, addr, networkProtocol)
|
tcpConn, err := gonet.DialTCPWithBind(ctx, w.stack, bind, addr, networkProtocol)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -133,7 +140,7 @@ func (w *StackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr)
|
|||||||
NIC: defaultNIC,
|
NIC: defaultNIC,
|
||||||
}
|
}
|
||||||
var networkProtocol tcpip.NetworkProtocolNumber
|
var networkProtocol tcpip.NetworkProtocolNumber
|
||||||
if destination.IsIPv4() {
|
if destination.IsIPv4() || w.addr6 == "" {
|
||||||
networkProtocol = header.IPv4ProtocolNumber
|
networkProtocol = header.IPv4ProtocolNumber
|
||||||
bind.Addr = w.addr4
|
bind.Addr = w.addr4
|
||||||
} else {
|
} else {
|
||||||
@@ -148,11 +155,11 @@ func (w *StackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *StackDevice) Inet4Address() netip.Addr {
|
func (w *StackDevice) Inet4Address() netip.Addr {
|
||||||
return tun.AddrFromAddress(w.addr4)
|
return M.AddrFromIP(net.IP(w.addr4))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *StackDevice) Inet6Address() netip.Addr {
|
func (w *StackDevice) Inet6Address() netip.Addr {
|
||||||
return tun.AddrFromAddress(w.addr6)
|
return M.AddrFromIP(net.IP(w.addr6))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *StackDevice) Start() error {
|
func (w *StackDevice) Start() error {
|
||||||
@@ -196,6 +203,14 @@ func (w *StackDevice) Write(bufs [][]byte, offset int) (count int, err error) {
|
|||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
handled, err := w.mapping.WritePacket(b)
|
||||||
|
if handled {
|
||||||
|
count++
|
||||||
|
if err != nil {
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
var networkProtocol tcpip.NetworkProtocolNumber
|
var networkProtocol tcpip.NetworkProtocolNumber
|
||||||
switch header.IPVersion(b) {
|
switch header.IPVersion(b) {
|
||||||
case header.IPv4Version:
|
case header.IPv4Version:
|
||||||
@@ -204,7 +219,7 @@ func (w *StackDevice) Write(bufs [][]byte, offset int) (count int, err error) {
|
|||||||
networkProtocol = header.IPv6ProtocolNumber
|
networkProtocol = header.IPv6ProtocolNumber
|
||||||
}
|
}
|
||||||
packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||||
Payload: buffer.MakeWithData(b),
|
Payload: bufferv2.MakeWithData(b),
|
||||||
})
|
})
|
||||||
w.dispatcher.DeliverNetworkPacket(networkProtocol, packetBuffer)
|
w.dispatcher.DeliverNetworkPacket(networkProtocol, packetBuffer)
|
||||||
packetBuffer.DecRef()
|
packetBuffer.DecRef()
|
||||||
@@ -248,6 +263,44 @@ func (w *StackDevice) BatchSize() int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *StackDevice) CreateDestination(session tun.RouteSession, conn tun.RouteContext) tun.DirectDestination {
|
||||||
|
w.mapping.CreateSession(session, conn)
|
||||||
|
return &stackNatDestination{
|
||||||
|
device: w,
|
||||||
|
session: session,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type stackNatDestination struct {
|
||||||
|
device *StackDevice
|
||||||
|
session tun.RouteSession
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *stackNatDestination) WritePacket(buffer *buf.Buffer) error {
|
||||||
|
if d.device.writer != nil {
|
||||||
|
d.device.writer.RewritePacket(buffer.Bytes())
|
||||||
|
}
|
||||||
|
d.device.packetOutbound <- buffer
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *stackNatDestination) WritePacketBuffer(buffer stack.PacketBufferPtr) error {
|
||||||
|
if d.device.writer != nil {
|
||||||
|
d.device.writer.RewritePacketBuffer(buffer)
|
||||||
|
}
|
||||||
|
d.device.outbound <- buffer
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *stackNatDestination) Close() error {
|
||||||
|
d.device.mapping.DeleteSession(d.session)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *stackNatDestination) Timeout() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
var _ stack.LinkEndpoint = (*wireEndpoint)(nil)
|
var _ stack.LinkEndpoint = (*wireEndpoint)(nil)
|
||||||
|
|
||||||
type wireEndpoint StackDevice
|
type wireEndpoint StackDevice
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ import (
|
|||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (Device, error) {
|
func NewStackDevice(localAddresses []netip.Prefix, mtu uint32, ipRewrite bool) (Device, error) {
|
||||||
return nil, tun.ErrGVisorNotIncluded
|
return nil, tun.ErrGVisorNotIncluded
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
//go:build with_gvisor
|
|
||||||
|
|
||||||
package wireguard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
|
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
|
||||||
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
|
|
||||||
"github.com/sagernet/gvisor/pkg/waiter"
|
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
)
|
|
||||||
|
|
||||||
func DialTCPWithBind(ctx context.Context, s *stack.Stack, localAddr, remoteAddr tcpip.FullAddress, network tcpip.NetworkProtocolNumber) (*gonet.TCPConn, error) {
|
|
||||||
// Create TCP endpoint, then connect.
|
|
||||||
var wq waiter.Queue
|
|
||||||
ep, err := s.NewEndpoint(tcp.ProtocolNumber, network, &wq)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New(err.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create wait queue entry that notifies a channel.
|
|
||||||
//
|
|
||||||
// We do this unconditionally as Connect will always return an error.
|
|
||||||
waitEntry, notifyCh := waiter.NewChannelEntry(waiter.WritableEvents)
|
|
||||||
wq.EventRegister(&waitEntry)
|
|
||||||
defer wq.EventUnregister(&waitEntry)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind before connect if requested.
|
|
||||||
if localAddr != (tcpip.FullAddress{}) {
|
|
||||||
if err = ep.Bind(localAddr); err != nil {
|
|
||||||
return nil, fmt.Errorf("ep.Bind(%+v) = %s", localAddr, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ep.Connect(remoteAddr)
|
|
||||||
if _, ok := err.(*tcpip.ErrConnectStarted); ok {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
ep.Close()
|
|
||||||
return nil, ctx.Err()
|
|
||||||
case <-notifyCh:
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ep.LastError()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
ep.Close()
|
|
||||||
return nil, &net.OpError{
|
|
||||||
Op: "connect",
|
|
||||||
Net: "tcp",
|
|
||||||
Addr: M.SocksaddrFromNetIP(netip.AddrPortFrom(tun.AddrFromAddress(remoteAddr.Addr), remoteAddr.Port)).TCPAddr(),
|
|
||||||
Err: errors.New(err.String()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sing-box added: set keepalive
|
|
||||||
ep.SocketOptions().SetKeepAlive(true)
|
|
||||||
keepAliveIdle := tcpip.KeepaliveIdleOption(15 * time.Second)
|
|
||||||
ep.SetSockOpt(&keepAliveIdle)
|
|
||||||
keepAliveInterval := tcpip.KeepaliveIntervalOption(15 * time.Second)
|
|
||||||
ep.SetSockOpt(&keepAliveInterval)
|
|
||||||
|
|
||||||
return gonet.NewTCPConn(&wq, ep), nil
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user