Compare commits

..

58 Commits

Author SHA1 Message Date
世界
de1b5971e1 Update documentation 2023-04-16 16:28:41 +08:00
世界
5c20d0b4d5 Update dependencies 2023-04-16 16:28:41 +08:00
世界
9df96ac7f1 Fix deadline usage on websocket conn 2023-04-16 16:28:40 +08:00
世界
87cd925144 Fix conntrack return pointer 2023-04-14 21:00:45 +08:00
世界
fecb796000 android: Remove Seq.Delete warning 2023-04-14 21:00:40 +08:00
世界
cfb6c804aa Print sniff result 2023-04-14 21:00:01 +08:00
世界
11c50c7558 Fix processing domain address in packet 2023-04-14 20:59:57 +08:00
世界
34cc7f176e Fix parsing query in http path 2023-04-14 20:59:16 +08:00
Xiaokang Wang (Shelikhoo)
b54da9c6af Fix '?' at end of WebSocket path get escaped
This fix align sing-box's behaviour with V2Ray when it comes to processing ? at the end of WebSocket's path.
2023-04-14 20:58:12 +08:00
世界
f44f86b832 Fix workflows 2023-04-14 20:57:08 +08:00
世界
4ebf40f582 Fix find process user 2023-04-14 20:56:58 +08:00
世界
53e4302143 Fix set HTTP TLS ALPN 2023-04-14 20:56:55 +08:00
世界
cf778eda4f Fix v2ray http transport server read request 2023-04-14 20:56:51 +08:00
世界
bb63429079 Update cancel context usage 2023-04-14 20:56:16 +08:00
世界
f7f9a7ae20 Fix write log to stderr 2023-04-14 20:55:57 +08:00
世界
8699412a4c platform: Add stderr redirect 2023-04-14 20:55:45 +08:00
世界
0d7aa19cd1 Fix write http status after response sent 2023-04-14 20:55:20 +08:00
世界
50a7295360 Replace usages of uber/atomic 2023-04-14 20:55:05 +08:00
世界
e57b6ae98d Update dependencies 2023-04-14 20:54:56 +08:00
世界
6843970536 Add loopback check 2023-04-08 09:13:50 +08:00
世界
62425ad3e4 Add close monitor 2023-04-08 08:10:03 +08:00
世界
e1e217854e Add start and close track message 2023-04-08 08:09:28 +08:00
世界
5bf177b021 platform: Fix build on windows 2023-04-07 21:10:16 +08:00
世界
72dbf2e2b4 documentation: Update changelog 2023-04-07 19:18:26 +08:00
世界
46c318c6fe Fix v2ray HTTP/1.1 transport compatibility 2023-04-07 18:20:07 +08:00
世界
05bb1b88c3 dns: Fix rewrite TTL 2023-04-07 16:19:34 +08:00
世界
5176ea9fe0 Update dependencies 2023-04-07 16:19:34 +08:00
世界
36d349acd2 dns: Fix calculate TTL 2023-04-07 13:12:16 +08:00
世界
4feee983b5 Update reality protocol 2023-04-06 19:05:05 +08:00
世界
9b12e3e389 Update client documentation 2023-04-06 12:51:26 +08:00
世界
afd3464216 Minor fixes 2023-04-05 21:41:06 +08:00
世界
8b64446274 platform: Fixes and improvements 2023-04-05 19:54:20 +08:00
世界
28aa4c4d1f Refactor log factory constructor 2023-04-03 20:24:13 +08:00
世界
0be3cdc8fb platform: Add http client 2023-04-03 15:12:44 +08:00
armv9
f8be484019 conntrack: Fix missing tracking for udp conn 2023-04-02 12:06:03 +08:00
世界
35f03f092d Improve UDP domain destination NAT 2023-04-02 12:05:59 +08:00
世界
c3d7401ead platform: Add check config func 2023-04-02 10:35:03 +08:00
世界
4db7eb9d9e documentation: Update changelog 2023-03-31 16:29:08 +08:00
世界
fd4efd6104 Fix dns transport read 2023-03-31 14:31:35 +08:00
世界
19a35ec6a4 Fix http2 transport close 2023-03-31 14:31:35 +08:00
世界
2012c0ca1e Update release scripts 2023-03-31 14:31:35 +08:00
世界
187421c754 Append time to session log 2023-03-31 14:31:35 +08:00
世界
b3fb86d415 Accept "any" outbound in dns rule 2023-03-31 14:31:35 +08:00
世界
88fafd4e30 Fix dns routing context 2023-03-31 09:14:04 +08:00
世界
8056932f9c Update documentation 2023-03-27 08:23:01 +08:00
世界
c8af003bfc Update dependencies 2023-03-27 08:22:56 +08:00
世界
4999441a85 Fix missing default host in v2ray http transport`s request 2023-03-27 08:20:59 +08:00
世界
09b001e795 Revert remove install shell 2023-03-27 08:20:55 +08:00
世界
3b3a251008 Update LICENSE 2023-03-27 08:20:51 +08:00
世界
2e4eb9aa39 Update dockerfile 2023-03-24 08:29:11 +08:00
世界
77fd284703 documentation: Update changelog 2023-03-24 08:04:36 +08:00
世界
0a4517f4b7 Update dependencies 2023-03-24 07:06:45 +08:00
世界
4395db3206 documentation: Update set_system_proxy usage 2023-03-23 21:27:50 +08:00
世界
dd5b0abc67 Fix slow open 2023-03-23 17:14:38 +08:00
世界
466800aa3a Fix wireguard mutex 2023-03-23 15:43:17 +08:00
世界
4328c535a9 Improve timeout canceler 2023-03-23 15:39:12 +08:00
世界
f9516709da Update documentation 2023-03-23 07:54:24 +08:00
世界
5dce722879 Update dependencies 2023-03-23 07:49:14 +08:00
170 changed files with 2324 additions and 2230 deletions

View File

@@ -31,12 +31,6 @@ jobs:
uses: actions/setup-go@v4
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Add cache to Go proxy
run: |
version=`git rev-parse HEAD`
@@ -196,12 +190,6 @@ jobs:
uses: actions/setup-go@v4
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: Build
id: build
run: make

View File

@@ -31,12 +31,6 @@ jobs:
uses: actions/setup-go@v4
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Cache go module
uses: actions/cache@v3
with:
path: |
~/go/pkg/mod
key: go-${{ hashFiles('**/go.sum') }}
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:

View File

@@ -117,8 +117,6 @@ nfpms:
dst: /etc/systemd/system/sing-box@.service
- src: LICENSE
dst: /usr/share/licenses/sing-box/LICENSE
scripts:
postremove: release/config/postremove.sh
source:
enabled: false
name_template: '{{ .ProjectName }}-{{ .Version }}.source'

View File

@@ -9,7 +9,7 @@ RUN set -ex \
&& apk add git build-base \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& export VERSION=$(go run ./cmd/internal/read_tag) \
&& go build -v -trimpath -tags with_quic,with_wireguard,with_reality_server,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 \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
./cmd/sing-box

View File

@@ -11,4 +11,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program. If not, see <http://www.gnu.org/licenses/>.
In addition, no derivative work may use the name or imply association
with this application without prior consent.

View File

@@ -77,13 +77,20 @@ test_stdio:
go mod tidy && \
go test -v -tags "$(TAGS_TEST),force_stdio" .
android:
go run ./cmd/internal/build_libbox -target android
ios:
go run ./cmd/internal/build_libbox -target ios
lib:
go run ./cmd/internal/build_libbox
go run ./cmd/internal/build_libbox -target android
go run ./cmd/internal/build_libbox -target ios
lib_install:
go get -v -d
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20221130124640-349ebaa752ca
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20221130124640-349ebaa752ca
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230413023804-244d7ff07035
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230413023804-244d7ff07035
clean:
rm -rf bin dist sing-box

View File

@@ -8,6 +8,10 @@ The universal proxy platform.
https://sing-box.sagernet.org
## Support
https://community.sagernet.org/c/sing-box/
## License
```
@@ -25,4 +29,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
In addition, no derivative work may use the name or imply association
with this application without prior consent.
```

View File

@@ -27,7 +27,7 @@ type InjectableInbound interface {
type InboundContext struct {
Inbound string
InboundType string
IPVersion uint8
IPVersion int
Network string
Source M.Socksaddr
Destination M.Socksaddr

View File

@@ -4,7 +4,6 @@ import (
"context"
"net"
tun "github.com/sagernet/sing-tun"
N "github.com/sagernet/sing/common/network"
)
@@ -18,8 +17,3 @@ type Outbound interface {
NewConnection(ctx context.Context, conn net.Conn, 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)
}

View File

@@ -23,9 +23,6 @@ type Router interface {
RouteConnection(ctx context.Context, conn net.Conn, 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
LoadGeosite(code string) (Rule, error)
@@ -42,9 +39,7 @@ type Router interface {
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
Rules() []Rule
IPRules() []IPRule
TimeService
@@ -83,11 +78,6 @@ type DNSRule interface {
DisableCache() bool
}
type IPRule interface {
Rule
Action() tun.ActionType
}
type InterfaceUpdateListener interface {
InterfaceUpdated() error
}

143
box.go
View File

@@ -9,7 +9,6 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/inbound"
@@ -31,18 +30,25 @@ type Box struct {
outbounds []adapter.Outbound
logFactory log.Factory
logger log.ContextLogger
logFile *os.File
preServices map[string]adapter.Service
postServices map[string]adapter.Service
done chan struct{}
}
func New(ctx context.Context, options option.Options, platformInterface platform.Interface) (*Box, error) {
createdAt := time.Now()
type Options struct {
option.Options
Context context.Context
PlatformInterface platform.Interface
}
func New(options Options) (*Box, error) {
ctx := options.Context
if ctx == nil {
ctx = context.Background()
}
createdAt := time.Now()
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
var needClashAPI bool
var needV2RayAPI bool
if experimentalOptions.ClashAPI != nil && experimentalOptions.ClashAPI.ExternalController != "" {
@@ -51,60 +57,20 @@ func New(ctx context.Context, options option.Options, platformInterface platform
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
logOptions := common.PtrValueOrDefault(options.Log)
var logFactory log.Factory
var observableLogFactory log.ObservableFactory
var logFile *os.File
var logWriter io.Writer
if logOptions.Disabled {
observableLogFactory = log.NewNOPFactory()
logFactory = observableLogFactory
} else {
switch logOptions.Output {
case "":
if platformInterface != nil {
logWriter = io.Discard
} else {
logWriter = os.Stdout
}
case "stderr":
logWriter = os.Stderr
case "stdout":
logWriter = os.Stdout
default:
var err error
logFile, err = os.OpenFile(C.BasePath(logOptions.Output), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return nil, err
}
logWriter = logFile
}
logFormatter := log.Formatter{
BaseTime: createdAt,
DisableColors: logOptions.DisableColor || logFile != nil,
DisableTimestamp: !logOptions.Timestamp && logFile != nil,
FullTimestamp: logOptions.Timestamp,
TimestampFormat: "-0700 2006-01-02 15:04:05",
}
if needClashAPI {
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter, platformInterface)
logFactory = observableLogFactory
} else {
logFactory = log.NewFactory(logFormatter, logWriter, platformInterface)
}
if logOptions.Level != "" {
logLevel, err := log.ParseLevel(logOptions.Level)
if err != nil {
return nil, E.Cause(err, "parse log level")
}
logFactory.SetLevel(logLevel)
} else {
logFactory.SetLevel(log.LevelTrace)
}
var defaultLogWriter io.Writer
if options.PlatformInterface != nil {
defaultLogWriter = io.Discard
}
logFactory, err := log.New(log.Options{
Options: common.PtrValueOrDefault(options.Log),
Observable: needClashAPI,
DefaultWriter: defaultLogWriter,
BaseTime: createdAt,
PlatformWriter: options.PlatformInterface,
})
if err != nil {
return nil, E.Cause(err, "create log factory")
}
router, err := route.NewRouter(
ctx,
logFactory,
@@ -112,7 +78,7 @@ func New(ctx context.Context, options option.Options, platformInterface platform
common.PtrValueOrDefault(options.DNS),
common.PtrValueOrDefault(options.NTP),
options.Inbounds,
platformInterface,
options.PlatformInterface,
)
if err != nil {
return nil, E.Cause(err, "parse route options")
@@ -132,7 +98,7 @@ func New(ctx context.Context, options option.Options, platformInterface platform
router,
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
inboundOptions,
platformInterface,
options.PlatformInterface,
)
if err != nil {
return nil, E.Cause(err, "parse inbound[", i, "]")
@@ -151,6 +117,7 @@ func New(ctx context.Context, options option.Options, platformInterface platform
ctx,
router,
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
tag,
outboundOptions)
if err != nil {
return nil, E.Cause(err, "parse outbound[", i, "]")
@@ -158,7 +125,7 @@ func New(ctx context.Context, options option.Options, platformInterface platform
outbounds = append(outbounds, out)
}
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), option.Outbound{Type: "direct", Tag: "default"})
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
common.Must(oErr)
outbounds = append(outbounds, out)
return out
@@ -169,7 +136,7 @@ func New(ctx context.Context, options option.Options, platformInterface platform
preServices := make(map[string]adapter.Service)
postServices := make(map[string]adapter.Service)
if needClashAPI {
clashServer, err := experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
clashServer, err := experimental.NewClashServer(router, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(options.Experimental.ClashAPI))
if err != nil {
return nil, E.Cause(err, "create clash api server")
}
@@ -191,7 +158,6 @@ func New(ctx context.Context, options option.Options, platformInterface platform
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
logFile: logFile,
preServices: preServices,
postServices: postServices,
done: make(chan struct{}),
@@ -238,21 +204,21 @@ func (s *Box) Start() error {
func (s *Box) preStart() error {
for serviceName, service := range s.preServices {
s.logger.Trace("pre-starting ", serviceName)
s.logger.Trace("pre-start ", serviceName)
err := adapter.PreStart(service)
if err != nil {
return E.Cause(err, "pre-start ", serviceName)
return E.Cause(err, "pre-starting ", serviceName)
}
}
for i, out := range s.outbounds {
var tag string
if out.Tag() == "" {
tag = F.ToString(i)
} else {
tag = out.Tag()
}
if starter, isStarter := out.(common.Starter); isStarter {
var tag string
if out.Tag() == "" {
tag = F.ToString(i)
} else {
tag = out.Tag()
}
s.logger.Trace("initializing outbound ", tag)
s.logger.Trace("initializing outbound/", out.Type(), "[", tag, "]")
err := starter.Start()
if err != nil {
return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
@@ -281,17 +247,17 @@ func (s *Box) start() error {
} else {
tag = in.Tag()
}
s.logger.Trace("initializing inbound ", tag)
s.logger.Trace("initializing inbound/", in.Type(), "[", tag, "]")
err = in.Start()
if err != nil {
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
}
}
for serviceName, service := range s.postServices {
s.logger.Trace("start ", serviceName)
s.logger.Trace("starting ", service)
err = service.Start()
if err != nil {
return E.Cause(err, "starting ", serviceName)
return E.Cause(err, "start ", serviceName)
}
}
return nil
@@ -306,33 +272,21 @@ func (s *Box) Close() error {
}
var errors error
for serviceName, service := range s.postServices {
s.logger.Trace("closing ", serviceName)
errors = E.Append(errors, service.Close(), func(err error) error {
s.logger.Trace("closing ", serviceName)
return E.Cause(err, "close ", serviceName)
})
}
for i, in := range s.inbounds {
var tag string
if in.Tag() == "" {
tag = F.ToString(i)
} else {
tag = in.Tag()
}
s.logger.Trace("closing inbound ", tag)
s.logger.Trace("closing inbound/", in.Type(), "[", i, "]")
errors = E.Append(errors, in.Close(), func(err error) error {
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
})
}
for i, out := range s.outbounds {
var tag string
if out.Tag() == "" {
tag = F.ToString(i)
} else {
tag = out.Tag()
}
s.logger.Trace("closing outbound ", tag)
s.logger.Trace("closing outbound/", out.Type(), "[", i, "]")
errors = E.Append(errors, common.Close(out), func(err error) error {
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
})
}
s.logger.Trace("closing router")
@@ -347,17 +301,12 @@ func (s *Box) Close() error {
return E.Cause(err, "close ", serviceName)
})
}
s.logger.Trace("closing logger")
s.logger.Trace("closing log factory")
if err := common.Close(s.logFactory); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close log factory")
})
}
if s.logFile != nil {
errors = E.Append(errors, s.logFile.Close(), func(err error) error {
return E.Cause(err, "close log file")
})
}
return errors
}

View File

@@ -31,7 +31,10 @@ func check() error {
return err
}
ctx, cancel := context.WithCancel(context.Background())
instance, err := box.New(ctx, options, nil)
instance, err := box.New(box.Options{
Context: ctx,
Options: options,
})
if err == nil {
instance.Close()
}

View File

@@ -10,6 +10,7 @@ import (
"sort"
"strings"
"syscall"
"time"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/common/badjsonmerge"
@@ -127,7 +128,10 @@ func create() (*box.Box, context.CancelFunc, error) {
options.Log.DisableColor = true
}
ctx, cancel := context.WithCancel(context.Background())
instance, err := box.New(ctx, options, nil)
instance, err := box.New(box.Options{
Context: ctx,
Options: options,
})
if err != nil {
cancel()
return nil, nil, E.Cause(err, "create service")
@@ -174,7 +178,10 @@ func run() error {
}
}
cancel()
closeCtx, closed := context.WithCancel(context.Background())
go closeMonitor(closeCtx)
instance.Close()
closed()
if osSignal != syscall.SIGHUP {
return nil
}
@@ -182,3 +189,13 @@ func run() error {
}
}
}
func closeMonitor(ctx context.Context) {
time.Sleep(3 * time.Second)
select {
case <-ctx.Done():
return
default:
}
log.Fatal("sing-box did not close!")
}

View File

@@ -1,8 +1,6 @@
package main
import (
"context"
"github.com/sagernet/sing-box"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
@@ -27,7 +25,7 @@ func createPreStartedClient() (*box.Box, error) {
if err != nil {
return nil, err
}
instance, err := box.New(context.Background(), options, nil)
instance, err := box.New(box.Options{Options: options})
if err != nil {
return nil, E.Cause(err, "create service")
}

View File

@@ -1,48 +0,0 @@
package canceler
import (
"context"
"time"
)
type Instance struct {
ctx context.Context
cancelFunc context.CancelFunc
timer *time.Timer
timeout time.Duration
}
func New(ctx context.Context, cancelFunc context.CancelFunc, timeout time.Duration) *Instance {
instance := &Instance{
ctx,
cancelFunc,
time.NewTimer(timeout),
timeout,
}
go instance.wait()
return instance
}
func (i *Instance) Update() bool {
if !i.timer.Stop() {
return false
}
if !i.timer.Reset(i.timeout) {
return false
}
return true
}
func (i *Instance) wait() {
select {
case <-i.timer.C:
case <-i.ctx.Done():
}
i.Close()
}
func (i *Instance) Close() error {
i.timer.Stop()
i.cancelFunc()
return nil
}

View File

@@ -1,49 +0,0 @@
package canceler
import (
"context"
"time"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type PacketConn struct {
N.PacketConn
instance *Instance
}
func NewPacketConn(ctx context.Context, conn N.PacketConn, timeout time.Duration) (context.Context, N.PacketConn) {
ctx, cancel := context.WithCancel(ctx)
instance := New(ctx, cancel, timeout)
return ctx, &PacketConn{conn, instance}
}
func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
destination, err = c.PacketConn.ReadPacket(buffer)
if err == nil {
c.instance.Update()
}
return
}
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
err := c.PacketConn.WritePacket(buffer, destination)
if err == nil {
c.instance.Update()
}
return err
}
func (c *PacketConn) Close() error {
return common.Close(
c.PacketConn,
c.instance,
)
}
func (c *PacketConn) Upstream() any {
return c.PacketConn
}

View File

@@ -12,7 +12,7 @@ type Conn struct {
element *list.Element[io.Closer]
}
func NewConn(conn net.Conn) (*Conn, error) {
func NewConn(conn net.Conn) (net.Conn, error) {
connAccess.Lock()
element := openConnection.PushBack(conn)
connAccess.Unlock()

View File

@@ -12,7 +12,7 @@ type PacketConn struct {
element *list.Element[io.Closer]
}
func NewPacketConn(conn net.PacketConn) (*PacketConn, error) {
func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
connAccess.Lock()
element := openConnection.PushBack(conn)
connAccess.Unlock()

View File

@@ -14,10 +14,16 @@ var (
)
func Count() int {
if !Enabled {
return 0
}
return openConnection.Len()
}
func List() []io.Closer {
if !Enabled {
return nil
}
connAccess.RLock()
defer connAccess.RUnlock()
connList := make([]io.Closer, 0, openConnection.Len())
@@ -28,6 +34,9 @@ func List() []io.Closer {
}
func Close() {
if !Enabled {
return
}
connAccess.Lock()
defer connAccess.Unlock()
for element := openConnection.Front(); element != nil; element = element.Next() {

View File

@@ -154,9 +154,9 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
switch N.NetworkName(network) {
case N.NetworkUDP:
if !address.IsIPv6() {
return d.udpDialer4.DialContext(ctx, network, address.String())
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
} else {
return d.udpDialer6.DialContext(ctx, network, address.String())
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
}
}
if !address.IsIPv6() {

View File

@@ -9,6 +9,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
@@ -68,11 +69,11 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
if err != nil {
return nil, err
}
conn, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
conn, destinationAddress, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
if err != nil {
return nil, err
}
return NewResolvePacketConn(ctx, d.router, d.strategy, conn), nil
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
}
func (d *ResolveDialer) Upstream() any {

View File

@@ -1,84 +0,0 @@
package dialer
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func NewResolvePacketConn(ctx context.Context, router adapter.Router, strategy dns.DomainStrategy, conn net.PacketConn) N.NetPacketConn {
if udpConn, ok := conn.(*net.UDPConn); ok {
return &ResolveUDPConn{udpConn, ctx, router, strategy}
} else {
return &ResolvePacketConn{conn, ctx, router, strategy}
}
}
type ResolveUDPConn struct {
*net.UDPConn
ctx context.Context
router adapter.Router
strategy dns.DomainStrategy
}
func (w *ResolveUDPConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
n, addr, err := w.ReadFromUDPAddrPort(buffer.FreeBytes())
if err != nil {
return M.Socksaddr{}, err
}
buffer.Truncate(n)
return M.SocksaddrFromNetIP(addr), nil
}
func (w *ResolveUDPConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
defer buffer.Release()
if destination.IsFqdn() {
addresses, err := w.router.Lookup(w.ctx, destination.Fqdn, w.strategy)
if err != nil {
return err
}
return common.Error(w.UDPConn.WriteToUDPAddrPort(buffer.Bytes(), M.SocksaddrFrom(addresses[0], destination.Port).AddrPort()))
}
return common.Error(w.UDPConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort()))
}
func (w *ResolveUDPConn) Upstream() any {
return w.UDPConn
}
type ResolvePacketConn struct {
net.PacketConn
ctx context.Context
router adapter.Router
strategy dns.DomainStrategy
}
func (w *ResolvePacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
_, addr, err := buffer.ReadPacketFrom(w)
if err != nil {
return M.Socksaddr{}, err
}
return M.SocksaddrFromNet(addr), err
}
func (w *ResolvePacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
defer buffer.Release()
if destination.IsFqdn() {
addresses, err := w.router.Lookup(w.ctx, destination.Fqdn, w.strategy)
if err != nil {
return err
}
return common.Error(w.WriteTo(buffer.Bytes(), M.SocksaddrFrom(addresses[0], destination.Port).UDPAddr()))
}
return common.Error(w.WriteTo(buffer.Bytes(), destination.UDPAddr()))
}
func (w *ResolvePacketConn) Upstream() any {
return w.PacketConn
}

View File

@@ -27,12 +27,7 @@ type slowOpenConn struct {
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
switch N.NetworkName(network) {
case N.NetworkTCP, N.NetworkUDP:
return dialer.Dialer.DialContext(ctx, network, destination.String())
default:
return dialer.Dialer.DialContext(ctx, network, destination.AddrString())
}
return dialer.DialContext(ctx, network, destination.String(), nil)
}
return &slowOpenConn{
dialer: dialer,
@@ -124,6 +119,10 @@ func (c *slowOpenConn) LazyHeadroom() bool {
return c.conn == nil
}
func (c *slowOpenConn) NeedHandshake() bool {
return c.conn == nil
}
func (c *slowOpenConn) ReadFrom(r io.Reader) (n int64, err error) {
if c.conn != nil {
return bufio.Copy(c.conn, r)

View File

@@ -413,7 +413,11 @@ func (c *ClientPacketAddrConn) ReadFrom(p []byte) (n int, addr net.Addr, err err
if err != nil {
return
}
addr = destination.UDPAddr()
if destination.IsFqdn() {
addr = destination
} else {
addr = destination.UDPAddr()
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {

View File

@@ -3,10 +3,12 @@ package process
import (
"context"
"net/netip"
"os/user"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-tun"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
type Searcher interface {
@@ -28,5 +30,15 @@ type Info struct {
}
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
return findProcessInfo(searcher, ctx, network, source, destination)
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
if err != nil {
return nil, err
}
if info.UserId != -1 {
osUser, _ := user.LookupId(F.ToString(info.UserId))
if osUser != nil {
info.User = osUser.Username
}
}
return info, nil
}

View File

@@ -1,25 +0,0 @@
//go:build linux && !android
package process
import (
"context"
"net/netip"
"os/user"
F "github.com/sagernet/sing/common/format"
)
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
if err != nil {
return nil, err
}
if info.UserId != -1 {
osUser, _ := user.LookupId(F.ToString(info.UserId))
if osUser != nil {
info.User = osUser.Username
}
}
return info, nil
}

View File

@@ -1,12 +0,0 @@
//go:build !linux || android
package process
import (
"context"
"net/netip"
)
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
return searcher.FindProcessInfo(ctx, network, source, destination)
}

View File

@@ -24,12 +24,12 @@ func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout
}
err := conn.SetReadDeadline(time.Now().Add(timeout))
if err != nil {
return nil, err
return nil, E.Cause(err, "set read deadline")
}
_, err = buffer.ReadOnceFrom(conn)
err = E.Errors(err, conn.SetReadDeadline(time.Time{}))
if err != nil {
return nil, err
return nil, E.Cause(err, "read payload")
}
var metadata *adapter.InboundContext
var errors []error

View File

@@ -124,8 +124,9 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
hello.SessionId[0] = 1
hello.SessionId[1] = 7
hello.SessionId[2] = 5
hello.SessionId[1] = 8
hello.SessionId[2] = 0
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))
copy(hello.SessionId[8:], e.shortID[:])
if debug.Enabled {

View File

@@ -180,7 +180,7 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
tlsConfig.ServerName = options.ServerName
}
if len(options.ALPN) > 0 {
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
tlsConfig.NextProtos = append(options.ALPN, tlsConfig.NextProtos...)
}
if options.MinVersion != "" {
minVersion, err := ParseTLSVersion(options.MinVersion)

View File

@@ -1,3 +1,57 @@
#### 1.2.4
* Fixes and improvements
#### 1.2.3
* Introducing our [new Android client application](/installation/clients/sfa)
* Improve UDP domain destination NAT
* Update reality protocol
* Fix TTL calculation for DNS response
* Fix v2ray HTTP transport compatibility
* Fix bugs and update dependencies
#### 1.2.2
* Accept `any` outbound in dns rule **1**
* Fix bugs and update dependencies
*1*:
Now you can use the `any` outbound rule to match server address queries instead of filling in all server domains
to `domain` rule.
#### 1.2.1
* Fix missing default host in v2ray http transport`s request
* Flush DNS cache for macOS when tun start/close
* Fix tun's DNS hijacking compatibility with systemd-resolved
#### 1.2.0
* Fix bugs and update dependencies
Important changes since 1.1:
* Introducing our [new iOS client application](/installation/clients/sfi)
* Introducing [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
* Add [reality TLS](/configuration/shared/tls) support
* Add [NTP service](/configuration/ntp)
* Add [DHCP DNS server](/configuration/dns/server) support
* Add SSH [host key validation](/configuration/outbound/ssh) support
* Add [query_type](/configuration/dns/rule) DNS rule item
* Add fallback support for v2ray transport
* Add custom TLS server support for http based v2ray transports
* Add health check support for http-based v2ray transports
* Add multiple configuration support
#### 1.2-rc1
* Fix bugs and update dependencies
#### 1.2-beta10
* Add multiple configuration support **1**
@@ -5,9 +59,11 @@
*1*:
Now you can pass the parameter `--config` or `-c` multiple times, or use the new parameter `--config-directory` or `-C` to load all configuration files in a directory.
Now you can pass the parameter `--config` or `-c` multiple times, or use the new parameter `--config-directory` or `-C`
to load all configuration files in a directory.
Loaded configuration files are sorted by name. If you want to control the merge order, add a numeric prefix to the file name.
Loaded configuration files are sorted by name. If you want to control the merge order, add a numeric prefix to the file
name.
#### 1.1.7

View File

@@ -232,6 +232,8 @@ Invert match result.
Match outbound.
`any` can be used as a value to match any outbound.
#### server
==Required==
@@ -254,18 +256,4 @@ Disable cache and save cache in this query.
#### rules
Included default rules.
#### invert
Invert match result.
#### server
==Required==
Tag of the target dns server.
#### disable_cache
Disable cache and save cache in this query.
Included default rules.

View File

@@ -231,6 +231,8 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
匹配出站。
`any` 可作为值用于匹配任意出站。
#### server
==必填==
@@ -253,18 +255,4 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### rules
包括的默认规则。
#### invert
反选匹配结果。
#### server
==必填==
目标 DNS 服务器的标签。
#### disable_cache
在此查询中禁用缓存。
包括的默认规则。

View File

@@ -40,4 +40,8 @@ No authentication required if empty.
Only supported on Linux, Android, Windows, and macOS.
!!! warning ""
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
Automatically set system proxy configuration when start and clean up when stop.

View File

@@ -40,4 +40,8 @@ HTTP 用户
仅支持 Linux、Android、Windows 和 macOS。
!!! warning ""
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
启动时自动设置系统代理,停止时自动清理。

View File

@@ -37,4 +37,8 @@ No authentication required if empty.
Only supported on Linux, Android, Windows, and macOS.
Automatically set system proxy configuration when start and clean up when stop.
!!! warning ""
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
Automatically set system proxy configuration when start and clean up when stop.

View File

@@ -37,4 +37,8 @@ SOCKS 和 HTTP 用户
仅支持 Linux、Android、Windows 和 macOS。
!!! warning ""
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
启动时自动设置系统代理,停止时自动清理。

View File

@@ -107,8 +107,7 @@ Enforce strict routing rules when `auto_route` is enabled:
* Let unsupported network unreachable
* Route all connections to tun
It prevents address leaks and makes DNS hijacking work on Android and Linux with systemd-resolved, but your device will
not be accessible by others.
It prevents address leaks and makes DNS hijacking work on Android, but your device will not be accessible by others.
*In Windows*:

View File

@@ -107,7 +107,7 @@ tun 接口的 IPv6 前缀。
* 让不支持的网络无法到达
* 将所有连接路由到 tun
它可以防止地址泄漏,并使 DNS 劫持在 Android 和使用 systemd-resolved 的 Linux 上工作,但你的设备将无法其他设备被访问。
它可以防止地址泄漏,并使 DNS 劫持在 Android 上工作,但你的设备将无法其他设备被访问。
*在 Windows 中*:

View File

@@ -7,9 +7,9 @@
#### Install
```shell
git clone https://github.com/SagerNet/sing-box
git clone -b main https://github.com/SagerNet/sing-box
cd sing-box
./release/local/install_go.sh # skip if you have go1.19 already installed
./release/local/install_go.sh # skip if you have golang already installed
./release/local/install.sh
```

View File

@@ -7,9 +7,9 @@
#### 安装
```shell
git clone https://github.com/SagerNet/sing-box
git clone -b main https://github.com/SagerNet/sing-box
cd sing-box
./release/local/install_go.sh # 如果已安装 go1.19 则跳过
./release/local/install_go.sh # 如果已安装 golang 则跳过
./release/local/install.sh
```

View File

@@ -23,7 +23,10 @@
"disable_cache": true
},
{
"domain": "mydomain.com",
"outbound": "any",
"server": "local"
},
{
"geosite": "cn",
"server": "local"
}

View File

@@ -9,10 +9,6 @@ the public internet.
`auto-route` cannot automatically hijack DNS requests when Android's `Private DNS` enabled or `strict_route` disabled.
##### on Linux
`auto-route` cannot automatically hijack DNS requests with `systemd-resolved` enabled and `strict_route` disabled.
#### System proxy
##### on Linux

View File

@@ -8,10 +8,6 @@
`auto-route` 无法自动劫持 DNS 请求如果 `私人 DNS` 开启或 `strict_route` 禁用。
##### Linux
`auto-route` 无法自动劫持 DNS 请求如果 `systemd-resolved` 开启且 `strict_route` 禁用。
#### 系统代理
##### Linux

View File

@@ -25,4 +25,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
In addition, no derivative work may use the name or imply association
with this application without prior consent.
```

View File

@@ -25,4 +25,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
In addition, no derivative work may use the name or imply association
with this application without prior consent.
```

View File

@@ -0,0 +1,16 @@
# SFA
Experimental Android client for sing-box.
#### Requirements
* Android 5.0+
#### Download
* [AppCenter](https://install.appcenter.ms/users/nekohasekai/apps/sfa/distribution_groups/publictest)
#### Note
* Working directory is at `/sdcard/Android/data/io.nekohasekai.sfa/files` (External files directory)
* User Agent is `SFA/$version ($version_code; sing-box $sing_box_version)` in the remote profile request

View File

@@ -0,0 +1,16 @@
# SFA
实验性的 Android sing-box 客户端。
#### 要求
* Android 5.0+
#### 下载
* [AppCenter](https://install.appcenter.ms/users/nekohasekai/apps/sfa/distribution_groups/publictest)
#### 注意事项
* 工作目录位于 `/sdcard/Android/data/io.nekohasekai.sfa/files` (外部文件目录)
* 远程配置文件请求中的 User Agent 为 `SFA/$version ($version_code; sing-box $sing_box_version)`

View File

@@ -1,6 +1,6 @@
# SFI
Experimental official iOS client for sing-box.
Experimental iOS client for sing-box.
#### Requirements
@@ -11,9 +11,10 @@ Experimental official iOS client for sing-box.
* [TestFlight](https://testflight.apple.com/join/c6ylui2j)
#### Limit
#### Note
* `system` tun stack not working
* `system` tun stack not working on iOS
* User Agent is `SFI/$version ($version_code; sing-box $sing_box_version)` in the remote profile request
#### Privacy policy

View File

@@ -1,6 +1,6 @@
# SFI
实验性的官方 iOS sing-box 客户端。
实验性的 iOS sing-box 客户端。
#### 要求
@@ -11,9 +11,10 @@
* [TestFlight](https://testflight.apple.com/join/c6ylui2j)
#### 限制
#### 注意事项
* `system` tun stack 不工作
* `system` tun stack 在 iOS 不工作
* 远程配置文件请求中的 User Agent 为 `SFI/$version ($version_code; sing-box $sing_box_version)`
#### 隐私政策

View File

@@ -4,32 +4,26 @@ import (
"time"
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
"go.uber.org/atomic"
"github.com/sagernet/sing/common/atomic"
)
type Manager struct {
connections compatible.Map[string, tracker]
uploadTemp *atomic.Int64
downloadTemp *atomic.Int64
uploadBlip *atomic.Int64
downloadBlip *atomic.Int64
uploadTotal *atomic.Int64
downloadTotal *atomic.Int64
ticker *time.Ticker
done chan struct{}
uploadTemp atomic.Int64
downloadTemp atomic.Int64
uploadBlip atomic.Int64
downloadBlip atomic.Int64
uploadTotal atomic.Int64
downloadTotal atomic.Int64
connections compatible.Map[string, tracker]
ticker *time.Ticker
done chan struct{}
}
func NewManager() *Manager {
manager := &Manager{
uploadTemp: atomic.NewInt64(0),
downloadTemp: atomic.NewInt64(0),
uploadBlip: atomic.NewInt64(0),
downloadBlip: atomic.NewInt64(0),
uploadTotal: atomic.NewInt64(0),
downloadTotal: atomic.NewInt64(0),
ticker: time.NewTicker(time.Second),
done: make(chan struct{}),
ticker: time.NewTicker(time.Second),
done: make(chan struct{}),
}
go manager.handle()
return manager

View File

@@ -1,6 +1,7 @@
package trafficontrol
import (
"encoding/json"
"net"
"net/netip"
"time"
@@ -8,10 +9,10 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/experimental/trackerconn"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
N "github.com/sagernet/sing/common/network"
"github.com/gofrs/uuid"
"go.uber.org/atomic"
"github.com/gofrs/uuid/v5"
)
type Metadata struct {
@@ -43,6 +44,19 @@ type trackerInfo struct {
RulePayload string `json:"rulePayload"`
}
func (t trackerInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"id": t.UUID.String(),
"metadata": t.Metadata,
"upload": t.UploadTotal.Load(),
"download": t.DownloadTotal.Load(),
"start": t.Start,
"chains": t.Chain,
"rule": t.Rule,
"rulePayload": t.RulePayload,
})
}
type tcpTracker struct {
N.ExtendedConn `json:"-"`
*trackerInfo
@@ -97,8 +111,8 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad
next = group.Now()
}
upload := atomic.NewInt64(0)
download := atomic.NewInt64(0)
upload := new(atomic.Int64)
download := new(atomic.Int64)
t := &tcpTracker{
ExtendedConn: trackerconn.NewHook(conn, func(n int64) {
@@ -184,8 +198,8 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route
next = group.Now()
}
upload := atomic.NewInt64(0)
download := atomic.NewInt64(0)
upload := new(atomic.Int64)
download := new(atomic.Int64)
ut := &udpTracker{
PacketConn: trackerconn.NewHookPacket(conn, func(n int64) {

View File

@@ -1,5 +1,3 @@
//go:build darwin
package libbox
const (

View File

@@ -1,5 +1,3 @@
//go:build darwin
package libbox
import (
@@ -46,6 +44,7 @@ func clientConnect(sharedDirectory string) (net.Conn, error) {
}
func (c *CommandClient) Connect() error {
common.Close(c.conn)
conn, err := clientConnect(c.sharedDirectory)
if err != nil {
return err

View File

@@ -1,5 +1,3 @@
//go:build darwin
package libbox
import (

View File

@@ -1,5 +1,3 @@
//go:build darwin
package libbox
import (

View File

@@ -1,5 +1,3 @@
//go:build darwin
package libbox
import (

View File

@@ -1,5 +1,3 @@
//go:build darwin
package libbox
import (
@@ -10,6 +8,8 @@ import (
"sync"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/observable"
"github.com/sagernet/sing/common/x/list"
@@ -57,7 +57,10 @@ func (s *CommandServer) Start() error {
}
func (s *CommandServer) Close() error {
return s.listener.Close()
return common.Close(
s.listener,
s.observer,
)
}
func (s *CommandServer) loopConnection(listener net.Listener) {
@@ -69,7 +72,9 @@ func (s *CommandServer) loopConnection(listener net.Listener) {
go func() {
hErr := s.handleConnection(conn)
if hErr != nil && !E.IsClosed(err) {
log.Warn("log-server: process connection: ", hErr)
if debug.Enabled {
log.Warn("log-server: process connection: ", hErr)
}
}
}()
}

View File

@@ -1,5 +1,3 @@
//go:build darwin
package libbox
import (
@@ -44,7 +42,7 @@ func (s *CommandServer) handleStatusConn(conn net.Conn) error {
}
select {
case <-ctx.Done():
return nil
return ctx.Err()
case <-ticker.C:
}
}

View File

@@ -1,5 +1,3 @@
//go:build darwin
package libbox
import (

View File

@@ -1,8 +1,11 @@
//go:build linux || darwin
package libbox
import (
"bytes"
"context"
"encoding/json"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
@@ -15,3 +18,36 @@ func parseConfig(configContent string) (option.Options, error) {
}
return options, nil
}
func CheckConfig(configContent string) error {
options, err := parseConfig(configContent)
if err != nil {
return err
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
instance, err := box.New(box.Options{
Context: ctx,
Options: options,
})
if err == nil {
instance.Close()
}
return err
}
func FormatConfig(configContent string) (string, error) {
options, err := parseConfig(configContent)
if err != nil {
return "", err
}
var buffer bytes.Buffer
json.NewEncoder(&buffer)
encoder := json.NewEncoder(&buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(options)
if err != nil {
return "", err
}
return buffer.String(), nil
}

241
experimental/libbox/http.go Normal file
View File

@@ -0,0 +1,241 @@
package libbox
import (
"bytes"
"context"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"errors"
"fmt"
"io"
"math/rand"
"net"
"net/http"
"net/url"
"os"
"strconv"
"sync"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/protocol/socks"
"github.com/sagernet/sing/protocol/socks/socks5"
)
type HTTPClient interface {
RestrictedTLS()
ModernTLS()
PinnedTLS12()
PinnedSHA256(sumHex string)
TrySocks5(port int32)
KeepAlive()
NewRequest() HTTPRequest
Close()
}
type HTTPRequest interface {
SetURL(link string) error
SetMethod(method string)
SetHeader(key string, value string)
SetContent(content []byte)
SetContentString(content string)
RandomUserAgent()
SetUserAgent(userAgent string)
Execute() (HTTPResponse, error)
}
type HTTPResponse interface {
GetContent() ([]byte, error)
GetContentString() (string, error)
WriteTo(path string) error
}
var (
_ HTTPClient = (*httpClient)(nil)
_ HTTPRequest = (*httpRequest)(nil)
_ HTTPResponse = (*httpResponse)(nil)
)
type httpClient struct {
tls tls.Config
client http.Client
transport http.Transport
}
func NewHTTPClient() HTTPClient {
client := new(httpClient)
client.client.Timeout = C.TCPTimeout
client.client.Transport = &client.transport
client.transport.TLSClientConfig = &client.tls
client.transport.DisableKeepAlives = true
return client
}
func (c *httpClient) ModernTLS() {
c.tls.MinVersion = tls.VersionTLS12
c.tls.CipherSuites = common.Map(tls.CipherSuites(), func(it *tls.CipherSuite) uint16 { return it.ID })
}
func (c *httpClient) RestrictedTLS() {
c.tls.MinVersion = tls.VersionTLS13
c.tls.CipherSuites = common.Map(common.Filter(tls.CipherSuites(), func(it *tls.CipherSuite) bool {
return common.Contains(it.SupportedVersions, uint16(tls.VersionTLS13))
}), func(it *tls.CipherSuite) uint16 {
return it.ID
})
}
func (c *httpClient) PinnedTLS12() {
c.tls.MinVersion = tls.VersionTLS12
c.tls.MaxVersion = tls.VersionTLS12
}
func (c *httpClient) PinnedSHA256(sumHex string) {
c.tls.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
for _, rawCert := range rawCerts {
certSum := sha256.Sum256(rawCert)
if sumHex == hex.EncodeToString(certSum[:]) {
return nil
}
}
return E.New("pinned sha256 sum mismatch")
}
}
func (c *httpClient) TrySocks5(port int32) {
dialer := new(net.Dialer)
c.transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
for {
socksConn, err := dialer.DialContext(ctx, "tcp", "127.0.0.1:"+strconv.Itoa(int(port)))
if err != nil {
break
}
_, err = socks.ClientHandshake5(socksConn, socks5.CommandConnect, M.ParseSocksaddr(addr), "", "")
if err != nil {
break
}
//nolint:staticcheck
return socksConn, err
}
return dialer.DialContext(ctx, network, addr)
}
}
func (c *httpClient) KeepAlive() {
c.transport.ForceAttemptHTTP2 = true
c.transport.DisableKeepAlives = false
}
func (c *httpClient) NewRequest() HTTPRequest {
req := &httpRequest{httpClient: c}
req.request = http.Request{
Method: "GET",
Header: http.Header{},
}
return req
}
func (c *httpClient) Close() {
c.transport.CloseIdleConnections()
}
type httpRequest struct {
*httpClient
request http.Request
}
func (r *httpRequest) SetURL(link string) (err error) {
r.request.URL, err = url.Parse(link)
if r.request.URL.User != nil {
user := r.request.URL.User.Username()
password, _ := r.request.URL.User.Password()
r.request.SetBasicAuth(user, password)
}
return
}
func (r *httpRequest) SetMethod(method string) {
r.request.Method = method
}
func (r *httpRequest) SetHeader(key string, value string) {
r.request.Header.Set(key, value)
}
func (r *httpRequest) RandomUserAgent() {
r.request.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2))
}
func (r *httpRequest) SetUserAgent(userAgent string) {
r.request.Header.Set("User-Agent", userAgent)
}
func (r *httpRequest) SetContent(content []byte) {
buffer := bytes.Buffer{}
buffer.Write(content)
r.request.Body = io.NopCloser(bytes.NewReader(buffer.Bytes()))
r.request.ContentLength = int64(len(content))
}
func (r *httpRequest) SetContentString(content string) {
r.SetContent([]byte(content))
}
func (r *httpRequest) Execute() (HTTPResponse, error) {
response, err := r.client.Do(&r.request)
if err != nil {
return nil, err
}
httpResp := &httpResponse{Response: response}
if response.StatusCode != http.StatusOK {
return nil, errors.New(httpResp.errorString())
}
return httpResp, nil
}
type httpResponse struct {
*http.Response
getContentOnce sync.Once
content []byte
contentError error
}
func (h *httpResponse) errorString() string {
content, err := h.GetContentString()
if err != nil {
return fmt.Sprint("HTTP ", h.Status)
}
return fmt.Sprint("HTTP ", h.Status, ": ", content)
}
func (h *httpResponse) GetContent() ([]byte, error) {
h.getContentOnce.Do(func() {
defer h.Body.Close()
h.content, h.contentError = io.ReadAll(h.Body)
})
return h.content, h.contentError
}
func (h *httpResponse) GetContentString() (string, error) {
content, err := h.GetContent()
if err != nil {
return "", err
}
return string(content), nil
}
func (h *httpResponse) WriteTo(path string) error {
defer h.Body.Close()
file, err := os.Create(path)
if err != nil {
return err
}
defer file.Close()
return common.Error(bufio.Copy(file, h.Body))
}

View File

@@ -1,5 +1,3 @@
//go:build linux || darwin
package libbox
import "github.com/sagernet/sing/common"

View File

@@ -0,0 +1,29 @@
//go:build darwin || linux
package libbox
import (
"os"
"golang.org/x/sys/unix"
)
var stderrFile *os.File
func RedirectStderr(path string) error {
if stats, err := os.Stat(path); err == nil && stats.Size() > 0 {
_ = os.Rename(path, path+".old")
}
outputFile, err := os.Create(path)
if err != nil {
return err
}
err = unix.Dup2(int(outputFile.Fd()), int(os.Stderr.Fd()))
if err != nil {
outputFile.Close()
os.Remove(outputFile.Name())
return err
}
stderrFile = outputFile
return nil
}

View File

@@ -1,5 +1,3 @@
//go:build linux || darwin
package libbox
import "github.com/sagernet/sing-box/option"

View File

@@ -1,5 +1,3 @@
//go:build linux || darwin
package libbox
import (

View File

@@ -1,5 +1,3 @@
//go:build linux || darwin
package libbox
import (
@@ -30,7 +28,11 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
return nil, err
}
ctx, cancel := context.WithCancel(context.Background())
instance, err := box.New(ctx, options, &platformInterfaceWrapper{platformInterface, platformInterface.UseProcFS()})
instance, err := box.New(box.Options{
Context: ctx,
Options: options,
PlatformInterface: &platformInterfaceWrapper{platformInterface, platformInterface.UseProcFS()},
})
if err != nil {
cancel()
return nil, E.Cause(err, "create service")
@@ -77,7 +79,7 @@ func (w *platformInterfaceWrapper) OpenTun(options tun.Options, platformOptions
if err != nil {
return nil, err
}
dupFd, err := syscall.Dup(int(tunFd))
dupFd, err := dup(int(tunFd))
if err != nil {
return nil, E.Cause(err, "dup tun file descriptor")
}

View File

@@ -0,0 +1,9 @@
//go:build !windows
package libbox
import "syscall"
func dup(fd int) (nfd int, err error) {
return syscall.Dup(fd)
}

View File

@@ -0,0 +1,7 @@
package libbox
import "os"
func dup(fd int) (nfd int, err error) {
return 0, os.ErrInvalid
}

View File

@@ -1,5 +1,3 @@
//go:build linux || darwin
package libbox
import (

View File

@@ -1,5 +1,3 @@
//go:build linux || darwin
package libbox
import (

View File

@@ -3,11 +3,10 @@ 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"
"go.uber.org/atomic"
)
func New(conn net.Conn, readCounter []*atomic.Int64, writeCounter []*atomic.Int64) *Conn {

View File

@@ -1,11 +1,10 @@
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"
"go.uber.org/atomic"
)
func NewPacket(conn N.PacketConn, readCounter []*atomic.Int64, writeCounter []*atomic.Int64) *PacketConn {

View File

@@ -12,10 +12,9 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/experimental/trackerconn"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/atomic"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"go.uber.org/atomic"
)
func init() {
@@ -211,7 +210,7 @@ func (s *StatsService) loadOrCreateCounter(name string) *atomic.Int64 {
if loaded {
return counter
}
counter = atomic.NewInt64(0)
counter = &atomic.Int64{}
s.counters[name] = counter
return counter
}

35
go.mod
View File

@@ -4,7 +4,7 @@ go 1.18
require (
berty.tech/go-libtor v1.0.385
github.com/Dreamacro/clash v1.14.0
github.com/Dreamacro/clash v1.15.0
github.com/caddyserver/certmagic v0.17.2
github.com/cretz/bine v0.2.0
github.com/dustin/go-humanize v1.0.1
@@ -13,41 +13,41 @@ require (
github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.2
github.com/gofrs/uuid v4.4.0+incompatible
github.com/gofrs/uuid/v5 v5.0.0
github.com/hashicorp/yamux v0.1.1
github.com/insomniacslk/dhcp v0.0.0-20230307103557-e252950ab961
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/mholt/acmez v1.1.0
github.com/miekg/dns v1.1.52
github.com/miekg/dns v1.1.53
github.com/ooni/go-libtor v1.1.7
github.com/oschwald/maxminddb-golang v1.10.0
github.com/pires/go-proxyproto v0.7.0
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca
github.com/sagernet/gomobile v0.0.0-20230413023804-244d7ff07035
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8
github.com/sagernet/sing v0.2.1-0.20230321172705-3e60222a1a7d
github.com/sagernet/sing-dns v0.1.4
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.2.3
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc
github.com/sagernet/sing-shadowsocks v0.2.0
github.com/sagernet/sing-shadowtls v0.1.0
github.com/sagernet/sing-tun v0.1.3-0.20230321172818-56bedd2f0558
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab
github.com/sagernet/sing-vmess v0.1.3
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
github.com/spf13/cobra v1.6.1
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.2
go.etcd.io/bbolt v1.3.7
go.uber.org/atomic v1.10.0
go.uber.org/zap v1.24.0
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
golang.org/x/crypto v0.7.0
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0
golang.org/x/net v0.8.0
golang.org/x/sys v0.6.0
golang.org/x/crypto v0.8.0
golang.org/x/exp v0.0.0-20230321023759-10a507213a29
golang.org/x/net v0.9.0
golang.org/x/sys v0.7.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
google.golang.org/grpc v1.53.0
google.golang.org/grpc v1.54.0
google.golang.org/protobuf v1.30.0
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c
)
@@ -64,7 +64,7 @@ require (
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/inconshreveable/mousetrap v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/klauspost/cpuid/v2 v2.1.1 // indirect
@@ -82,9 +82,10 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect

68
go.sum
View File

@@ -1,7 +1,7 @@
berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw=
berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw=
github.com/Dreamacro/clash v1.14.0 h1:ehJ/C/1m9LEjmME72WSE/Y2YqbR3Q54AbjqiRCvtyW4=
github.com/Dreamacro/clash v1.14.0/go.mod h1:ia2CU7V713H1QdCqMwOLK9U9V5Ay8X0voj3yQr2tk+I=
github.com/Dreamacro/clash v1.15.0 h1:mlpD950VEggXZBNahV66hyKDRxcczkj3vymoAt78KyE=
github.com/Dreamacro/clash v1.15.0/go.mod h1:WNH69bN11LiAdgdSr4hpkEuXVMfBbWyhEKMCTx9BtNE=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
@@ -35,6 +35,8 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8Wd
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
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/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=
@@ -49,10 +51,10 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/insomniacslk/dhcp v0.0.0-20230307103557-e252950ab961 h1:x/YtdDlmypenG1te/FfH6LVM+3krhXk5CFV8VYNNX5M=
github.com/insomniacslk/dhcp v0.0.0-20230307103557-e252950ab961/go.mod h1:IKrnDWs3/Mqq5n0lI+RxA2sB7MvN/vbMBP3ehXg65UI=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16 h1:+aAGyK41KRn8jbF2Q7PLL0Sxwg6dShGcQSeCC7nZQ8E=
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.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
@@ -70,8 +72,8 @@ github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczG
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/mholt/acmez v1.1.0 h1:IQ9CGHKOHokorxnffsqDvmmE30mDenO1lptYZ1AYkHY=
github.com/mholt/acmez v1.1.0/go.mod h1:zwo5+fbLLTowAX8o8ETfQzbDtwGEXnPhkmGdKIP+bgs=
github.com/miekg/dns v1.1.52 h1:Bmlc/qsNNULOe6bpXcUTsuOajd0DzRHwup6D9k1An0c=
github.com/miekg/dns v1.1.52/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/miekg/dns v1.1.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
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/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
@@ -101,26 +103,26 @@ github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca h1:w56+kf8BeqLqllrRJ1tdwKc3sCdWOn/DuNHpY9fAiqs=
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU=
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/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/quic-go v0.0.0-20230202071646-a8c8afb18b32 h1:tztuJB+giOWNRKQEBVY2oI3PsheTooMdh+/yxemYQYY=
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32/go.mod h1:QMCkxXAC3CvBgDZVIJp43NWTuwGBScCzMLVLynjERL8=
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8 h1:4M3+0/kqvJuTsimEORH0/vUYTpMDUETBH2zNUU38SUw=
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.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.2.1-0.20230321172705-3e60222a1a7d h1:ktk03rtgPqTDyUd2dWg1uzyr5RnptX8grSMvIzedJlQ=
github.com/sagernet/sing v0.2.1-0.20230321172705-3e60222a1a7d/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw=
github.com/sagernet/sing-dns v0.1.4 h1:7VxgeoSCiiazDSaXXQVcvrTBxFpOePPq/4XdgnUDN+0=
github.com/sagernet/sing-dns v0.1.4/go.mod h1:1+6pCa48B1AI78lD+/i/dLgpw4MwfnsSpZo0Ds8wzzk=
github.com/sagernet/sing v0.2.3 h1:V50MvZ4c3Iij2lYFWPlzL1PyipwSzjGeN9x+Ox89vpk=
github.com/sagernet/sing v0.2.3/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc h1:hmbuqKv48SAjiKPoqtJGvS5pEHVPZjTHq9CPwQY2cZ4=
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc/go.mod h1:ZKuuqgsHRxDahYrzgSgy4vIAGGuKPlIf4hLcNzYzLkY=
github.com/sagernet/sing-shadowsocks v0.2.0 h1:ILDWL7pwWfkPLEbviE/MyCgfjaBmJY/JVVY+5jhSb58=
github.com/sagernet/sing-shadowsocks v0.2.0/go.mod h1:ysYzszRLpNzJSorvlWRMuzU6Vchsp7sd52q+JNY4axw=
github.com/sagernet/sing-shadowtls v0.1.0 h1:05MYce8aR5xfKIn+y7xRFsdKhKt44QZTSEQW+lG5IWQ=
github.com/sagernet/sing-shadowtls v0.1.0/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
github.com/sagernet/sing-tun v0.1.3-0.20230321172818-56bedd2f0558 h1:c5Rm6BTOclEeayS6G9+1rI1kTeilCsn0ALSFbOdlgRE=
github.com/sagernet/sing-tun v0.1.3-0.20230321172818-56bedd2f0558/go.mod h1:cqnZEm+2ArgP4Gq1NcQfVFm9CZaeGw21mG9AcnYOTiU=
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab h1:a9oeWuPBuIZ70qMhIIH6RrYhp886xN9jJIwsuu4ZFUo=
github.com/sagernet/sing-tun v0.1.4-0.20230326080954-8848c0e4cbab/go.mod h1:4YxIDEkkCjGXDOTMPw1SXpLmCQUFAWuaQN250oo+928=
github.com/sagernet/sing-vmess v0.1.3 h1:q/+tsF46dvvapL6CpQBgPHJ6nQrDUZqEtLHCbsjO7iM=
github.com/sagernet/sing-vmess v0.1.3/go.mod h1:GVXqAHwe9U21uS+Voh4YBIrADQyE4F9v0ayGSixSQAE=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
@@ -133,8 +135,8 @@ 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/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
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/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -168,10 +170,10 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
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.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
@@ -180,8 +182,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
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.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
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=
@@ -199,15 +201,15 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
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.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -223,8 +225,8 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde h1:ybF7AMzI
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
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/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=

View File

@@ -10,11 +10,10 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"go.uber.org/atomic"
)
var _ adapter.Inbound = (*myInboundAdapter)(nil)

View File

@@ -5,10 +5,8 @@ import (
"net"
"strconv"
"strings"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/canceler"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log"
@@ -21,10 +19,7 @@ import (
"github.com/sagernet/sing/common/ranges"
)
var (
_ adapter.Inbound = (*Tun)(nil)
_ tun.Router = (*Tun)(nil)
)
var _ adapter.Inbound = (*Tun)(nil)
type Tun struct {
tag string
@@ -43,6 +38,10 @@ type Tun struct {
}
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
tunName := options.InterfaceName
if tunName == "" {
tunName = tun.CalculateInterfaceName("")
}
tunMTU := options.MTU
if tunMTU == 0 {
tunMTU = 9000
@@ -76,7 +75,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
logger: logger,
inboundOptions: options.InboundOptions,
tunOptions: tun.Options{
Name: options.InterfaceName,
Name: tunName,
MTU: tunMTU,
Inet4Address: common.Map(options.Inet4Address, option.ListenPrefix.Build),
Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build),
@@ -142,17 +141,12 @@ func (t *Tun) Tag() string {
func (t *Tun) Start() error {
if C.IsAndroid && t.platformInterface == nil {
t.logger.Trace("building android rules")
t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
}
if t.tunOptions.Name == "" {
t.tunOptions.Name = tun.CalculateInterfaceName("")
}
var (
tunInterface tun.Tun
err error
)
t.logger.Trace("opening interface")
if t.platformInterface != nil {
tunInterface, err = t.platformInterface.OpenTun(t.tunOptions, t.platformOptions)
} else {
@@ -161,12 +155,7 @@ func (t *Tun) Start() error {
if err != nil {
return E.Cause(err, "configure tun interface")
}
t.logger.Trace("creating stack")
t.tunIf = tunInterface
var tunRouter tun.Router
if len(t.router.IPRules()) > 0 {
tunRouter = t
}
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
Context: t.ctx,
Tun: tunInterface,
@@ -176,7 +165,6 @@ func (t *Tun) Start() error {
Inet6Address: t.tunOptions.Inet6Address,
EndpointIndependentNat: t.endpointIndependentNat,
UDPTimeout: t.udpTimeout,
Router: tunRouter,
Handler: t,
Logger: t.logger,
UnderPlatform: t.platformInterface != nil,
@@ -184,7 +172,6 @@ func (t *Tun) Start() error {
if err != nil {
return err
}
t.logger.Trace("starting stack")
err = t.tunStack.Start()
if err != nil {
return err
@@ -200,21 +187,6 @@ 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 {
ctx = log.ContextWithNewID(ctx)
var metadata adapter.InboundContext
@@ -234,9 +206,6 @@ func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata
func (t *Tun) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error {
ctx = log.ContextWithNewID(ctx)
if tun.NeedTimeoutFromContext(ctx) {
ctx, conn = canceler.NewPacketConn(ctx, conn, time.Duration(t.udpTimeout)*time.Second)
}
var metadata adapter.InboundContext
metadata.Inbound = t.tag
metadata.InboundType = C.TypeTun

View File

@@ -12,7 +12,7 @@ import (
)
func init() {
dns.RegisterTransport([]string{"dhcp"}, func(ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) {
dns.RegisterTransport([]string{"dhcp"}, func(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) {
return nil, E.New(`DHCP is not included in this build, rebuild with -tags with_dhcp`)
})
}

View File

@@ -19,7 +19,7 @@ import (
const WithQUIC = false
func init() {
dns.RegisterTransport([]string{"quic", "h3"}, func(ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) {
dns.RegisterTransport([]string{"quic", "h3"}, func(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) {
return nil, C.ErrQUICNotIncluded
})
v2ray.RegisterQUICConstructor(

View File

@@ -49,6 +49,10 @@ func (f *simpleFactory) NewLogger(tag string) ContextLogger {
return &simpleLogger{f, tag}
}
func (f *simpleFactory) Close() error {
return nil
}
var _ ContextLogger = (*simpleLogger)(nil)
type simpleLogger struct {

View File

@@ -15,6 +15,7 @@ type Factory interface {
SetLevel(level Level)
Logger() ContextLogger
NewLogger(tag string) ContextLogger
Close() error
}
type ObservableFactory interface {

View File

@@ -36,15 +36,16 @@ func (f Formatter) Format(ctx context.Context, level Level, tag string, message
if tag != "" {
message = tag + ": " + message
}
var id uint32
var id ID
var hasId bool
if ctx != nil {
id, hasId = IDFromContext(ctx)
}
if hasId {
activeDuration := formatDuration(time.Since(id.CreatedAt))
if !f.DisableColors {
var color aurora.Color
color = aurora.Color(uint8(id))
color = aurora.Color(uint8(id.ID))
color %= 215
row := uint(color / 36)
column := uint(color % 36)
@@ -62,9 +63,9 @@ func (f Formatter) Format(ctx context.Context, level Level, tag string, message
color += 16
color = color << 16
color |= 1 << 14
message = F.ToString("[", aurora.Colorize(id, color).String(), "] ", message)
message = F.ToString("[", aurora.Colorize(id.ID, color).String(), " ", activeDuration, "] ", message)
} else {
message = F.ToString("[", id, "] ", message)
message = F.ToString("[", id.ID, " ", activeDuration, "] ", message)
}
}
switch {
@@ -99,15 +100,16 @@ func (f Formatter) FormatWithSimple(ctx context.Context, level Level, tag string
message = tag + ": " + message
}
messageSimple := message
var id uint32
var id ID
var hasId bool
if ctx != nil {
id, hasId = IDFromContext(ctx)
}
if hasId {
activeDuration := formatDuration(time.Since(id.CreatedAt))
if !f.DisableColors {
var color aurora.Color
color = aurora.Color(uint8(id))
color = aurora.Color(uint8(id.ID))
color %= 215
row := uint(color / 36)
column := uint(color % 36)
@@ -125,11 +127,11 @@ func (f Formatter) FormatWithSimple(ctx context.Context, level Level, tag string
color += 16
color = color << 16
color |= 1 << 14
message = F.ToString("[", aurora.Colorize(id, color).String(), "] ", message)
message = F.ToString("[", aurora.Colorize(id.ID, color).String(), " ", activeDuration, "] ", message)
} else {
message = F.ToString("[", id, "] ", message)
message = F.ToString("[", id.ID, " ", activeDuration, "] ", message)
}
messageSimple = F.ToString("[", id, "] ", messageSimple)
messageSimple = F.ToString("[", id.ID, " ", activeDuration, "] ", messageSimple)
}
switch {
@@ -153,3 +155,13 @@ func xd(value int, x int) string {
}
return message
}
func formatDuration(duration time.Duration) string {
if duration < time.Second {
return F.ToString(duration.Milliseconds(), "ms")
} else if duration < time.Minute {
return F.ToString(int64(duration.Seconds()), ".", int64(duration.Seconds()*100)%100, "s")
} else {
return F.ToString(int64(duration.Minutes()), "m", int64(duration.Seconds())%60, "s")
}
}

View File

@@ -3,6 +3,7 @@ package log
import (
"context"
"math/rand"
"time"
"github.com/sagernet/sing/common/random"
)
@@ -13,11 +14,19 @@ func init() {
type idKey struct{}
func ContextWithNewID(ctx context.Context) context.Context {
return context.WithValue(ctx, (*idKey)(nil), rand.Uint32())
type ID struct {
ID uint32
CreatedAt time.Time
}
func IDFromContext(ctx context.Context) (uint32, bool) {
id, loaded := ctx.Value((*idKey)(nil)).(uint32)
func ContextWithNewID(ctx context.Context) context.Context {
return context.WithValue(ctx, (*idKey)(nil), ID{
ID: rand.Uint32(),
CreatedAt: time.Now(),
})
}
func IDFromContext(ctx context.Context) (ID, bool) {
id, loaded := ctx.Value((*idKey)(nil)).(ID)
return id, loaded
}

110
log/log.go Normal file
View File

@@ -0,0 +1,110 @@
package log
import (
"io"
"os"
"time"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type factoryWithFile struct {
Factory
file *os.File
}
func (f *factoryWithFile) Close() error {
return common.Close(
f.Factory,
common.PtrOrNil(f.file),
)
}
type observableFactoryWithFile struct {
ObservableFactory
file *os.File
}
func (f *observableFactoryWithFile) Close() error {
return common.Close(
f.ObservableFactory,
common.PtrOrNil(f.file),
)
}
type Options struct {
Options option.LogOptions
Observable bool
DefaultWriter io.Writer
BaseTime time.Time
PlatformWriter io.Writer
}
func New(options Options) (Factory, error) {
logOptions := options.Options
if logOptions.Disabled {
return NewNOPFactory(), nil
}
var logFile *os.File
var logWriter io.Writer
switch logOptions.Output {
case "":
logWriter = options.DefaultWriter
if logWriter == nil {
logWriter = os.Stderr
}
case "stderr":
logWriter = os.Stderr
case "stdout":
logWriter = os.Stdout
default:
var err error
logFile, err = os.OpenFile(C.BasePath(logOptions.Output), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return nil, err
}
logWriter = logFile
}
logFormatter := Formatter{
BaseTime: options.BaseTime,
DisableColors: logOptions.DisableColor || logFile != nil,
DisableTimestamp: !logOptions.Timestamp && logFile != nil,
FullTimestamp: logOptions.Timestamp,
TimestampFormat: "-0700 2006-01-02 15:04:05",
}
var factory Factory
if options.Observable {
factory = NewObservableFactory(logFormatter, logWriter, options.PlatformWriter)
} else {
factory = NewFactory(logFormatter, logWriter, options.PlatformWriter)
}
if logOptions.Level != "" {
logLevel, err := ParseLevel(logOptions.Level)
if err != nil {
return nil, E.Cause(err, "parse log level")
}
factory.SetLevel(logLevel)
} else {
factory.SetLevel(LevelTrace)
}
if logFile != nil {
if options.Observable {
factory = &observableFactoryWithFile{
ObservableFactory: factory.(ObservableFactory),
file: logFile,
}
} else {
factory = &factoryWithFile{
Factory: factory,
file: logFile,
}
}
}
return factory, nil
}

View File

@@ -72,6 +72,10 @@ func (f *nopFactory) FatalContext(ctx context.Context, args ...any) {
func (f *nopFactory) PanicContext(ctx context.Context, args ...any) {
}
func (f *nopFactory) Close() error {
return nil
}
func (f *nopFactory) Subscribe() (subscription observable.Subscription[Entry], done <-chan struct{}, err error) {
return nil, nil, os.ErrInvalid
}

View File

@@ -38,8 +38,8 @@ nav:
- Installation:
- From source: installation/from-source.md
- Clients:
- SFI:
- installation/clients/sfi/index.md
- iOS: installation/clients/sfi.md
- Android: installation/clients/sfa.md
- Configuration:
- configuration/index.md
- Log:

View File

@@ -2,6 +2,7 @@ package ntp
import (
"context"
"os"
"time"
"github.com/sagernet/sing-box/adapter"
@@ -9,6 +10,7 @@ import (
"github.com/sagernet/sing-box/common/settings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"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"
@@ -20,7 +22,7 @@ var _ adapter.TimeService = (*Service)(nil)
type Service struct {
ctx context.Context
cancel context.CancelFunc
cancel common.ContextCancelCauseFunc
server M.Socksaddr
writeToSystem bool
dialer N.Dialer
@@ -30,7 +32,7 @@ type Service struct {
}
func NewService(ctx context.Context, router adapter.Router, logger logger.Logger, options option.NTPOptions) *Service {
ctx, cancel := context.WithCancel(ctx)
ctx, cancel := common.ContextWithCancelCause(ctx)
server := options.ServerOptions.Build()
if server.Port == 0 {
server.Port = 123
@@ -64,7 +66,7 @@ func (s *Service) Start() error {
func (s *Service) Close() error {
s.ticker.Stop()
s.cancel()
s.cancel(os.ErrClosed)
return nil
}

View File

@@ -1,5 +1,14 @@
package option
import (
"reflect"
"github.com/sagernet/sing-box/common/json"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type DNSOptions struct {
Servers []DNSServerOptions `json:"servers,omitempty"`
Rules []DNSRule `json:"rules,omitempty"`
@@ -22,3 +31,97 @@ type DNSServerOptions struct {
Strategy DomainStrategy `json:"strategy,omitempty"`
Detour string `json:"detour,omitempty"`
}
type _DNSRule struct {
Type string `json:"type,omitempty"`
DefaultOptions DefaultDNSRule `json:"-"`
LogicalOptions LogicalDNSRule `json:"-"`
}
type DNSRule _DNSRule
func (r DNSRule) 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((_DNSRule)(r), v)
}
func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_DNSRule)(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, (*_DNSRule)(r), v)
if err != nil {
return E.Cause(err, "dns route rule")
}
return nil
}
type DefaultDNSRule struct {
Inbound Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"`
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
Network string `json:"network,omitempty"`
AuthUser Listable[string] `json:"auth_user,omitempty"`
Protocol Listable[string] `json:"protocol,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"`
SourceIPCIDR Listable[string] `json:"source_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"`
ProcessName Listable[string] `json:"process_name,omitempty"`
ProcessPath Listable[string] `json:"process_path,omitempty"`
PackageName Listable[string] `json:"package_name,omitempty"`
User Listable[string] `json:"user,omitempty"`
UserID Listable[int32] `json:"user_id,omitempty"`
Outbound Listable[string] `json:"outbound,omitempty"`
ClashMode string `json:"clash_mode,omitempty"`
Invert bool `json:"invert,omitempty"`
Server string `json:"server,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
}
func (r DefaultDNSRule) IsValid() bool {
var defaultValue DefaultDNSRule
defaultValue.Invert = r.Invert
defaultValue.Server = r.Server
defaultValue.DisableCache = r.DisableCache
return !reflect.DeepEqual(r, defaultValue)
}
type LogicalDNSRule struct {
Mode string `json:"mode"`
Rules []DefaultDNSRule `json:"rules,omitempty"`
Invert bool `json:"invert,omitempty"`
Server string `json:"server,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
}
func (r LogicalDNSRule) IsValid() bool {
return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid)
}

View File

@@ -1,9 +1,17 @@
package option
import (
"reflect"
"github.com/sagernet/sing-box/common/json"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type RouteOptions struct {
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
Geosite *GeositeOptions `json:"geosite,omitempty"`
IPRules []IPRule `json:"ip_rules,omitempty"`
Rules []Rule `json:"rules,omitempty"`
Final string `json:"final,omitempty"`
FindProcess bool `json:"find_process,omitempty"`
@@ -24,3 +32,94 @@ type GeositeOptions struct {
DownloadURL string `json:"download_url,omitempty"`
DownloadDetour string `json:"download_detour,omitempty"`
}
type _Rule struct {
Type string `json:"type,omitempty"`
DefaultOptions DefaultRule `json:"-"`
LogicalOptions LogicalRule `json:"-"`
}
type Rule _Rule
func (r Rule) 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((_Rule)(r), v)
}
func (r *Rule) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_Rule)(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, (*_Rule)(r), v)
if err != nil {
return E.Cause(err, "route rule")
}
return nil
}
type DefaultRule struct {
Inbound Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"`
Network string `json:"network,omitempty"`
AuthUser Listable[string] `json:"auth_user,omitempty"`
Protocol Listable[string] `json:"protocol,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"`
ProcessName Listable[string] `json:"process_name,omitempty"`
ProcessPath Listable[string] `json:"process_path,omitempty"`
PackageName Listable[string] `json:"package_name,omitempty"`
User Listable[string] `json:"user,omitempty"`
UserID Listable[int32] `json:"user_id,omitempty"`
ClashMode string `json:"clash_mode,omitempty"`
Invert bool `json:"invert,omitempty"`
Outbound string `json:"outbound,omitempty"`
}
func (r DefaultRule) IsValid() bool {
var defaultValue DefaultRule
defaultValue.Invert = r.Invert
defaultValue.Outbound = r.Outbound
return !reflect.DeepEqual(r, defaultValue)
}
type LogicalRule struct {
Mode string `json:"mode"`
Rules []DefaultRule `json:"rules,omitempty"`
Invert bool `json:"invert,omitempty"`
Outbound string `json:"outbound,omitempty"`
}
func (r LogicalRule) IsValid() bool {
return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid)
}

View File

@@ -1,101 +0,0 @@
package option
import (
"reflect"
"github.com/sagernet/sing-box/common/json"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type _Rule struct {
Type string `json:"type,omitempty"`
DefaultOptions DefaultRule `json:"-"`
LogicalOptions LogicalRule `json:"-"`
}
type Rule _Rule
func (r Rule) 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((_Rule)(r), v)
}
func (r *Rule) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_Rule)(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, (*_Rule)(r), v)
if err != nil {
return E.Cause(err, "route rule")
}
return nil
}
type DefaultRule struct {
Inbound Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"`
Network Listable[string] `json:"network,omitempty"`
AuthUser Listable[string] `json:"auth_user,omitempty"`
Protocol Listable[string] `json:"protocol,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"`
ProcessName Listable[string] `json:"process_name,omitempty"`
ProcessPath Listable[string] `json:"process_path,omitempty"`
PackageName Listable[string] `json:"package_name,omitempty"`
User Listable[string] `json:"user,omitempty"`
UserID Listable[int32] `json:"user_id,omitempty"`
ClashMode string `json:"clash_mode,omitempty"`
Invert bool `json:"invert,omitempty"`
Outbound string `json:"outbound,omitempty"`
}
func (r DefaultRule) IsValid() bool {
var defaultValue DefaultRule
defaultValue.Invert = r.Invert
defaultValue.Outbound = r.Outbound
return !reflect.DeepEqual(r, defaultValue)
}
type LogicalRule struct {
Mode string `json:"mode"`
Rules []DefaultRule `json:"rules,omitempty"`
Invert bool `json:"invert,omitempty"`
Outbound string `json:"outbound,omitempty"`
}
func (r LogicalRule) IsValid() bool {
return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid)
}

View File

@@ -1,104 +0,0 @@
package option
import (
"reflect"
"github.com/sagernet/sing-box/common/json"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type _DNSRule struct {
Type string `json:"type,omitempty"`
DefaultOptions DefaultDNSRule `json:"-"`
LogicalOptions LogicalDNSRule `json:"-"`
}
type DNSRule _DNSRule
func (r DNSRule) 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((_DNSRule)(r), v)
}
func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_DNSRule)(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, (*_DNSRule)(r), v)
if err != nil {
return E.Cause(err, "dns route rule")
}
return nil
}
type DefaultDNSRule struct {
Inbound Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"`
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
Network Listable[string] `json:"network,omitempty"`
AuthUser Listable[string] `json:"auth_user,omitempty"`
Protocol Listable[string] `json:"protocol,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"`
SourceIPCIDR Listable[string] `json:"source_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"`
ProcessName Listable[string] `json:"process_name,omitempty"`
ProcessPath Listable[string] `json:"process_path,omitempty"`
PackageName Listable[string] `json:"package_name,omitempty"`
User Listable[string] `json:"user,omitempty"`
UserID Listable[int32] `json:"user_id,omitempty"`
Outbound Listable[string] `json:"outbound,omitempty"`
ClashMode string `json:"clash_mode,omitempty"`
Invert bool `json:"invert,omitempty"`
Server string `json:"server,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
}
func (r DefaultDNSRule) IsValid() bool {
var defaultValue DefaultDNSRule
defaultValue.Invert = r.Invert
defaultValue.Server = r.Server
defaultValue.DisableCache = r.DisableCache
return !reflect.DeepEqual(r, defaultValue)
}
type LogicalDNSRule struct {
Mode string `json:"mode"`
Rules []DefaultDNSRule `json:"rules,omitempty"`
Invert bool `json:"invert,omitempty"`
Server string `json:"server,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
}
func (r LogicalDNSRule) IsValid() bool {
return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid)
}

View File

@@ -1,125 +0,0 @@
package option
import (
"reflect"
"github.com/sagernet/sing-box/common/json"
C "github.com/sagernet/sing-box/constant"
tun "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"`
SourceIPCIDR Listable[string] `json:"source_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) {
switch tun.ActionType(a) {
case tun.ActionTypeReject, tun.ActionTypeDirect:
default:
return nil, E.New("unknown action: ", a)
}
return json.Marshal(tun.ActionTypeName(tun.ActionType(a)))
}
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
}
switch actionType {
case tun.ActionTypeReject, tun.ActionTypeDirect:
default:
return E.New("unknown action: ", a)
}
*a = RouteAction(actionType)
return nil
}
func (r DefaultIPRule) IsValid() bool {
var defaultValue DefaultIPRule
defaultValue.Invert = r.Invert
defaultValue.Action = r.Action
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)
}

View File

@@ -61,19 +61,19 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error {
}
type V2RayHTTPOptions struct {
Host Listable[string] `json:"host,omitempty"`
Path string `json:"path,omitempty"`
Method string `json:"method,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
IdleTimeout Duration `json:"idle_timeout,omitempty"`
PingTimeout Duration `json:"ping_timeout,omitempty"`
Host Listable[string] `json:"host,omitempty"`
Path string `json:"path,omitempty"`
Method string `json:"method,omitempty"`
Headers map[string]Listable[string] `json:"headers,omitempty"`
IdleTimeout Duration `json:"idle_timeout,omitempty"`
PingTimeout Duration `json:"ping_timeout,omitempty"`
}
type V2RayWebsocketOptions struct {
Path string `json:"path,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
MaxEarlyData uint32 `json:"max_early_data,omitempty"`
EarlyDataHeaderName string `json:"early_data_header_name,omitempty"`
Path string `json:"path,omitempty"`
Headers map[string]Listable[string] `json:"headers,omitempty"`
MaxEarlyData uint32 `json:"max_early_data,omitempty"`
EarlyDataHeaderName string `json:"early_data_header_name,omitempty"`
}
type V2RayQUICOptions struct{}

View File

@@ -13,5 +13,4 @@ type WireGuardOutboundOptions struct {
Workers int `json:"workers,omitempty"`
MTU uint32 `json:"mtu,omitempty"`
Network NetworkList `json:"network,omitempty"`
IPRewrite bool `json:"ip_rewrite,omitempty"`
}

View File

@@ -10,45 +10,51 @@ import (
E "github.com/sagernet/sing/common/exceptions"
)
func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Outbound) (adapter.Outbound, error) {
func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Outbound) (adapter.Outbound, error) {
var metadata *adapter.InboundContext
if tag != "" {
ctx, metadata = adapter.AppendContext(ctx)
metadata.Outbound = tag
}
if options.Type == "" {
return nil, E.New("missing outbound type")
}
ctx = ContextWithTag(ctx, tag)
switch options.Type {
case C.TypeDirect:
return NewDirect(router, logger, options.Tag, options.DirectOptions)
return NewDirect(router, logger, tag, options.DirectOptions)
case C.TypeBlock:
return NewBlock(logger, options.Tag), nil
return NewBlock(logger, tag), nil
case C.TypeDNS:
return NewDNS(router, options.Tag), nil
return NewDNS(router, tag), nil
case C.TypeSocks:
return NewSocks(router, logger, options.Tag, options.SocksOptions)
return NewSocks(router, logger, tag, options.SocksOptions)
case C.TypeHTTP:
return NewHTTP(router, logger, options.Tag, options.HTTPOptions)
return NewHTTP(router, logger, tag, options.HTTPOptions)
case C.TypeShadowsocks:
return NewShadowsocks(ctx, router, logger, options.Tag, options.ShadowsocksOptions)
return NewShadowsocks(ctx, router, logger, tag, options.ShadowsocksOptions)
case C.TypeVMess:
return NewVMess(ctx, router, logger, options.Tag, options.VMessOptions)
return NewVMess(ctx, router, logger, tag, options.VMessOptions)
case C.TypeTrojan:
return NewTrojan(ctx, router, logger, options.Tag, options.TrojanOptions)
return NewTrojan(ctx, router, logger, tag, options.TrojanOptions)
case C.TypeWireGuard:
return NewWireGuard(ctx, router, logger, options.Tag, options.WireGuardOptions)
return NewWireGuard(ctx, router, logger, tag, options.WireGuardOptions)
case C.TypeHysteria:
return NewHysteria(ctx, router, logger, options.Tag, options.HysteriaOptions)
return NewHysteria(ctx, router, logger, tag, options.HysteriaOptions)
case C.TypeTor:
return NewTor(ctx, router, logger, options.Tag, options.TorOptions)
return NewTor(ctx, router, logger, tag, options.TorOptions)
case C.TypeSSH:
return NewSSH(ctx, router, logger, options.Tag, options.SSHOptions)
return NewSSH(ctx, router, logger, tag, options.SSHOptions)
case C.TypeShadowTLS:
return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions)
return NewShadowTLS(ctx, router, logger, tag, options.ShadowTLSOptions)
case C.TypeShadowsocksR:
return NewShadowsocksR(ctx, router, logger, options.Tag, options.ShadowsocksROptions)
return NewShadowsocksR(ctx, router, logger, tag, options.ShadowsocksROptions)
case C.TypeVLESS:
return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions)
return NewVLESS(ctx, router, logger, tag, options.VLESSOptions)
case C.TypeSelector:
return NewSelector(router, logger, options.Tag, options.SelectorOptions)
return NewSelector(router, logger, tag, options.SelectorOptions)
case C.TypeURLTest:
return NewURLTest(router, logger, options.Tag, options.URLTestOptions)
return NewURLTest(router, logger, tag, options.URLTestOptions)
default:
return nil, E.New("unknown outbound type: ", options.Type)
}

View File

@@ -3,17 +3,18 @@ package outbound
import (
"context"
"net"
"net/netip"
"os"
"runtime"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/canceler"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/canceler"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
)
@@ -56,15 +57,21 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error {
ctx = adapter.WithContext(ctx, &metadata)
var outConn net.PacketConn
var destinationAddress netip.Addr
var err error
if len(metadata.DestinationAddresses) > 0 {
outConn, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses)
outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses)
} else {
outConn, err = this.ListenPacket(ctx, metadata.Destination)
}
if err != nil {
return N.HandshakeFailure(conn, err)
}
if destinationAddress.IsValid() {
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
natConn.UpdateDestination(destinationAddress)
}
}
switch metadata.Protocol {
case C.ProtocolSTUN:
ctx, conn = canceler.NewPacketConn(ctx, conn, C.STUNTimeout)

View File

@@ -7,11 +7,11 @@ import (
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/canceler"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/canceler"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/task"
@@ -102,11 +102,10 @@ 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 {
ctx = adapter.WithContext(ctx, &metadata)
fastClose, cancel := context.WithCancel(ctx)
fastClose, cancel := common.ContextWithCancelCause(ctx)
timeout := canceler.New(fastClose, cancel, C.DNSTimeout)
var group task.Group
group.Append0(func(ctx context.Context) error {
defer cancel()
_buffer := buf.StackNewSize(dns.FixedPacketSize)
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
@@ -115,11 +114,13 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada
buffer.FullReset()
destination, err := conn.ReadPacket(buffer)
if err != nil {
cancel(err)
return err
}
var message mDNS.Msg
err = message.Unpack(buffer.Bytes())
if err != nil {
cancel(err)
return err
}
timeout.Update()
@@ -127,17 +128,22 @@ func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metada
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
}()
}

View File

@@ -14,14 +14,14 @@ import (
"github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/protocol/http"
sHTTP "github.com/sagernet/sing/protocol/http"
)
var _ adapter.Outbound = (*HTTP)(nil)
type HTTP struct {
myOutboundAdapter
client *http.Client
client *sHTTP.Client
}
func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (*HTTP, error) {
@@ -37,7 +37,12 @@ func NewHTTP(router adapter.Router, logger log.ContextLogger, tag string, option
logger: logger,
tag: tag,
},
http.NewClient(detour, options.ServerOptions.Build(), options.Username, options.Password),
sHTTP.NewClient(sHTTP.Options{
Dialer: detour,
Server: options.ServerOptions.Build(),
Username: options.Username,
Password: options.Password,
}),
}, nil
}

14
outbound/lookback.go Normal file
View File

@@ -0,0 +1,14 @@
package outbound
import "context"
type outboundTagKey struct{}
func ContextWithTag(ctx context.Context, outboundTag string) context.Context {
return context.WithValue(ctx, outboundTagKey{}, outboundTag)
}
func TagFromContext(ctx context.Context) (string, bool) {
value, loaded := ctx.Value(outboundTagKey{}).(string)
return value, loaded
}

Some files were not shown because too many files have changed in this diff Show More