Compare commits

..

47 Commits

Author SHA1 Message Date
世界
a7f77d59c1 Update documentation 2023-06-11 22:38:37 +08:00
shadow750d6
597248130f Reconnect once if hysteria request fails
This allows graceful recovery when network isn't good enough.

[Original hysteria source
code](13d46da998/core/cs/client.go (L182))
has similar mechanism.
2023-06-11 22:21:32 +08:00
世界
3c2c9cf317 Migrate gVisor to fork 2023-06-11 22:07:36 +08:00
世界
e572b9d0cd Update dependencies 2023-06-11 20:58:59 +08:00
世界
52e9059a8d Fix fakeip routing 2023-06-11 20:58:59 +08:00
世界
0cb9cff690 Fix shadowsocks none client 2023-06-08 10:23:39 +08:00
世界
c0669cb2a5 Fix TLS 1.2 support for shadow-tls client 2023-06-07 21:03:39 +08:00
世界
c5902f2473 Fix using v2ray websocket transport with detour 2023-06-07 21:03:21 +08:00
世界
22028602e8 Improve read waiter interface 2023-06-07 21:03:11 +08:00
世界
bd54608473 Fix DNS outbound 2023-06-07 21:03:08 +08:00
世界
3741394269 Add cache_id option for Clash cache file 2023-06-07 21:03:06 +08:00
世界
6266d2df7e Fix shadowsocks AEAD UDP server 2023-06-07 21:02:52 +08:00
世界
01dfba722a Use API to create windows firewall rule 2023-06-07 21:01:29 +08:00
世界
f8d5f01665 Reimplemented shadowsocks client 2023-06-07 20:57:07 +08:00
Hellojack
ad999d4791 Fix UVariantLen usage 2023-06-07 20:56:57 +08:00
世界
6f1b258501 Improve DNS caching 2023-06-07 20:56:55 +08:00
世界
f949ddc0ab Set TCP keepalive for WireGuard gVisor TCP connections 2023-06-07 20:53:26 +08:00
Weltolk
f53007cbf3 documentation: Fix fakeip link broken 2023-06-07 20:53:13 +08:00
世界
c287731df9 Improve direct copy 2023-06-07 20:53:00 +08:00
世界
bc32c78d03 Improve multiplex 2023-06-07 20:46:34 +08:00
世界
daee0db7bb clash-api: Reset outbounds in DELETE /connections 2023-06-07 20:45:39 +08:00
世界
91fbf4c79b Add multiplexer for VLESS outbound 2023-06-07 20:45:39 +08:00
世界
54d9ef2f2a Update gVisor to 20230417.0 2023-06-07 20:45:25 +08:00
世界
e056d4502b Add debug http server 2023-06-07 20:45:25 +08:00
世界
98c2c439aa Add filemanager api 2023-06-07 20:45:25 +08:00
世界
b6068cea6b Update wireguard-go 2023-06-07 20:38:30 +08:00
世界
9c9affa719 Ignore system tun stack bind interface error 2023-06-07 20:35:02 +08:00
世界
8eb7dd0059 Improve VLESS request 2023-06-07 20:35:00 +08:00
世界
a62ad44883 Add deadline interface 2023-06-07 20:34:56 +08:00
世界
2850354070 shadowsocks: Multi-user support for legacy AEAD inbound
Signed-off-by: wwqgtxx <wwqgtxx@gmail.com>
2023-06-07 20:34:54 +08:00
世界
0a4abcbbc8 Add headers option for HTTP outbound 2023-06-07 20:34:52 +08:00
世界
b491c350ae URLTest improvements 2023-06-07 20:33:56 +08:00
世界
1fbe7c54bf Fix wireguard reconnect 2023-06-07 20:33:53 +08:00
世界
9d32fc9bd1 Use HTTPS URLTest source 2023-06-07 20:33:50 +08:00
世界
542612129d clash-api: Add Clash.Meta APIs 2023-06-07 20:33:41 +08:00
世界
750f87bb0a clash api: download clash-dashboard if external-ui directory is empty 2023-06-07 20:33:06 +08:00
世界
e168de79c7 Add multi-peer support for wireguard outbound 2023-06-07 20:33:04 +08:00
世界
9bca5a517f Add fakeip support 2023-06-07 20:31:26 +08:00
世界
aa94cfb876 Refactor rules 2023-06-07 20:28:21 +08:00
世界
52b776b561 Add dns reverse mapping 2023-06-07 20:19:46 +08:00
世界
c74d3a53d4 documentation: Update changelog 2023-05-19 15:48:35 +08:00
世界
fe7ac80a6c Update dependencies 2023-05-19 15:48:35 +08:00
世界
e50b334b9a Fix uTLS ALPN 2023-05-19 15:41:18 +08:00
XYenon
a0d8e374fb Fix incorrect use of sort.Slice 2023-05-19 15:40:48 +08:00
Larvan2
d3a67cb5ae Enable mkdocs search in documentation
Signed-off-by: Larvan2 <78135608+Larvan2@users.noreply.github.com>
2023-05-19 15:40:42 +08:00
世界
e69e98b185 Fix documentation 2023-05-19 15:40:13 +08:00
世界
5e1499d67b Update badtls 2023-05-19 15:39:40 +08:00
78 changed files with 1102 additions and 2817 deletions

View File

@@ -22,7 +22,7 @@ install:
fmt:
@gofumpt -l -w .
@gofmt -s -w .
@gci write --custom-order -s "standard,prefix(github.com/sagernet/),default" .
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
fmt_install:
go install -v mvdan.cc/gofumpt@latest

View File

@@ -21,3 +21,8 @@ type FakeIPStorage interface {
FakeIPLoad(address netip.Addr) (string, bool)
FakeIPReset() error
}
type FakeIPTransport interface {
dns.Transport
Store() FakeIPStore
}

View File

@@ -46,6 +46,7 @@ type InboundContext struct {
SourceGeoIPCode string
GeoIPCode string
ProcessInfo *process.Info
FakeIP bool
// dns cache

View File

@@ -4,7 +4,6 @@ import (
"context"
"net"
"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

@@ -25,9 +25,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)
@@ -45,9 +42,7 @@ type Router interface {
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
Rules() []Rule
IPRules() []IPRule
TimeService
@@ -56,6 +51,8 @@ type Router interface {
V2RayServer() V2RayServer
SetV2RayServer(server V2RayServer)
ResetNetwork() error
}
type routerContextKey struct{}
@@ -87,11 +84,6 @@ type DNSRule interface {
RewriteTTL() *uint32
}
type IPRule interface {
Rule
Action() tun.ActionType
}
type InterfaceUpdateListener interface {
InterfaceUpdated() error
}

View File

@@ -5,6 +5,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"
_ "github.com/sagernet/gomobile/event/key"
"github.com/sagernet/sing-box/cmd/internal/build_shared"
@@ -38,18 +39,23 @@ func main() {
var (
sharedFlags []string
debugFlags []string
sharedTags []string
debugTags []string
)
func init() {
sharedFlags = append(sharedFlags, "-trimpath")
sharedFlags = append(sharedFlags, "-ldflags")
currentTag, err := build_shared.ReadTag()
if err != nil {
currentTag = "unknown"
}
sharedFlags = append(sharedFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
debugFlags = append(debugFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
sharedTags = append(sharedTags, "test_sing_shadowsocks2")
debugTags = append(debugTags, "debug")
}
func buildAndroid() {
@@ -70,9 +76,9 @@ func buildAndroid() {
args = append(args, "-tags")
if !debugEnabled {
args = append(args, "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api")
args = append(args, strings.Join(sharedTags, ","))
} else {
args = append(args, "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,debug")
args = append(args, strings.Join(append(sharedTags, debugTags...), ","))
}
args = append(args, "./experimental/libbox")
@@ -109,11 +115,12 @@ func buildiOS() {
args = append(args, debugFlags...)
}
tags := append(sharedTags, "with_low_memory", "with_conntrack")
args = append(args, "-tags")
if !debugEnabled {
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack")
args = append(args, strings.Join(tags, ","))
} else {
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack,debug")
args = append(args, strings.Join(append(tags, debugTags...), ","))
}
args = append(args, "./experimental/libbox")

View File

@@ -4,6 +4,7 @@ import (
"io"
"net"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/x/list"
)
@@ -42,7 +43,7 @@ func (c *PacketConn) Close() error {
}
func (c *PacketConn) Upstream() any {
return c.PacketConn
return bufio.NewPacketConn(c.PacketConn)
}
func (c *PacketConn) ReaderReplaceable() bool {

View File

@@ -1,554 +1,21 @@
package mux
import (
"context"
"encoding/binary"
"io"
"net"
"sync"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing-mux"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
)
var _ N.Dialer = (*Client)(nil)
type Client struct {
access sync.Mutex
connections list.List[abstractSession]
ctx context.Context
dialer N.Dialer
protocol Protocol
maxConnections int
minStreams int
maxStreams int
paddingEnabled bool
}
func NewClient(ctx context.Context, dialer N.Dialer, protocol Protocol, maxConnections int, minStreams int, maxStreams int, paddingEnabled bool) (*Client, error) {
return &Client{
ctx: ctx,
dialer: dialer,
protocol: protocol,
maxConnections: maxConnections,
minStreams: minStreams,
maxStreams: maxStreams,
paddingEnabled: paddingEnabled,
}, nil
}
func NewClientWithOptions(ctx context.Context, dialer N.Dialer, options option.MultiplexOptions) (*Client, error) {
func NewClientWithOptions(dialer N.Dialer, options option.MultiplexOptions) (*Client, error) {
if !options.Enabled {
return nil, nil
}
if options.MaxConnections == 0 && options.MaxStreams == 0 {
options.MinStreams = 8
}
protocol, err := ParseProtocol(options.Protocol)
if err != nil {
return nil, err
}
return NewClient(ctx, dialer, protocol, options.MaxConnections, options.MinStreams, options.MaxStreams, options.Padding)
}
func (c *Client) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
switch N.NetworkName(network) {
case N.NetworkTCP:
stream, err := c.openStream()
if err != nil {
return nil, err
}
return &ClientConn{Conn: stream, destination: destination}, nil
case N.NetworkUDP:
stream, err := c.openStream()
if err != nil {
return nil, err
}
return bufio.NewUnbindPacketConn(&ClientPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}), nil
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}
}
func (c *Client) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
stream, err := c.openStream()
if err != nil {
return nil, err
}
return &ClientPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: destination}, nil
}
func (c *Client) openStream() (net.Conn, error) {
var (
session abstractSession
stream net.Conn
err error
)
for attempts := 0; attempts < 2; attempts++ {
session, err = c.offer()
if err != nil {
continue
}
stream, err = session.Open()
if err != nil {
continue
}
break
}
if err != nil {
return nil, err
}
return &wrapStream{stream}, nil
}
func (c *Client) offer() (abstractSession, error) {
c.access.Lock()
defer c.access.Unlock()
sessions := make([]abstractSession, 0, c.maxConnections)
for element := c.connections.Front(); element != nil; {
if element.Value.IsClosed() {
nextElement := element.Next()
c.connections.Remove(element)
element = nextElement
continue
}
sessions = append(sessions, element.Value)
element = element.Next()
}
session := common.MinBy(common.Filter(sessions, abstractSession.CanTakeNewRequest), abstractSession.NumStreams)
if session == nil {
return c.offerNew()
}
numStreams := session.NumStreams()
if numStreams == 0 {
return session, nil
}
if c.maxConnections > 0 {
if len(sessions) >= c.maxConnections || numStreams < c.minStreams {
return session, nil
}
} else {
if c.maxStreams > 0 && numStreams < c.maxStreams {
return session, nil
}
}
return c.offerNew()
}
func (c *Client) offerNew() (abstractSession, error) {
conn, err := c.dialer.DialContext(c.ctx, N.NetworkTCP, Destination)
if err != nil {
return nil, err
}
var version byte
if c.paddingEnabled {
version = Version1
} else {
version = Version0
}
conn = newProtocolConn(conn, Request{
Version: version,
Protocol: c.protocol,
PaddingEnabled: c.paddingEnabled,
return mux.NewClient(mux.Options{
Dialer: dialer,
Protocol: options.Protocol,
MaxConnections: options.MaxConnections,
MinStreams: options.MinStreams,
MaxStreams: options.MaxStreams,
Padding: options.Padding,
})
if c.paddingEnabled {
conn = newPaddingConn(conn)
}
session, err := c.protocol.newClient(conn)
if err != nil {
return nil, err
}
c.connections.PushBack(session)
return session, nil
}
func (c *Client) Reset() {
c.access.Lock()
defer c.access.Unlock()
for _, session := range c.connections.Array() {
session.Close()
}
c.connections.Init()
}
func (c *Client) Close() error {
c.access.Lock()
defer c.access.Unlock()
for _, session := range c.connections.Array() {
session.Close()
}
return nil
}
type ClientConn struct {
net.Conn
destination M.Socksaddr
requestWrite bool
responseRead bool
}
func (c *ClientConn) readResponse() error {
response, err := ReadStreamResponse(c.Conn)
if err != nil {
return err
}
if response.Status == statusError {
return E.New("remote error: ", response.Message)
}
return nil
}
func (c *ClientConn) Read(b []byte) (n int, err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
return c.Conn.Read(b)
}
func (c *ClientConn) Write(b []byte) (n int, err error) {
if c.requestWrite {
return c.Conn.Write(b)
}
request := StreamRequest{
Network: N.NetworkTCP,
Destination: c.destination,
}
_buffer := buf.StackNewSize(streamRequestLen(request) + len(b))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
EncodeStreamRequest(request, buffer)
buffer.Write(b)
_, err = c.Conn.Write(buffer.Bytes())
if err != nil {
return
}
c.requestWrite = true
return len(b), nil
}
func (c *ClientConn) ReadFrom(r io.Reader) (n int64, err error) {
if !c.requestWrite {
return bufio.ReadFrom0(c, r)
}
return bufio.Copy(c.Conn, r)
}
func (c *ClientConn) WriteTo(w io.Writer) (n int64, err error) {
if !c.responseRead {
return bufio.WriteTo0(c, w)
}
return bufio.Copy(w, c.Conn)
}
func (c *ClientConn) LocalAddr() net.Addr {
return c.Conn.LocalAddr()
}
func (c *ClientConn) RemoteAddr() net.Addr {
return c.destination.TCPAddr()
}
func (c *ClientConn) ReaderReplaceable() bool {
return c.responseRead
}
func (c *ClientConn) WriterReplaceable() bool {
return c.requestWrite
}
func (c *ClientConn) NeedAdditionalReadDeadline() bool {
return true
}
func (c *ClientConn) Upstream() any {
return c.Conn
}
type ClientPacketConn struct {
N.ExtendedConn
destination M.Socksaddr
requestWrite bool
responseRead bool
}
func (c *ClientPacketConn) readResponse() error {
response, err := ReadStreamResponse(c.ExtendedConn)
if err != nil {
return err
}
if response.Status == statusError {
return E.New("remote error: ", response.Message)
}
return nil
}
func (c *ClientPacketConn) Read(b []byte) (n int, err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
if cap(b) < int(length) {
return 0, io.ErrShortBuffer
}
return io.ReadFull(c.ExtendedConn, b[:length])
}
func (c *ClientPacketConn) writeRequest(payload []byte) (n int, err error) {
request := StreamRequest{
Network: N.NetworkUDP,
Destination: c.destination,
}
rLen := streamRequestLen(request)
if len(payload) > 0 {
rLen += 2 + len(payload)
}
_buffer := buf.StackNewSize(rLen)
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
EncodeStreamRequest(request, buffer)
if len(payload) > 0 {
common.Must(
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
common.Error(buffer.Write(payload)),
)
}
_, err = c.ExtendedConn.Write(buffer.Bytes())
if err != nil {
return
}
c.requestWrite = true
return len(payload), nil
}
func (c *ClientPacketConn) Write(b []byte) (n int, err error) {
if !c.requestWrite {
return c.writeRequest(b)
}
err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(b)))
if err != nil {
return
}
return c.ExtendedConn.Write(b)
}
func (c *ClientPacketConn) ReadBuffer(buffer *buf.Buffer) (err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
return
}
func (c *ClientPacketConn) WriteBuffer(buffer *buf.Buffer) error {
if !c.requestWrite {
defer buffer.Release()
return common.Error(c.writeRequest(buffer.Bytes()))
}
bLen := buffer.Len()
binary.BigEndian.PutUint16(buffer.ExtendHeader(2), uint16(bLen))
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ClientPacketConn) FrontHeadroom() int {
return 2
}
func (c *ClientPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
err = c.ReadBuffer(buffer)
return
}
func (c *ClientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
return c.WriteBuffer(buffer)
}
func (c *ClientPacketConn) LocalAddr() net.Addr {
return c.ExtendedConn.LocalAddr()
}
func (c *ClientPacketConn) RemoteAddr() net.Addr {
return c.destination.UDPAddr()
}
func (c *ClientPacketConn) NeedAdditionalReadDeadline() bool {
return true
}
func (c *ClientPacketConn) Upstream() any {
return c.ExtendedConn
}
var _ N.NetPacketConn = (*ClientPacketAddrConn)(nil)
type ClientPacketAddrConn struct {
N.ExtendedConn
destination M.Socksaddr
requestWrite bool
responseRead bool
}
func (c *ClientPacketAddrConn) readResponse() error {
response, err := ReadStreamResponse(c.ExtendedConn)
if err != nil {
return err
}
if response.Status == statusError {
return E.New("remote error: ", response.Message)
}
return nil
}
func (c *ClientPacketAddrConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
destination, err := M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
if err != nil {
return
}
if destination.IsFqdn() {
addr = destination
} else {
addr = destination.UDPAddr()
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
if cap(p) < int(length) {
return 0, nil, io.ErrShortBuffer
}
n, err = io.ReadFull(c.ExtendedConn, p[:length])
return
}
func (c *ClientPacketAddrConn) writeRequest(payload []byte, destination M.Socksaddr) (n int, err error) {
request := StreamRequest{
Network: N.NetworkUDP,
Destination: c.destination,
PacketAddr: true,
}
rLen := streamRequestLen(request)
if len(payload) > 0 {
rLen += M.SocksaddrSerializer.AddrPortLen(destination) + 2 + len(payload)
}
_buffer := buf.StackNewSize(rLen)
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
EncodeStreamRequest(request, buffer)
if len(payload) > 0 {
common.Must(
M.SocksaddrSerializer.WriteAddrPort(buffer, destination),
binary.Write(buffer, binary.BigEndian, uint16(len(payload))),
common.Error(buffer.Write(payload)),
)
}
_, err = c.ExtendedConn.Write(buffer.Bytes())
if err != nil {
return
}
c.requestWrite = true
return len(payload), nil
}
func (c *ClientPacketAddrConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
if !c.requestWrite {
return c.writeRequest(p, M.SocksaddrFromNet(addr))
}
err = M.SocksaddrSerializer.WriteAddrPort(c.ExtendedConn, M.SocksaddrFromNet(addr))
if err != nil {
return
}
err = binary.Write(c.ExtendedConn, binary.BigEndian, uint16(len(p)))
if err != nil {
return
}
return c.ExtendedConn.Write(p)
}
func (c *ClientPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
if !c.responseRead {
err = c.readResponse()
if err != nil {
return
}
c.responseRead = true
}
destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
if err != nil {
return
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
return
}
func (c *ClientPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
if !c.requestWrite {
defer buffer.Release()
return common.Error(c.writeRequest(buffer.Bytes(), destination))
}
bLen := buffer.Len()
header := buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination) + 2))
common.Must(
M.SocksaddrSerializer.WriteAddrPort(header, destination),
binary.Write(header, binary.BigEndian, uint16(bLen)),
)
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ClientPacketAddrConn) LocalAddr() net.Addr {
return c.ExtendedConn.LocalAddr()
}
func (c *ClientPacketAddrConn) FrontHeadroom() int {
return 2 + M.MaxSocksaddrLength
}
func (c *ClientPacketAddrConn) NeedAdditionalReadDeadline() bool {
return true
}
func (c *ClientPacketAddrConn) Upstream() any {
return c.ExtendedConn
}

View File

@@ -1,235 +0,0 @@
package mux
import (
"context"
"crypto/tls"
"io"
"net"
"net/http"
"net/url"
"os"
"time"
"github.com/sagernet/sing-box/transport/v2rayhttp"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"golang.org/x/net/http2"
)
const idleTimeout = 30 * time.Second
var _ abstractSession = (*H2MuxServerSession)(nil)
type H2MuxServerSession struct {
server http2.Server
active atomic.Int32
conn net.Conn
inbound chan net.Conn
done chan struct{}
}
func NewH2MuxServer(conn net.Conn) *H2MuxServerSession {
session := &H2MuxServerSession{
conn: conn,
inbound: make(chan net.Conn),
done: make(chan struct{}),
server: http2.Server{
IdleTimeout: idleTimeout,
},
}
go func() {
session.server.ServeConn(conn, &http2.ServeConnOpts{
Handler: session,
})
_ = session.Close()
}()
return session
}
func (s *H2MuxServerSession) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
s.active.Add(1)
defer s.active.Add(-1)
writer.WriteHeader(http.StatusOK)
conn := newHTTP2Wrapper(&v2rayhttp.ServerHTTPConn{
HTTP2Conn: v2rayhttp.NewHTTPConn(request.Body, writer),
Flusher: writer.(http.Flusher),
})
s.inbound <- conn
select {
case <-conn.done:
case <-s.done:
}
}
func (s *H2MuxServerSession) Open() (net.Conn, error) {
return nil, os.ErrInvalid
}
func (s *H2MuxServerSession) Accept() (net.Conn, error) {
select {
case conn := <-s.inbound:
return conn, nil
case <-s.done:
return nil, os.ErrClosed
}
}
func (s *H2MuxServerSession) NumStreams() int {
return int(s.active.Load())
}
func (s *H2MuxServerSession) Close() error {
select {
case <-s.done:
default:
close(s.done)
}
return s.conn.Close()
}
func (s *H2MuxServerSession) IsClosed() bool {
select {
case <-s.done:
return true
default:
return false
}
}
func (s *H2MuxServerSession) CanTakeNewRequest() bool {
return false
}
type h2MuxConnWrapper struct {
N.ExtendedConn
done chan struct{}
}
func newHTTP2Wrapper(conn net.Conn) *h2MuxConnWrapper {
return &h2MuxConnWrapper{
ExtendedConn: bufio.NewExtendedConn(conn),
done: make(chan struct{}),
}
}
func (w *h2MuxConnWrapper) Write(p []byte) (n int, err error) {
select {
case <-w.done:
return 0, net.ErrClosed
default:
}
return w.ExtendedConn.Write(p)
}
func (w *h2MuxConnWrapper) WriteBuffer(buffer *buf.Buffer) error {
select {
case <-w.done:
return net.ErrClosed
default:
}
return w.ExtendedConn.WriteBuffer(buffer)
}
func (w *h2MuxConnWrapper) Close() error {
select {
case <-w.done:
default:
close(w.done)
}
return w.ExtendedConn.Close()
}
func (w *h2MuxConnWrapper) Upstream() any {
return w.ExtendedConn
}
var _ abstractSession = (*H2MuxClientSession)(nil)
type H2MuxClientSession struct {
transport *http2.Transport
clientConn *http2.ClientConn
done chan struct{}
}
func NewH2MuxClient(conn net.Conn) (*H2MuxClientSession, error) {
session := &H2MuxClientSession{
transport: &http2.Transport{
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
return conn, nil
},
ReadIdleTimeout: idleTimeout,
},
done: make(chan struct{}),
}
session.transport.ConnPool = session
clientConn, err := session.transport.NewClientConn(conn)
if err != nil {
return nil, err
}
session.clientConn = clientConn
return session, nil
}
func (s *H2MuxClientSession) GetClientConn(req *http.Request, addr string) (*http2.ClientConn, error) {
return s.clientConn, nil
}
func (s *H2MuxClientSession) MarkDead(conn *http2.ClientConn) {
s.Close()
}
func (s *H2MuxClientSession) Open() (net.Conn, error) {
pipeInReader, pipeInWriter := io.Pipe()
request := &http.Request{
Method: http.MethodConnect,
Body: pipeInReader,
URL: &url.URL{Scheme: "https", Host: "localhost"},
}
conn := v2rayhttp.NewLateHTTPConn(pipeInWriter)
go func() {
response, err := s.transport.RoundTrip(request)
if err != nil {
conn.Setup(nil, err)
} else if response.StatusCode != 200 {
response.Body.Close()
conn.Setup(nil, E.New("unexpected status: ", response.StatusCode, " ", response.Status))
} else {
conn.Setup(response.Body, nil)
}
}()
return conn, nil
}
func (s *H2MuxClientSession) Accept() (net.Conn, error) {
return nil, os.ErrInvalid
}
func (s *H2MuxClientSession) NumStreams() int {
return s.clientConn.State().StreamsActive
}
func (s *H2MuxClientSession) Close() error {
select {
case <-s.done:
default:
close(s.done)
}
return s.clientConn.Close()
}
func (s *H2MuxClientSession) IsClosed() bool {
select {
case <-s.done:
return true
default:
}
return s.clientConn.State().Closed
}
func (s *H2MuxClientSession) CanTakeNewRequest() bool {
return s.clientConn.CanTakeNewRequest()
}

View File

@@ -1,240 +0,0 @@
package mux
import (
"encoding/binary"
"io"
"math/rand"
"net"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/rw"
)
const kFirstPaddings = 16
type paddingConn struct {
N.ExtendedConn
writer N.VectorisedWriter
readPadding int
writePadding int
readRemaining int
paddingRemaining int
}
func newPaddingConn(conn net.Conn) net.Conn {
writer, isVectorised := bufio.CreateVectorisedWriter(conn)
if isVectorised {
return &vectorisedPaddingConn{
paddingConn{
ExtendedConn: bufio.NewExtendedConn(conn),
writer: bufio.NewVectorisedWriter(conn),
},
writer,
}
} else {
return &paddingConn{
ExtendedConn: bufio.NewExtendedConn(conn),
writer: bufio.NewVectorisedWriter(conn),
}
}
}
func (c *paddingConn) Read(p []byte) (n int, err error) {
if c.readRemaining > 0 {
if len(p) > c.readRemaining {
p = p[:c.readRemaining]
}
n, err = c.ExtendedConn.Read(p)
if err != nil {
return
}
c.readRemaining -= n
return
}
if c.paddingRemaining > 0 {
err = rw.SkipN(c.ExtendedConn, c.paddingRemaining)
if err != nil {
return
}
c.paddingRemaining = 0
}
if c.readPadding < kFirstPaddings {
var paddingHdr []byte
if len(p) >= 4 {
paddingHdr = p[:4]
} else {
_paddingHdr := make([]byte, 4)
defer common.KeepAlive(_paddingHdr)
paddingHdr = common.Dup(_paddingHdr)
}
_, err = io.ReadFull(c.ExtendedConn, paddingHdr)
if err != nil {
return
}
originalDataSize := int(binary.BigEndian.Uint16(paddingHdr[:2]))
paddingLen := int(binary.BigEndian.Uint16(paddingHdr[2:]))
if len(p) > originalDataSize {
p = p[:originalDataSize]
}
n, err = c.ExtendedConn.Read(p)
if err != nil {
return
}
c.readPadding++
c.readRemaining = originalDataSize - n
c.paddingRemaining = paddingLen
return
}
return c.ExtendedConn.Read(p)
}
func (c *paddingConn) Write(p []byte) (n int, err error) {
for pLen := len(p); pLen > 0; {
var data []byte
if pLen > 65535 {
data = p[:65535]
p = p[65535:]
pLen -= 65535
} else {
data = p
pLen = 0
}
var writeN int
writeN, err = c.write(data)
n += writeN
if err != nil {
break
}
}
return n, err
}
func (c *paddingConn) write(p []byte) (n int, err error) {
if c.writePadding < kFirstPaddings {
paddingLen := 256 + rand.Intn(512)
_buffer := buf.StackNewSize(4 + len(p) + paddingLen)
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
header := buffer.Extend(4)
binary.BigEndian.PutUint16(header[:2], uint16(len(p)))
binary.BigEndian.PutUint16(header[2:], uint16(paddingLen))
common.Must1(buffer.Write(p))
buffer.Extend(paddingLen)
_, err = c.ExtendedConn.Write(buffer.Bytes())
if err == nil {
n = len(p)
}
c.writePadding++
return
}
return c.ExtendedConn.Write(p)
}
func (c *paddingConn) ReadBuffer(buffer *buf.Buffer) error {
p := buffer.FreeBytes()
if c.readRemaining > 0 {
if len(p) > c.readRemaining {
p = p[:c.readRemaining]
}
n, err := c.ExtendedConn.Read(p)
if err != nil {
return err
}
c.readRemaining -= n
buffer.Truncate(n)
return nil
}
if c.paddingRemaining > 0 {
err := rw.SkipN(c.ExtendedConn, c.paddingRemaining)
if err != nil {
return err
}
c.paddingRemaining = 0
}
if c.readPadding < kFirstPaddings {
var paddingHdr []byte
if len(p) >= 4 {
paddingHdr = p[:4]
} else {
_paddingHdr := make([]byte, 4)
defer common.KeepAlive(_paddingHdr)
paddingHdr = common.Dup(_paddingHdr)
}
_, err := io.ReadFull(c.ExtendedConn, paddingHdr)
if err != nil {
return err
}
originalDataSize := int(binary.BigEndian.Uint16(paddingHdr[:2]))
paddingLen := int(binary.BigEndian.Uint16(paddingHdr[2:]))
if len(p) > originalDataSize {
p = p[:originalDataSize]
}
n, err := c.ExtendedConn.Read(p)
if err != nil {
return err
}
c.readPadding++
c.readRemaining = originalDataSize - n
c.paddingRemaining = paddingLen
buffer.Truncate(n)
return nil
}
return c.ExtendedConn.ReadBuffer(buffer)
}
func (c *paddingConn) WriteBuffer(buffer *buf.Buffer) error {
if c.writePadding < kFirstPaddings {
bufferLen := buffer.Len()
if bufferLen > 65535 {
return common.Error(c.Write(buffer.Bytes()))
}
paddingLen := 256 + rand.Intn(512)
header := buffer.ExtendHeader(4)
binary.BigEndian.PutUint16(header[:2], uint16(bufferLen))
binary.BigEndian.PutUint16(header[2:], uint16(paddingLen))
buffer.Extend(paddingLen)
c.writePadding++
}
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *paddingConn) FrontHeadroom() int {
return 4 + 256 + 1024
}
type vectorisedPaddingConn struct {
paddingConn
writer N.VectorisedWriter
}
func (c *vectorisedPaddingConn) WriteVectorised(buffers []*buf.Buffer) error {
if c.writePadding < kFirstPaddings {
bufferLen := buf.LenMulti(buffers)
if bufferLen > 65535 {
defer buf.ReleaseMulti(buffers)
for _, buffer := range buffers {
_, err := c.Write(buffer.Bytes())
if err != nil {
return err
}
}
return nil
}
paddingLen := 256 + rand.Intn(512)
header := buf.NewSize(4)
common.Must(
binary.Write(header, binary.BigEndian, uint16(bufferLen)),
binary.Write(header, binary.BigEndian, uint16(paddingLen)),
)
c.writePadding++
padding := buf.NewSize(paddingLen)
padding.Extend(paddingLen)
buffers = append(append([]*buf.Buffer{header}, buffers...), padding)
}
return c.writer.WriteVectorised(buffers)
}

View File

@@ -1,299 +1,14 @@
package mux
import (
"encoding/binary"
"io"
"math/rand"
"net"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/smux"
"github.com/hashicorp/yamux"
"github.com/sagernet/sing-mux"
)
var Destination = M.Socksaddr{
Fqdn: "sp.mux.sing-box.arpa",
Port: 444,
}
const (
ProtocolSMux Protocol = iota
ProtocolYAMux
ProtocolH2Mux
type (
Client = mux.Client
)
type Protocol byte
func ParseProtocol(name string) (Protocol, error) {
switch name {
case "", "smux":
return ProtocolSMux, nil
case "yamux":
return ProtocolYAMux, nil
case "h2mux":
return ProtocolH2Mux, nil
default:
return ProtocolSMux, E.New("unknown multiplex protocol: ", name)
}
}
func (p Protocol) newServer(conn net.Conn) (abstractSession, error) {
switch p {
case ProtocolSMux:
session, err := smux.Server(conn, smuxConfig())
if err != nil {
return nil, err
}
return &smuxSession{session}, nil
case ProtocolYAMux:
session, err := yamux.Server(conn, yaMuxConfig())
if err != nil {
return nil, err
}
return &yamuxSession{session}, nil
case ProtocolH2Mux:
return NewH2MuxServer(conn), nil
default:
panic("unknown protocol")
}
}
func (p Protocol) newClient(conn net.Conn) (abstractSession, error) {
switch p {
case ProtocolSMux:
session, err := smux.Client(conn, smuxConfig())
if err != nil {
return nil, err
}
return &smuxSession{session}, nil
case ProtocolYAMux:
session, err := yamux.Client(conn, yaMuxConfig())
if err != nil {
return nil, err
}
return &yamuxSession{session}, nil
case ProtocolH2Mux:
return NewH2MuxClient(conn)
default:
panic("unknown protocol")
}
}
func smuxConfig() *smux.Config {
config := smux.DefaultConfig()
config.KeepAliveDisabled = true
return config
}
func yaMuxConfig() *yamux.Config {
config := yamux.DefaultConfig()
config.LogOutput = io.Discard
config.StreamCloseTimeout = C.TCPTimeout
config.StreamOpenTimeout = C.TCPTimeout
return config
}
func (p Protocol) String() string {
switch p {
case ProtocolSMux:
return "smux"
case ProtocolYAMux:
return "yamux"
case ProtocolH2Mux:
return "h2mux"
default:
return "unknown"
}
}
const (
Version0 = iota
Version1
var (
Destination = mux.Destination
HandleConnection = mux.HandleConnection
)
type Request struct {
Version byte
Protocol Protocol
PaddingEnabled bool
}
func ReadRequest(reader io.Reader) (*Request, error) {
version, err := rw.ReadByte(reader)
if err != nil {
return nil, err
}
if version < Version0 || version > Version1 {
return nil, E.New("unsupported version: ", version)
}
protocol, err := rw.ReadByte(reader)
if err != nil {
return nil, err
}
var paddingEnabled bool
if version == Version1 {
err = binary.Read(reader, binary.BigEndian, &paddingEnabled)
if err != nil {
return nil, err
}
if paddingEnabled {
var paddingLen uint16
err = binary.Read(reader, binary.BigEndian, &paddingLen)
if err != nil {
return nil, err
}
err = rw.SkipN(reader, int(paddingLen))
if err != nil {
return nil, err
}
}
}
return &Request{Version: version, Protocol: Protocol(protocol), PaddingEnabled: paddingEnabled}, nil
}
func EncodeRequest(request Request, payload []byte) *buf.Buffer {
var requestLen int
requestLen += 2
var paddingLen uint16
if request.Version == Version1 {
requestLen += 1
if request.PaddingEnabled {
requestLen += 2
paddingLen = uint16(256 + rand.Intn(512))
requestLen += int(paddingLen)
}
}
buffer := buf.NewSize(requestLen + len(payload))
common.Must(
buffer.WriteByte(request.Version),
buffer.WriteByte(byte(request.Protocol)),
)
if request.Version == Version1 {
common.Must(binary.Write(buffer, binary.BigEndian, request.PaddingEnabled))
if request.PaddingEnabled {
common.Must(binary.Write(buffer, binary.BigEndian, paddingLen))
buffer.Extend(int(paddingLen))
}
}
common.Must1(buffer.Write(payload))
return buffer
}
const (
flagUDP = 1
flagAddr = 2
statusSuccess = 0
statusError = 1
)
type StreamRequest struct {
Network string
Destination M.Socksaddr
PacketAddr bool
}
func ReadStreamRequest(reader io.Reader) (*StreamRequest, error) {
var flags uint16
err := binary.Read(reader, binary.BigEndian, &flags)
if err != nil {
return nil, err
}
destination, err := M.SocksaddrSerializer.ReadAddrPort(reader)
if err != nil {
return nil, err
}
var network string
var udpAddr bool
if flags&flagUDP == 0 {
network = N.NetworkTCP
} else {
network = N.NetworkUDP
udpAddr = flags&flagAddr != 0
}
return &StreamRequest{network, destination, udpAddr}, nil
}
func streamRequestLen(request StreamRequest) int {
var rLen int
rLen += 1 // version
rLen += 2 // flags
rLen += M.SocksaddrSerializer.AddrPortLen(request.Destination)
return rLen
}
func EncodeStreamRequest(request StreamRequest, buffer *buf.Buffer) {
destination := request.Destination
var flags uint16
if request.Network == N.NetworkUDP {
flags |= flagUDP
}
if request.PacketAddr {
flags |= flagAddr
if !destination.IsValid() {
destination = Destination
}
}
common.Must(
binary.Write(buffer, binary.BigEndian, flags),
M.SocksaddrSerializer.WriteAddrPort(buffer, destination),
)
}
type StreamResponse struct {
Status uint8
Message string
}
func ReadStreamResponse(reader io.Reader) (*StreamResponse, error) {
var response StreamResponse
status, err := rw.ReadByte(reader)
if err != nil {
return nil, err
}
response.Status = status
if status == statusError {
response.Message, err = rw.ReadVString(reader)
if err != nil {
return nil, err
}
}
return &response, nil
}
type wrapStream struct {
net.Conn
}
func (w *wrapStream) Read(p []byte) (n int, err error) {
n, err = w.Conn.Read(p)
err = wrapError(err)
return
}
func (w *wrapStream) Write(p []byte) (n int, err error) {
n, err = w.Conn.Write(p)
err = wrapError(err)
return
}
func (w *wrapStream) WriteIsThreadUnsafe() {
}
func (w *wrapStream) Upstream() any {
return w.Conn
}
func wrapError(err error) error {
switch err {
case yamux.ErrStreamClosed:
return io.EOF
default:
return err
}
}

View File

@@ -1,272 +0,0 @@
package mux
import (
"context"
"encoding/binary"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/rw"
"github.com/sagernet/sing/common/task"
)
func NewConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, conn net.Conn, metadata adapter.InboundContext) error {
request, err := ReadRequest(conn)
if err != nil {
return err
}
if request.PaddingEnabled {
conn = newPaddingConn(conn)
}
session, err := request.Protocol.newServer(conn)
if err != nil {
return err
}
var group task.Group
group.Append0(func(ctx context.Context) error {
var stream net.Conn
for {
stream, err = session.Accept()
if err != nil {
return err
}
go newConnection(ctx, router, errorHandler, logger, stream, metadata)
}
})
group.Cleanup(func() {
session.Close()
})
return group.Run(ctx)
}
func newConnection(ctx context.Context, router adapter.Router, errorHandler E.Handler, logger log.ContextLogger, stream net.Conn, metadata adapter.InboundContext) {
stream = &wrapStream{stream}
request, err := ReadStreamRequest(stream)
if err != nil {
logger.ErrorContext(ctx, err)
return
}
metadata.Destination = request.Destination
if request.Network == N.NetworkTCP {
logger.InfoContext(ctx, "inbound multiplex connection to ", metadata.Destination)
hErr := router.RouteConnection(ctx, &ServerConn{ExtendedConn: bufio.NewExtendedConn(stream)}, metadata)
stream.Close()
if hErr != nil {
errorHandler.NewError(ctx, hErr)
}
} else {
var packetConn N.PacketConn
if !request.PacketAddr {
logger.InfoContext(ctx, "inbound multiplex packet connection to ", metadata.Destination)
packetConn = &ServerPacketConn{ExtendedConn: bufio.NewExtendedConn(stream), destination: request.Destination}
} else {
logger.InfoContext(ctx, "inbound multiplex packet connection")
packetConn = &ServerPacketAddrConn{ExtendedConn: bufio.NewExtendedConn(stream)}
}
hErr := router.RoutePacketConnection(ctx, packetConn, metadata)
stream.Close()
if hErr != nil {
errorHandler.NewError(ctx, hErr)
}
}
}
var _ N.HandshakeConn = (*ServerConn)(nil)
type ServerConn struct {
N.ExtendedConn
responseWrite bool
}
func (c *ServerConn) HandshakeFailure(err error) error {
errMessage := err.Error()
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
common.Must(
buffer.WriteByte(statusError),
rw.WriteVString(_buffer, errMessage),
)
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerConn) Write(b []byte) (n int, err error) {
if c.responseWrite {
return c.ExtendedConn.Write(b)
}
_buffer := buf.StackNewSize(1 + len(b))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
common.Must(
buffer.WriteByte(statusSuccess),
common.Error(buffer.Write(b)),
)
_, err = c.ExtendedConn.Write(buffer.Bytes())
if err != nil {
return
}
c.responseWrite = true
return len(b), nil
}
func (c *ServerConn) WriteBuffer(buffer *buf.Buffer) error {
if c.responseWrite {
return c.ExtendedConn.WriteBuffer(buffer)
}
buffer.ExtendHeader(1)[0] = statusSuccess
c.responseWrite = true
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerConn) FrontHeadroom() int {
if !c.responseWrite {
return 1
}
return 0
}
func (c *ServerConn) NeedAdditionalReadDeadline() bool {
return true
}
func (c *ServerConn) Upstream() any {
return c.ExtendedConn
}
var (
_ N.HandshakeConn = (*ServerPacketConn)(nil)
_ N.PacketConn = (*ServerPacketConn)(nil)
)
type ServerPacketConn struct {
N.ExtendedConn
destination M.Socksaddr
responseWrite bool
}
func (c *ServerPacketConn) HandshakeFailure(err error) error {
errMessage := err.Error()
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
common.Must(
buffer.WriteByte(statusError),
rw.WriteVString(_buffer, errMessage),
)
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
if err != nil {
return
}
destination = c.destination
return
}
func (c *ServerPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
pLen := buffer.Len()
common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen)))
if !c.responseWrite {
buffer.ExtendHeader(1)[0] = statusSuccess
c.responseWrite = true
}
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerPacketConn) NeedAdditionalReadDeadline() bool {
return true
}
func (c *ServerPacketConn) Upstream() any {
return c.ExtendedConn
}
func (c *ServerPacketConn) FrontHeadroom() int {
if !c.responseWrite {
return 3
}
return 2
}
var (
_ N.HandshakeConn = (*ServerPacketAddrConn)(nil)
_ N.PacketConn = (*ServerPacketAddrConn)(nil)
)
type ServerPacketAddrConn struct {
N.ExtendedConn
responseWrite bool
}
func (c *ServerPacketAddrConn) HandshakeFailure(err error) error {
errMessage := err.Error()
_buffer := buf.StackNewSize(1 + rw.UVariantLen(uint64(len(errMessage))) + len(errMessage))
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
common.Must(
buffer.WriteByte(statusError),
rw.WriteVString(_buffer, errMessage),
)
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerPacketAddrConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
destination, err = M.SocksaddrSerializer.ReadAddrPort(c.ExtendedConn)
if err != nil {
return
}
var length uint16
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
if err != nil {
return
}
_, err = buffer.ReadFullFrom(c.ExtendedConn, int(length))
if err != nil {
return
}
return
}
func (c *ServerPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
pLen := buffer.Len()
common.Must(binary.Write(buf.With(buffer.ExtendHeader(2)), binary.BigEndian, uint16(pLen)))
common.Must(M.SocksaddrSerializer.WriteAddrPort(buf.With(buffer.ExtendHeader(M.SocksaddrSerializer.AddrPortLen(destination))), destination))
if !c.responseWrite {
buffer.ExtendHeader(1)[0] = statusSuccess
c.responseWrite = true
}
return c.ExtendedConn.WriteBuffer(buffer)
}
func (c *ServerPacketAddrConn) NeedAdditionalReadDeadline() bool {
return true
}
func (c *ServerPacketAddrConn) Upstream() any {
return c.ExtendedConn
}
func (c *ServerPacketAddrConn) FrontHeadroom() int {
if !c.responseWrite {
return 3 + M.MaxSocksaddrLength
}
return 2 + M.MaxSocksaddrLength
}

View File

@@ -1,111 +0,0 @@
package mux
import (
"io"
"net"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/smux"
"github.com/hashicorp/yamux"
)
type abstractSession interface {
Open() (net.Conn, error)
Accept() (net.Conn, error)
NumStreams() int
Close() error
IsClosed() bool
CanTakeNewRequest() bool
}
var _ abstractSession = (*smuxSession)(nil)
type smuxSession struct {
*smux.Session
}
func (s *smuxSession) Open() (net.Conn, error) {
return s.OpenStream()
}
func (s *smuxSession) Accept() (net.Conn, error) {
return s.AcceptStream()
}
func (s *smuxSession) CanTakeNewRequest() bool {
return true
}
type yamuxSession struct {
*yamux.Session
}
func (y *yamuxSession) CanTakeNewRequest() bool {
return true
}
type protocolConn struct {
net.Conn
request Request
protocolWritten bool
}
func newProtocolConn(conn net.Conn, request Request) net.Conn {
writer, isVectorised := bufio.CreateVectorisedWriter(conn)
if isVectorised {
return &vectorisedProtocolConn{
protocolConn{
Conn: conn,
request: request,
},
writer,
}
} else {
return &protocolConn{
Conn: conn,
request: request,
}
}
}
func (c *protocolConn) Write(p []byte) (n int, err error) {
if c.protocolWritten {
return c.Conn.Write(p)
}
buffer := EncodeRequest(c.request, p)
n, err = c.Conn.Write(buffer.Bytes())
buffer.Release()
if err == nil {
n--
}
c.protocolWritten = true
return n, err
}
func (c *protocolConn) ReadFrom(r io.Reader) (n int64, err error) {
if !c.protocolWritten {
return bufio.ReadFrom0(c, r)
}
return bufio.Copy(c.Conn, r)
}
func (c *protocolConn) Upstream() any {
return c.Conn
}
type vectorisedProtocolConn struct {
protocolConn
writer N.VectorisedWriter
}
func (c *vectorisedProtocolConn) WriteVectorised(buffers []*buf.Buffer) error {
if c.protocolWritten {
return c.writer.WriteVectorised(buffers)
}
c.protocolWritten = true
buffer := EncodeRequest(c.request, nil)
return c.writer.WriteVectorised(append([]*buf.Buffer{buffer}, buffers...))
}

View File

@@ -111,6 +111,16 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
if err != nil {
return nil, err
}
if len(uConfig.NextProtos) > 0 {
for _, extension := range uConn.Extensions {
if alpnExtension, isALPN := extension.(*utls.ALPNExtension); isALPN {
alpnExtension.AlpnProtocols = uConfig.NextProtos
break
}
}
}
hello := uConn.HandshakeState.Hello
hello.SessionId = make([]byte, 32)
copy(hello.Raw[39:], hello.SessionId)

View File

@@ -3,6 +3,7 @@
package tls
import (
"context"
"crypto/tls"
"crypto/x509"
"math/rand"
@@ -47,7 +48,7 @@ func (e *UTLSClientConfig) Config() (*STDConfig, error) {
}
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, nil
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, e.config.NextProtos}, nil
}
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
@@ -87,6 +88,31 @@ func (c *utlsConnWrapper) Upstream() any {
return c.UConn
}
type utlsALPNWrapper struct {
utlsConnWrapper
nextProtocols []string
}
func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error {
if len(c.nextProtocols) > 0 {
err := c.BuildHandshakeState()
if err != nil {
return err
}
for _, extension := range c.Extensions {
if alpnExtension, isALPN := extension.(*utls.ALPNExtension); isALPN {
alpnExtension.AlpnProtocols = c.nextProtocols
err = c.BuildHandshakeState()
if err != nil {
return err
}
break
}
}
}
return c.UConn.HandshakeContext(ctx)
}
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
var serverName string
if options.ServerName != "" {

View File

@@ -1,3 +1,43 @@
#### 1.3-beta13
* Fix resolving fakeip domains **1**
* Deprecate L3 routing
* Fix bugs and update dependencies
**1**:
If the destination address of the connection is obtained from fakeip, dns rules with server type fakeip will be skipped.
#### 1.3-beta12
* Automatically add Windows firewall rules in order for the system tun stack to work
* Fix TLS 1.2 support for shadow-tls client
* Add `cache_id` [option](/configuration/experimental#cache_id) for Clash cache file
* Fixes and improvements
#### 1.3-beta11
* Fix bugs and update dependencies
#### 1.3-beta10
* Improve direct copy **1**
* Improve DNS caching
* Add `independent_cache` [option](/configuration/dns#independent_cache) for DNS
* Reimplemented shadowsocks client **2**
* Add multiplex support for VLESS outbound
* Set TCP keepalive for WireGuard gVisor TCP connections
* Fixes and improvements
**1**:
* Make splice work with traffic statistics systems like Clash API
* Significantly reduces memory usage of idle connections
**2**:
Improved performance and reduced memory usage.
#### 1.3-beta9
* Improve multiplex **1**

View File

@@ -11,6 +11,7 @@
"strategy": "",
"disable_cache": false,
"disable_expire": false,
"independent_cache": false,
"reverse_mapping": false,
"fakeip": {}
}
@@ -48,6 +49,10 @@ Disable dns cache.
Disable dns cache expire.
#### independent_cache
Make each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance.
#### reverse_mapping
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.

View File

@@ -11,6 +11,7 @@
"strategy": "",
"disable_cache": false,
"disable_expire": false,
"independent_cache": false,
"reverse_mapping": false,
"fakeip": {}
}
@@ -47,6 +48,10 @@
禁用 DNS 缓存过期。
#### independent_cache
使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。
#### reverse_mapping
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。

View File

@@ -30,18 +30,18 @@ The tag of the dns server.
The address of the dns server.
| Protocol | Format |
|---------------------|-------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
| [FakeIP](./fakeip) | `fakeip` |
| Protocol | Format |
|-------------------------------------|-------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
| [FakeIP](/configuration/dns/fakeip) | `fakeip` |
!!! warning ""
@@ -94,4 +94,4 @@ Take no effect if override by other settings.
Tag of an outbound for connecting to the dns server.
Default outbound will be used if empty.
Default outbound will be used if empty.

View File

@@ -30,18 +30,18 @@ DNS 服务器的标签。
DNS 服务器的地址。
| 协议 | 格式 |
|--------------------|------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto``dhcp://en0` |
| [FakeIP](./fakeip) | `fakeip` |
| 协议 | 格式 |
|-------------------------------------|------------------------------|
| `System` | `local` |
| `TCP` | `tcp://1.0.0.1` |
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
| `TLS` | `tls://dns.google` |
| `HTTPS` | `https://1.1.1.1/dns-query` |
| `QUIC` | `quic://dns.adguard.com` |
| `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto``dhcp://en0` |
| [FakeIP](/configuration/dns/fakeip) | `fakeip` |
!!! warning ""

View File

@@ -7,13 +7,14 @@
"experimental": {
"clash_api": {
"external_controller": "127.0.0.1:9090",
"external_ui": "folder",
"external_ui": "",
"external_ui_download_url": "",
"external_ui_download_detour": "",
"secret": "",
"default_mode": "rule",
"default_mode": "",
"store_selected": false,
"cache_file": "cache.db"
"cache_file": "",
"cache_id": ""
},
"v2ray_api": {
"listen": "127.0.0.1:8080",
@@ -91,6 +92,12 @@ Store selected outbound for the `Selector` outbound in cache file.
Cache file path, `cache.db` will be used if empty.
#### cache_id
Cache ID.
If not empty, `store_selected` will use a separate store keyed by it.
### V2Ray API Fields
!!! error ""

View File

@@ -7,13 +7,14 @@
"experimental": {
"clash_api": {
"external_controller": "127.0.0.1:9090",
"external_ui": "folder",
"external_ui": "",
"external_ui_download_url": "",
"external_ui_download_detour": "",
"secret": "",
"default_mode": "rule",
"default_mode": "",
"store_selected": false,
"cache_file": "cache.db"
"cache_file": "",
"cache_id": ""
},
"v2ray_api": {
"listen": "127.0.0.1:8080",
@@ -89,6 +90,12 @@ Clash 中的默认模式,默认使用 `rule`。
缓存文件路径,默认使用`cache.db`
#### cache_id
缓存 ID。
如果不为空,`store_selected` 将会使用以此为键的独立存储。
### V2Ray API 字段
!!! error ""

View File

@@ -7,7 +7,6 @@
"route": {
"geoip": {},
"geosite": {},
"ip_rules": [],
"rules": [],
"final": "",
"auto_detect_interface": false,
@@ -24,7 +23,6 @@
|------------|------------------------------------|
| `geoip` | [GeoIP](./geoip) |
| `geosite` | [Geosite](./geosite) |
| `ip_rules` | List of [IP Route Rule](./ip-rule) |
| `rules` | List of [Route Rule](./rule) |
#### final

View File

@@ -31,7 +31,7 @@ Multiplex protocol.
| yamux | https://github.com/hashicorp/yamux |
| h2mux | https://golang.org/x/net/http2 |
SMux is used by default.
h2mux is used by default.
#### max_connections

View File

@@ -30,7 +30,7 @@
| yamux | https://github.com/hashicorp/yamux |
| h2mux | https://golang.org/x/net/http2 |
默认使用 SMux。
默认使用 h2mux。
#### max_connections

106
docs/examples/fakeip.md Normal file
View File

@@ -0,0 +1,106 @@
```json
{
"dns": {
"servers": [
{
"tag": "google",
"address": "tls://8.8.8.8"
},
{
"tag": "local",
"address": "223.5.5.5",
"detour": "direct"
},
{
"tag": "remote",
"address": "fakeip"
},
{
"tag": "block",
"address": "rcode://success"
}
],
"rules": [
{
"geosite": "category-ads-all",
"server": "block",
"disable_cache": true
},
{
"outbound": "any",
"server": "local"
},
{
"geosite": "cn",
"server": "local"
},
{
"query_type": [
"A",
"AAAA"
],
"server": "remote"
}
],
"fakeip": {
"enabled": true,
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18"
},
"independent_cache": true,
"strategy": "ipv4_only"
},
"inbounds": [
{
"type": "tun",
"inet4_address": "172.19.0.1/30",
"auto_route": true,
"sniff": true,
"domain_strategy": "ipv4_only" // remove this line if you want to resolve the domain remotely (if the server is not sing-box, UDP may not work due to wrong behavior).
}
],
"outbounds": [
{
"type": "shadowsocks",
"tag": "proxy",
"server": "mydomain.com",
"server_port": 8080,
"method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg=="
},
{
"type": "direct",
"tag": "direct"
},
{
"type": "block",
"tag": "block"
},
{
"type": "dns",
"tag": "dns-out"
}
],
"route": {
"rules": [
{
"protocol": "dns",
"outbound": "dns-out"
},
{
"geosite": "cn",
"geoip": [
"private",
"cn"
],
"outbound": "direct"
},
{
"geosite": "category-ads-all",
"outbound": "block"
}
],
"auto_detect_interface": true
}
}
```

106
docs/examples/fakeip.zh.md Normal file
View File

@@ -0,0 +1,106 @@
```json
{
"dns": {
"servers": [
{
"tag": "google",
"address": "tls://8.8.8.8"
},
{
"tag": "local",
"address": "223.5.5.5",
"detour": "direct"
},
{
"tag": "remote",
"address": "fakeip"
},
{
"tag": "block",
"address": "rcode://success"
}
],
"rules": [
{
"geosite": "category-ads-all",
"server": "block",
"disable_cache": true
},
{
"outbound": "any",
"server": "local"
},
{
"geosite": "cn",
"server": "local"
},
{
"query_type": [
"A",
"AAAA"
],
"server": "remote"
}
],
"fakeip": {
"enabled": true,
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18"
},
"independent_cache": true,
"strategy": "ipv4_only"
},
"inbounds": [
{
"type": "tun",
"inet4_address": "172.19.0.1/30",
"auto_route": true,
"sniff": true,
"domain_strategy": "ipv4_only" // 如果您想在远程解析域,删除此行 (如果服务器程序不为 sing-box可能由于错误的行为导致 UDP 无法使用)。
}
],
"outbounds": [
{
"type": "shadowsocks",
"tag": "proxy",
"server": "mydomain.com",
"server_port": 8080,
"method": "2022-blake3-aes-128-gcm",
"password": "8JCsPssfgS8tiRwiMlhARg=="
},
{
"type": "direct",
"tag": "direct"
},
{
"type": "block",
"tag": "block"
},
{
"type": "dns",
"tag": "dns-out"
}
],
"route": {
"rules": [
{
"protocol": "dns",
"outbound": "dns-out"
},
{
"geosite": "cn",
"geoip": [
"private",
"cn"
],
"outbound": "direct"
},
{
"geosite": "category-ads-all",
"outbound": "block"
}
],
"auto_detect_interface": true
}
}
```

View File

@@ -9,3 +9,4 @@ Configuration examples for sing-box.
* [ShadowTLS](./shadowtls)
* [Clash API](./clash-api)
* [WireGuard Direct](./wireguard-direct)
* [FakeIP](./fakeip)

View File

@@ -9,3 +9,4 @@ sing-box 的配置示例。
* [ShadowTLS](./shadowtls)
* [Clash API](./clash-api)
* [WireGuard Direct](./wireguard-direct)
* [FakeIP](./fakeip)

View File

@@ -5,8 +5,7 @@ responds to DNS requests with virtual results and restores mapping when acceptin
#### Advantage
* Retrieve the requested domain in places like IP routing (L3) where traffic detection is not possible to assist with routing.
* Decrease an RTT on the first TCP request to a domain (the most common reason).
*
#### Limitation
@@ -15,6 +14,6 @@ responds to DNS requests with virtual results and restores mapping when acceptin
#### Recommendation
* Do not use if you do not need L3 routing.
* Enable `dns.independent_cache` unless you always resolve FakeIP domains remotely.
* If using tun, make sure FakeIP ranges is included in the tun's routes.
* Enable `experimental.clash_api.store_fakeip` to persist FakeIP records, or use `dns.rules.rewrite_ttl` to avoid losing records after program restart in DNS cached environments.

View File

@@ -4,8 +4,7 @@ FakeIP 是指同时劫持 DNS 和连接请求的程序中的一种行为。它
#### 优点
* 在像 L3 路由这样无法进行流量探测的地方检索所请求的域名,以协助路由。
* 减少对一个域的第一个 TCP 请求的 RTT这是最常见的原因
*
#### 限制
@@ -14,6 +13,6 @@ FakeIP 是指同时劫持 DNS 和连接请求的程序中的一种行为。它
#### 建议
* 如果不需要 L3 路由,请勿使用
* 启用 `dns.independent_cache` 除非您始终远程解析 FakeIP 域
* 如果使用 tun请确保 tun 路由中包含 FakeIP 地址范围。
* 启用 `experimental.clash_api.store_fakeip` 以持久化 FakeIP 记录,或者使用 `dns.rules.rewrite_ttl` 避免程序重启后在 DNS 被缓存的环境中丢失记录。

View File

@@ -13,7 +13,7 @@ Experimental iOS client for sing-box.
#### Note
* User Agent in remote profile request is `SFA/$version ($version_code; sing-box $sing_box_version)`
* User Agent in remote profile request is `SFI/$version ($version_code; sing-box $sing_box_version)`
* Crash logs is located in `Settings` -> `View Service Log`
#### Privacy policy

View File

@@ -14,10 +14,11 @@ var bucketSelected = []byte("selected")
var _ adapter.ClashCacheFile = (*CacheFile)(nil)
type CacheFile struct {
DB *bbolt.DB
DB *bbolt.DB
cacheID []byte
}
func Open(path string) (*CacheFile, error) {
func Open(path string, cacheID string) (*CacheFile, error) {
const fileMode = 0o666
options := bbolt.Options{Timeout: time.Second}
db, err := bbolt.Open(path, fileMode, &options)
@@ -31,13 +32,39 @@ func Open(path string) (*CacheFile, error) {
if err != nil {
return nil, err
}
return &CacheFile{db}, nil
var cacheIDBytes []byte
if cacheID != "" {
cacheIDBytes = append([]byte{0}, []byte(cacheID)...)
}
return &CacheFile{db, cacheIDBytes}, nil
}
func (c *CacheFile) bucket(t *bbolt.Tx, key []byte) *bbolt.Bucket {
if c.cacheID == nil {
return t.Bucket(key)
}
bucket := t.Bucket(c.cacheID)
if bucket == nil {
return nil
}
return bucket.Bucket(key)
}
func (c *CacheFile) createBucket(t *bbolt.Tx, key []byte) (*bbolt.Bucket, error) {
if c.cacheID == nil {
return t.CreateBucketIfNotExists(key)
}
bucket, err := t.CreateBucketIfNotExists(c.cacheID)
if bucket == nil {
return nil, err
}
return bucket.CreateBucketIfNotExists(key)
}
func (c *CacheFile) LoadSelected(group string) string {
var selected string
c.DB.View(func(t *bbolt.Tx) error {
bucket := t.Bucket(bucketSelected)
bucket := c.bucket(t, bucketSelected)
if bucket == nil {
return nil
}
@@ -52,7 +79,7 @@ func (c *CacheFile) LoadSelected(group string) string {
func (c *CacheFile) StoreSelected(group, selected string) error {
return c.DB.Batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketSelected)
bucket, err := c.createBucket(t, bucketSelected)
if err != nil {
return err
}

View File

@@ -6,6 +6,7 @@ import (
"strconv"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/json"
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
"github.com/sagernet/websocket"
@@ -14,10 +15,10 @@ import (
"github.com/go-chi/render"
)
func connectionRouter(trafficManager *trafficontrol.Manager) http.Handler {
func connectionRouter(router adapter.Router, trafficManager *trafficontrol.Manager) http.Handler {
r := chi.NewRouter()
r.Get("/", getConnections(trafficManager))
r.Delete("/", closeAllConnections(trafficManager))
r.Delete("/", closeAllConnections(router, trafficManager))
r.Delete("/{id}", closeConnection(trafficManager))
return r
}
@@ -86,12 +87,13 @@ func closeConnection(trafficManager *trafficontrol.Manager) func(w http.Response
}
}
func closeAllConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
func closeAllConnections(router adapter.Router, trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
snapshot := trafficManager.Snapshot()
for _, c := range snapshot.Connections {
c.Close()
}
router.ResetNetwork()
render.NoContent(w, r)
}
}

View File

@@ -134,7 +134,7 @@ func getProxies(server *Server, router adapter.Router) func(w http.ResponseWrite
defaultTag = allProxies[0]
}
sort.Slice(allProxies, func(i, j int) bool {
sort.SliceStable(allProxies, func(i, j int) bool {
return allProxies[i] == defaultTag
})

View File

@@ -48,6 +48,7 @@ type Server struct {
storeSelected bool
storeFakeIP bool
cacheFilePath string
cacheID string
cacheFile adapter.ClashCacheFile
externalUI string
@@ -88,6 +89,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
cachePath = filemanager.BasePath(ctx, cachePath)
}
server.cacheFilePath = cachePath
server.cacheID = options.CacheID
}
cors := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
@@ -105,7 +107,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
r.Mount("/configs", configRouter(server, logFactory, server.logger))
r.Mount("/proxies", proxyRouter(server, router))
r.Mount("/rules", ruleRouter(router))
r.Mount("/connections", connectionRouter(trafficManager))
r.Mount("/connections", connectionRouter(router, trafficManager))
r.Mount("/providers/proxies", proxyProviderRouter())
r.Mount("/providers/rules", ruleProviderRouter())
r.Mount("/script", scriptRouter())
@@ -130,7 +132,7 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
func (s *Server) PreStart() error {
if s.cacheFilePath != "" {
cacheFile, err := cachefile.Open(s.cacheFilePath)
cacheFile, err := cachefile.Open(s.cacheFilePath, s.cacheID)
if err != nil {
return E.Cause(err, "open cache file")
}

View File

@@ -7,9 +7,9 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/experimental/trackerconn"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network"
"github.com/gofrs/uuid/v5"
@@ -115,13 +115,13 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad
download := new(atomic.Int64)
t := &tcpTracker{
ExtendedConn: trackerconn.NewHook(conn, func(n int64) {
ExtendedConn: bufio.NewCounterConn(conn, []N.CountFunc{func(n int64) {
upload.Add(n)
manager.PushUploaded(n)
}, func(n int64) {
}}, []N.CountFunc{func(n int64) {
download.Add(n)
manager.PushDownloaded(n)
}),
}}),
manager: manager,
trackerInfo: &trackerInfo{
UUID: uuid,
@@ -202,13 +202,13 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route
download := new(atomic.Int64)
ut := &udpTracker{
PacketConn: trackerconn.NewHookPacket(conn, func(n int64) {
PacketConn: bufio.NewCounterPacketConn(conn, []N.CountFunc{func(n int64) {
upload.Add(n)
manager.PushUploaded(n)
}, func(n int64) {
}}, []N.CountFunc{func(n int64) {
download.Add(n)
manager.PushDownloaded(n)
}),
}}),
manager: manager,
trackerInfo: &trackerInfo{
UUID: uuid,

View File

@@ -18,6 +18,7 @@ type PlatformInterface interface {
CloseDefaultInterfaceMonitor(listener InterfaceUpdateListener) error
UsePlatformInterfaceGetter() bool
GetInterfaces() (NetworkInterfaceIterator, error)
UnderNetworkExtension() bool
}
type TunInterface interface {

View File

@@ -22,6 +22,7 @@ type Interface interface {
CreateDefaultInterfaceMonitor(errorHandler E.Handler) tun.DefaultInterfaceMonitor
UsePlatformInterfaceGetter() bool
Interfaces() ([]NetworkInterface, error)
UnderNetworkExtension() bool
process.Searcher
io.Writer
}

View File

@@ -169,3 +169,7 @@ func (w *platformInterfaceWrapper) Interfaces() ([]platform.NetworkInterface, er
}
return interfaces, nil
}
func (w *platformInterfaceWrapper) UnderNetworkExtension() bool {
return w.iif.UnderNetworkExtension()
}

View File

@@ -1,108 +0,0 @@
package trackerconn
import (
"net"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
N "github.com/sagernet/sing/common/network"
)
func New(conn net.Conn, readCounter []*atomic.Int64, writeCounter []*atomic.Int64) *Conn {
return &Conn{bufio.NewExtendedConn(conn), readCounter, writeCounter}
}
func NewHook(conn net.Conn, readCounter func(n int64), writeCounter func(n int64)) *HookConn {
return &HookConn{bufio.NewExtendedConn(conn), readCounter, writeCounter}
}
type Conn struct {
N.ExtendedConn
readCounter []*atomic.Int64
writeCounter []*atomic.Int64
}
func (c *Conn) Read(p []byte) (n int, err error) {
n, err = c.ExtendedConn.Read(p)
for _, counter := range c.readCounter {
counter.Add(int64(n))
}
return n, err
}
func (c *Conn) ReadBuffer(buffer *buf.Buffer) error {
err := c.ExtendedConn.ReadBuffer(buffer)
if err != nil {
return err
}
for _, counter := range c.readCounter {
counter.Add(int64(buffer.Len()))
}
return nil
}
func (c *Conn) Write(p []byte) (n int, err error) {
n, err = c.ExtendedConn.Write(p)
for _, counter := range c.writeCounter {
counter.Add(int64(n))
}
return n, err
}
func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
dataLen := int64(buffer.Len())
err := c.ExtendedConn.WriteBuffer(buffer)
if err != nil {
return err
}
for _, counter := range c.writeCounter {
counter.Add(dataLen)
}
return nil
}
func (c *Conn) Upstream() any {
return c.ExtendedConn
}
type HookConn struct {
N.ExtendedConn
readCounter func(n int64)
writeCounter func(n int64)
}
func (c *HookConn) Read(p []byte) (n int, err error) {
n, err = c.ExtendedConn.Read(p)
c.readCounter(int64(n))
return n, err
}
func (c *HookConn) ReadBuffer(buffer *buf.Buffer) error {
err := c.ExtendedConn.ReadBuffer(buffer)
if err != nil {
return err
}
c.readCounter(int64(buffer.Len()))
return nil
}
func (c *HookConn) Write(p []byte) (n int, err error) {
n, err = c.ExtendedConn.Write(p)
c.writeCounter(int64(n))
return n, err
}
func (c *HookConn) WriteBuffer(buffer *buf.Buffer) error {
dataLen := int64(buffer.Len())
err := c.ExtendedConn.WriteBuffer(buffer)
if err != nil {
return err
}
c.writeCounter(dataLen)
return nil
}
func (c *HookConn) Upstream() any {
return c.ExtendedConn
}

View File

@@ -1,76 +0,0 @@
package trackerconn
import (
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func NewPacket(conn N.PacketConn, readCounter []*atomic.Int64, writeCounter []*atomic.Int64) *PacketConn {
return &PacketConn{conn, readCounter, writeCounter}
}
func NewHookPacket(conn N.PacketConn, readCounter func(n int64), writeCounter func(n int64)) *HookPacketConn {
return &HookPacketConn{conn, readCounter, writeCounter}
}
type PacketConn struct {
N.PacketConn
readCounter []*atomic.Int64
writeCounter []*atomic.Int64
}
func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
destination, err = c.PacketConn.ReadPacket(buffer)
if err == nil {
for _, counter := range c.readCounter {
counter.Add(int64(buffer.Len()))
}
}
return
}
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
dataLen := int64(buffer.Len())
err := c.PacketConn.WritePacket(buffer, destination)
if err != nil {
return err
}
for _, counter := range c.writeCounter {
counter.Add(dataLen)
}
return nil
}
func (c *PacketConn) Upstream() any {
return c.PacketConn
}
type HookPacketConn struct {
N.PacketConn
readCounter func(n int64)
writeCounter func(n int64)
}
func (c *HookPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
destination, err = c.PacketConn.ReadPacket(buffer)
if err == nil {
c.readCounter(int64(buffer.Len()))
}
return
}
func (c *HookPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
dataLen := int64(buffer.Len())
err := c.PacketConn.WritePacket(buffer, destination)
if err != nil {
return err
}
c.writeCounter(dataLen)
return nil
}
func (c *HookPacketConn) Upstream() any {
return c.PacketConn
}

View File

@@ -10,9 +10,9 @@ import (
"time"
"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"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
)
@@ -83,7 +83,7 @@ func (s *StatsService) RoutedConnection(inbound string, outbound string, user st
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
}
s.access.Unlock()
return trackerconn.New(conn, readCounter, writeCounter)
return bufio.NewInt64CounterConn(conn, readCounter, writeCounter)
}
func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn {
@@ -109,7 +109,7 @@ func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, u
writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink"))
}
s.access.Unlock()
return trackerconn.NewPacket(conn, readCounter, writeCounter)
return bufio.NewInt64CounterPacketConn(conn, readCounter, writeCounter)
}
func (s *StatsService) GetStats(ctx context.Context, request *GetStatsRequest) (*GetStatsResponse, error) {

64
go.mod
View File

@@ -4,8 +4,8 @@ go 1.18
require (
berty.tech/go-libtor v1.0.385
github.com/Dreamacro/clash v1.15.0
github.com/caddyserver/certmagic v0.17.2
github.com/Dreamacro/clash v1.16.0
github.com/caddyserver/certmagic v0.18.0
github.com/cretz/bine v0.2.0
github.com/dustin/go-humanize v1.0.1
github.com/fsnotify/fsnotify v1.6.0
@@ -13,23 +13,25 @@ require (
github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.2
github.com/gofrs/uuid/v5 v5.0.0
github.com/hashicorp/yamux v0.1.1
github.com/insomniacslk/dhcp v0.0.0-20230407062729-974c6f05fe16
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/mholt/acmez v1.1.0
github.com/miekg/dns v1.1.53
github.com/ooni/go-libtor v1.1.7
github.com/mholt/acmez v1.1.1
github.com/miekg/dns v1.1.54
github.com/ooni/go-libtor v1.1.8
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-20230413023804-244d7ff07035
github.com/sagernet/gvisor v0.0.0-20230611140528-4411f7659a08
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.2.4
github.com/sagernet/sing-dns v0.1.5-0.20230415085626-111ecf799dfc
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b
github.com/sagernet/sing-tun v0.1.5-0.20230422121432-209ec123ca7b
github.com/sagernet/sing v0.2.5-0.20230611070640-2812461739c3
github.com/sagernet/sing-dns v0.1.5-0.20230426113254-25d948c44223
github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230509053848-d83f8fe1194c
github.com/sagernet/sing-shadowsocks2 v0.0.0-20230608022204-942fbd98f846
github.com/sagernet/sing-shadowtls v0.1.2-0.20230531025805-ebadc7615da3
github.com/sagernet/sing-tun v0.1.5-0.20230611140633-41b2639e1385
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
@@ -37,40 +39,43 @@ require (
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.2
github.com/stretchr/testify v1.8.4
go.etcd.io/bbolt v1.3.7
go.uber.org/zap v1.24.0
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
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.54.0
golang.org/x/crypto v0.9.0
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/net v0.10.0
golang.org/x/sys v0.8.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
google.golang.org/grpc v1.55.0
google.golang.org/protobuf v1.30.0
gvisor.dev/gvisor v0.0.0-20230415003630-3981d5d5e523
)
//replace github.com/sagernet/sing => ../sing
require (
github.com/Dreamacro/protobytes v0.0.0-20230324064118-87bc784139cd // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/cloudflare/circl v1.2.1-0.20221019164342-6ab4dfed8f3c // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.15.15 // indirect
github.com/klauspost/cpuid/v2 v2.1.1 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/libdns/libdns v0.2.1 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.14 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
@@ -78,16 +83,17 @@ require (
github.com/quic-go/qtls-go1-20 v0.1.0 // indirect
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/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
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
golang.org/x/tools v0.6.0 // indirect
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.8.0 // indirect
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.1.7 // indirect

133
go.sum
View File

@@ -1,14 +1,16 @@
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.15.0 h1:mlpD950VEggXZBNahV66hyKDRxcczkj3vymoAt78KyE=
github.com/Dreamacro/clash v1.15.0/go.mod h1:WNH69bN11LiAdgdSr4hpkEuXVMfBbWyhEKMCTx9BtNE=
github.com/Dreamacro/clash v1.16.0 h1:Ve+8mvtsCv7ySa6f53/GOW6Tho0AFp5RNFsgQZFKcZs=
github.com/Dreamacro/clash v1.16.0/go.mod h1:ZM3UI2gqqUN7UL7L/F9aTHODTByya6sbC/WivQpaoJk=
github.com/Dreamacro/protobytes v0.0.0-20230324064118-87bc784139cd h1:ygk7IF14j4ep4H2ZyeDe3IEoMZF8JdbX851RVVa/4D8=
github.com/Dreamacro/protobytes v0.0.0-20230324064118-87bc784139cd/go.mod h1:QvmEZ/h6KXszPOr2wUFl7Zn3hfFNYdfbXwPVDTyZs6k=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE=
github.com/caddyserver/certmagic v0.17.2/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE=
github.com/caddyserver/certmagic v0.18.0 h1:L22mJES1WllfLoHUcQUy4wVO7UfOsoL5wtg/Bj7kmIw=
github.com/caddyserver/certmagic v0.18.0/go.mod h1:e0YLTnXIopZ05bBWCLzpIf1Yvk27Q90FGUmGowFRDY8=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -31,6 +33,8 @@ github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
@@ -38,10 +42,10 @@ github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
@@ -51,16 +55,16 @@ github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbg
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/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/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb h1:6fDKEAXwe3rsfS4khW3EZ8kEqmSiV9szhMPcDrD+Y7Q=
github.com/insomniacslk/dhcp v0.0.0-20230516061539-49801966e6cb/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4=
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0=
github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -68,17 +72,17 @@ github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
github.com/libdns/libdns v0.2.1/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
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.53 h1:ZBkuHr5dxHtB1caEOlZTLPo7D3L3TWckgUUs/RHfDxw=
github.com/miekg/dns v1.1.53/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
github.com/mholt/acmez v1.1.1 h1:sYeeYd/EHVm9cSmLdWey5oW/fXFVAq5pNLjSczN2ZUg=
github.com/mholt/acmez v1.1.1/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
github.com/miekg/dns v1.1.54 h1:5jon9mWcb0sFJGpnI99tOMhCPyJ+RPVz5b63MQG0VWI=
github.com/miekg/dns v1.1.54/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=
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
github.com/ooni/go-libtor v1.1.7 h1:ooVcdEPBqDox5OfeXAfXIeQFCbqMLJVfIpO+Irr7N9A=
github.com/ooni/go-libtor v1.1.7/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI=
github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w=
github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI=
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
@@ -86,6 +90,7 @@ github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
@@ -103,6 +108,8 @@ github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
github.com/sagernet/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/gvisor v0.0.0-20230611140528-4411f7659a08 h1:p1z8y0tXLCKSiJ7GbUlaYPhyEbWL8LKLMYFpVxRVsBg=
github.com/sagernet/gvisor v0.0.0-20230611140528-4411f7659a08/go.mod h1:FgbjODax/nj7J2lr7+rqe88vHs0Ts93pC9na5ZiG9wg=
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=
@@ -111,16 +118,20 @@ github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byL
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.4 h1:gC8BR5sglbJZX23RtMyFa8EETP9YEUADhfbEzU1yVbo=
github.com/sagernet/sing v0.2.4/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.2-0.20230417102954-f77257340507 h1:bAHZCdWqJkb8LEW98+YsMVDXGRMUVjka8IC+St6ot88=
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230417102954-f77257340507/go.mod h1:UJjvQGw0lyYaDGIDvUraL16fwaAEH1WFw1Y6sUcMPog=
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b h1:ouW/6IDCrxkBe19YSbdCd7buHix7b+UZ6BM4Zz74XF4=
github.com/sagernet/sing-shadowtls v0.1.2-0.20230417103049-4f682e05f19b/go.mod h1:oG8bPerYI6cZ74KquY3DvA7ynECyrILPBnce6wtBqeI=
github.com/sagernet/sing-tun v0.1.5-0.20230422121432-209ec123ca7b h1:9NsciSJGwzdkXwVvT2c2g+RvkTVkANeBLr2l+soJ7LM=
github.com/sagernet/sing-tun v0.1.5-0.20230422121432-209ec123ca7b/go.mod h1:DD7Ce2Gt0GFc6I/1+Uw4D/aUlBsGqrQsC52CMK/V818=
github.com/sagernet/sing v0.2.5-0.20230611070640-2812461739c3 h1:OnlfGM8HncECbFiV4s6uAX961fm2UpEKSCXYBLZ1Chg=
github.com/sagernet/sing v0.2.5-0.20230611070640-2812461739c3/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w=
github.com/sagernet/sing-dns v0.1.5-0.20230426113254-25d948c44223 h1:L4eMuM07iSHY3UCknFnuFuHoe5clZuF2Xnf2wwA6Lwc=
github.com/sagernet/sing-dns v0.1.5-0.20230426113254-25d948c44223/go.mod h1:ZKuuqgsHRxDahYrzgSgy4vIAGGuKPlIf4hLcNzYzLkY=
github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646 h1:X3ADfMqeGns1Q1FlXc9kaL9FwW1UM6D6tEQo8jFstpc=
github.com/sagernet/sing-mux v0.0.0-20230517134606-1ebe6bb26646/go.mod h1:pF+RnLvCAOhECrvauy6LYOpBakJ/vuaF1Wm4lPsWryI=
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230509053848-d83f8fe1194c h1:EiQ+i4gdPpSI8D2YUlOeBZA3R1ZGi0ShSLSXoSd/13A=
github.com/sagernet/sing-shadowsocks v0.2.2-0.20230509053848-d83f8fe1194c/go.mod h1:UJjvQGw0lyYaDGIDvUraL16fwaAEH1WFw1Y6sUcMPog=
github.com/sagernet/sing-shadowsocks2 v0.0.0-20230608022204-942fbd98f846 h1:aiEEbDyPS/IFx0V9RNa1yLdxHHegNP+q7JFpST/8D5I=
github.com/sagernet/sing-shadowsocks2 v0.0.0-20230608022204-942fbd98f846/go.mod h1:i6Eoor37Cy4JKBcelMMFfUVBNjqRYuvcqOBjUI3Zw80=
github.com/sagernet/sing-shadowtls v0.1.2-0.20230531025805-ebadc7615da3 h1:PNwJs1F+3e/iZguYQR7YzxsH8Sm0Eu7vVuHawD89r34=
github.com/sagernet/sing-shadowtls v0.1.2-0.20230531025805-ebadc7615da3/go.mod h1:oG8bPerYI6cZ74KquY3DvA7ynECyrILPBnce6wtBqeI=
github.com/sagernet/sing-tun v0.1.5-0.20230611140633-41b2639e1385 h1:iKDIJB05yHyUE57VYlamGhCEauXssIukB/xZsLmP7ic=
github.com/sagernet/sing-tun v0.1.5-0.20230611140633-41b2639e1385/go.mod h1:8amChfsau8s/hkooIVIU4uw7VavgrfHrT0KUbJo4ROc=
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3 h1:BHOnxrbC929JonuKqFdJ7ZbDp7zs4oTlH5KFvKtWu9U=
github.com/sagernet/sing-vmess v0.1.5-0.20230417103030-8c3070ae3fb3/go.mod h1:yKrAr+dqZd64DxBXCHWrYicp+n4qbqO73mtwv3dck8U=
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
@@ -133,20 +144,18 @@ 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-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo=
github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg=
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/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=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA=
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
@@ -154,12 +163,11 @@ github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 h1:nJAwRlGWZZDOD+6wni9KVUNHMpHko/OnRwsrCYeAzPo=
@@ -168,26 +176,27 @@ 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.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/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
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=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
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/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -199,32 +208,32 @@ 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.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
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.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-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde h1:ybF7AMzIUikL9x4LgwEmzhXtzRpKNqngme1VGDWz+Nk=
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.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
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=
@@ -236,7 +245,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20230415003630-3981d5d5e523 h1:zUQYeyyPLnSR6yMvLSOmLH37xDWCZ7BqlpE69fE5K3Q=
gvisor.dev/gvisor v0.0.0-20230415003630-3981d5d5e523/go.mod h1:pzr6sy8gDLfVmDAg8OYrlKvGEHw5C3PGTiBXBTCx76Q=
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=

View File

@@ -19,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
@@ -161,10 +158,6 @@ func (t *Tun) Start() error {
}
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,
@@ -174,7 +167,6 @@ func (t *Tun) Start() error {
Inet6Address: t.tunOptions.Inet6Address,
EndpointIndependentNat: t.endpointIndependentNat,
UDPTimeout: t.udpTimeout,
Router: tunRouter,
Handler: t,
Logger: t.logger,
ForwarderBindInterface: t.platformInterface != nil,
@@ -199,21 +191,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

View File

@@ -55,7 +55,6 @@ nav:
- configuration/route/index.md
- GeoIP: configuration/route/geoip.md
- Geosite: configuration/route/geosite.md
- IP Route Rule: configuration/route/ip-rule.md
- Route Rule: configuration/route/rule.md
- Protocol Sniff: configuration/route/sniff.md
- Experimental:
@@ -115,6 +114,7 @@ nav:
- ShadowTLS: examples/shadowtls.md
- Clash API: examples/clash-api.md
- WireGuard Direct: examples/wireguard-direct.md
- FakeIP: examples/fakeip.md
- Contributing:
- contributing/index.md
- Developing:
@@ -147,6 +147,7 @@ extra:
link: https://github.com/SagerNet/sing-box
generator: false
plugins:
- search
- i18n:
default_language: en
languages:
@@ -173,7 +174,6 @@ plugins:
DNS Rule: DNS 规则
Route: 路由
IP Route Rule: IP 路由规则
Route Rule: 路由规则
Protocol Sniff: 协议探测
@@ -192,4 +192,4 @@ plugins:
Known Issues: 已知问题
Examples: 示例
Linux Server Installation: Linux 服务器安装
DNS Hijack: DNS 劫持
DNS Hijack: DNS 劫持

View File

@@ -10,6 +10,7 @@ type ClashAPIOptions struct {
StoreSelected bool `json:"store_selected,omitempty"`
StoreFakeIP bool `json:"store_fakeip,omitempty"`
CacheFile string `json:"cache_file,omitempty"`
CacheID string `json:"cache_id,omitempty"`
}
type SelectorOutboundOptions struct {

View File

@@ -20,9 +20,10 @@ type DNSServerOptions struct {
}
type DNSClientOptions struct {
Strategy DomainStrategy `json:"strategy,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
DisableExpire bool `json:"disable_expire,omitempty"`
Strategy DomainStrategy `json:"strategy,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
DisableExpire bool `json:"disable_expire,omitempty"`
IndependentCache bool `json:"independent_cache,omitempty"`
}
type DNSFakeIPOptions struct {

View File

@@ -3,7 +3,6 @@ package option
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"`

View File

@@ -1,120 +0,0 @@
package option
import (
"reflect"
"github.com/sagernet/sing-box/common/json"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type _IPRule struct {
Type string `json:"type,omitempty"`
DefaultOptions DefaultIPRule `json:"-"`
LogicalOptions LogicalIPRule `json:"-"`
}
type IPRule _IPRule
func (r IPRule) MarshalJSON() ([]byte, error) {
var v any
switch r.Type {
case C.RuleTypeDefault:
r.Type = ""
v = r.DefaultOptions
case C.RuleTypeLogical:
v = r.LogicalOptions
default:
return nil, E.New("unknown rule type: " + r.Type)
}
return MarshallObjects((_IPRule)(r), v)
}
func (r *IPRule) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_IPRule)(r))
if err != nil {
return err
}
var v any
switch r.Type {
case "", C.RuleTypeDefault:
r.Type = C.RuleTypeDefault
v = &r.DefaultOptions
case C.RuleTypeLogical:
v = &r.LogicalOptions
default:
return E.New("unknown rule type: " + r.Type)
}
err = UnmarshallExcluded(bytes, (*_IPRule)(r), v)
if err != nil {
return E.Cause(err, "ip route rule")
}
return nil
}
type DefaultIPRule struct {
Inbound Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"`
Network Listable[string] `json:"network,omitempty"`
Domain Listable[string] `json:"domain,omitempty"`
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
Geosite Listable[string] `json:"geosite,omitempty"`
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
GeoIP Listable[string] `json:"geoip,omitempty"`
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
SourcePort Listable[uint16] `json:"source_port,omitempty"`
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
Port Listable[uint16] `json:"port,omitempty"`
PortRange Listable[string] `json:"port_range,omitempty"`
Invert bool `json:"invert,omitempty"`
Action RouteAction `json:"action,omitempty"`
Outbound string `json:"outbound,omitempty"`
}
type RouteAction tun.ActionType
func (a RouteAction) MarshalJSON() ([]byte, error) {
typeName, err := tun.ActionTypeName(tun.ActionType(a))
if err != nil {
return nil, err
}
return json.Marshal(typeName)
}
func (a *RouteAction) UnmarshalJSON(bytes []byte) error {
var value string
err := json.Unmarshal(bytes, &value)
if err != nil {
return err
}
actionType, err := tun.ParseActionType(value)
if err != nil {
return err
}
*a = RouteAction(actionType)
return nil
}
func (r DefaultIPRule) IsValid() bool {
var defaultValue DefaultIPRule
defaultValue.Invert = r.Invert
defaultValue.Outbound = r.Outbound
return !reflect.DeepEqual(r, defaultValue)
}
type LogicalIPRule struct {
Mode string `json:"mode"`
Rules []DefaultIPRule `json:"rules,omitempty"`
Invert bool `json:"invert,omitempty"`
Action RouteAction `json:"action,omitempty"`
Outbound string `json:"outbound,omitempty"`
}
func (r LogicalIPRule) IsValid() bool {
return len(r.Rules) > 0 && common.All(r.Rules, DefaultIPRule.IsValid)
}

View File

@@ -20,6 +20,7 @@ type VLESSOutboundOptions struct {
Flow string `json:"flow,omitempty"`
Network NetworkList `json:"network,omitempty"`
TLS *OutboundTLSOptions `json:"tls,omitempty"`
Multiplex *MultiplexOptions `json:"multiplex,omitempty"`
Transport *V2RayTransportOptions `json:"transport,omitempty"`
PacketEncoding *string `json:"packet_encoding,omitempty"`
}

View File

@@ -14,7 +14,6 @@ type WireGuardOutboundOptions struct {
Workers int `json:"workers,omitempty"`
MTU uint32 `json:"mtu,omitempty"`
Network NetworkList `json:"network,omitempty"`
IPRewrite bool `json:"ip_rewrite,omitempty"`
}
type WireGuardPeer struct {

View File

@@ -11,6 +11,7 @@ import (
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/canceler"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -101,29 +102,142 @@ 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 {
var reader N.PacketReader = conn
var counters []N.CountFunc
var cachedPackets []*N.PacketBuffer
for {
reader, counters = N.UnwrapCountPacketReader(reader, counters)
if cachedReader, isCached := reader.(N.CachedPacketReader); isCached {
packet := cachedReader.ReadCachedPacket()
if packet != nil {
cachedPackets = append(cachedPackets, packet)
continue
}
}
if readWaiter, created := bufio.CreatePacketReadWaiter(reader); created {
return d.newPacketConnection(ctx, conn, readWaiter, counters, cachedPackets, metadata)
}
break
}
ctx = adapter.WithContext(ctx, &metadata)
fastClose, cancel := common.ContextWithCancelCause(ctx)
timeout := canceler.New(fastClose, cancel, C.DNSTimeout)
var group task.Group
group.Append0(func(ctx context.Context) error {
_buffer := buf.StackNewSize(dns.FixedPacketSize)
defer common.KeepAlive(_buffer)
buffer := common.Dup(_buffer)
defer buffer.Release()
for {
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
var destination M.Socksaddr
var err error
if len(cachedPackets) > 0 {
packet := cachedPackets[0]
cachedPackets = cachedPackets[1:]
for _, counter := range counters {
counter(int64(packet.Buffer.Len()))
}
err = message.Unpack(packet.Buffer.Bytes())
packet.Buffer.Release()
if err != nil {
cancel(err)
return err
}
destination = packet.Destination
} else {
buffer := buf.NewPacket()
destination, err = conn.ReadPacket(buffer)
if err != nil {
buffer.Release()
cancel(err)
return err
}
for _, counter := range counters {
counter(int64(buffer.Len()))
}
err = message.Unpack(buffer.Bytes())
buffer.Release()
if err != nil {
cancel(err)
return err
}
timeout.Update()
}
metadataInQuery := metadata
go func() error {
response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message)
if err != nil {
cancel(err)
return err
}
timeout.Update()
responseBuffer := buf.NewPacket()
n, err := response.PackBuffer(responseBuffer.FreeBytes())
if err != nil {
cancel(err)
responseBuffer.Release()
return err
}
responseBuffer.Truncate(len(n))
err = conn.WritePacket(responseBuffer, destination)
if err != nil {
cancel(err)
}
return err
}()
}
})
group.Cleanup(func() {
conn.Close()
})
return group.Run(fastClose)
}
func (d *DNS) newPacketConnection(ctx context.Context, conn N.PacketConn, readWaiter N.PacketReadWaiter, readCounters []N.CountFunc, cached []*N.PacketBuffer, metadata adapter.InboundContext) error {
ctx = adapter.WithContext(ctx, &metadata)
fastClose, cancel := common.ContextWithCancelCause(ctx)
timeout := canceler.New(fastClose, cancel, C.DNSTimeout)
var group task.Group
group.Append0(func(ctx context.Context) error {
var buffer *buf.Buffer
readWaiter.InitializeReadWaiter(func() *buf.Buffer {
buffer = buf.NewSize(dns.FixedPacketSize)
buffer.FullReset()
return buffer
})
defer readWaiter.InitializeReadWaiter(nil)
for {
var message mDNS.Msg
var destination M.Socksaddr
var err error
if len(cached) > 0 {
packet := cached[0]
cached = cached[1:]
for _, counter := range readCounters {
counter(int64(packet.Buffer.Len()))
}
err = message.Unpack(packet.Buffer.Bytes())
packet.Buffer.Release()
if err != nil {
cancel(err)
return err
}
destination = packet.Destination
} else {
destination, err = readWaiter.WaitReadPacket()
if err != nil {
buffer.Release()
cancel(err)
return err
}
for _, counter := range readCounters {
counter(int64(buffer.Len()))
}
err = message.Unpack(buffer.Bytes())
buffer.Release()
if err != nil {
cancel(err)
return err
}
timeout.Update()
}
timeout.Update()
metadataInQuery := metadata
go func() error {
response, err := d.router.Exchange(adapter.WithContext(ctx, &metadataInQuery), &message)

View File

@@ -150,6 +150,7 @@ func (h *Hysteria) offer(ctx context.Context) (quic.Connection, error) {
if conn != nil && !common.Done(conn.Context()) {
return conn, nil
}
common.Close(h.rawConn)
conn, err := h.offerNew(ctx)
if err != nil {
return nil, err
@@ -260,14 +261,18 @@ func (h *Hysteria) Close() error {
return nil
}
func (h *Hysteria) open(ctx context.Context) (quic.Connection, quic.Stream, error) {
func (h *Hysteria) open(ctx context.Context, reconnect bool) (quic.Connection, quic.Stream, error) {
conn, err := h.offer(ctx)
if err != nil {
return nil, nil, err
if nErr, ok := err.(net.Error); ok && !nErr.Temporary() && reconnect {
return h.open(ctx, false)
}
}
stream, err := conn.OpenStream()
if err != nil {
return nil, nil, err
if nErr, ok := err.(net.Error); ok && !nErr.Temporary() && reconnect {
return h.open(ctx, false)
}
}
return conn, &hysteria.StreamWrapper{Stream: stream}, nil
}
@@ -276,7 +281,7 @@ func (h *Hysteria) DialContext(ctx context.Context, network string, destination
switch N.NetworkName(network) {
case N.NetworkTCP:
h.logger.InfoContext(ctx, "outbound connection to ", destination)
_, stream, err := h.open(ctx)
_, stream, err := h.open(ctx, true)
if err != nil {
return nil, err
}
@@ -302,7 +307,7 @@ func (h *Hysteria) DialContext(ctx context.Context, network string, destination
func (h *Hysteria) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
conn, stream, err := h.open(ctx)
conn, stream, err := h.open(ctx, true)
if err != nil {
return nil, err
}

View File

@@ -11,8 +11,7 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/sip003"
"github.com/sagernet/sing-shadowsocks"
"github.com/sagernet/sing-shadowsocks/shadowimpl"
"github.com/sagernet/sing-shadowsocks2"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
@@ -34,7 +33,9 @@ type Shadowsocks struct {
}
func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (*Shadowsocks, error) {
method, err := shadowimpl.FetchMethod(options.Method, options.Password, router.TimeFunc())
method, err := shadowsocks.CreateMethod(ctx, options.Method, shadowsocks.MethodOptions{
Password: options.Password,
})
if err != nil {
return nil, err
}
@@ -58,7 +59,7 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
}
uotOptions := common.PtrValueOrDefault(options.UDPOverTCPOptions)
if !uotOptions.Enabled {
outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions))
outbound.multiplexDialer, err = mux.NewClientWithOptions((*shadowsocksDialer)(outbound), common.PtrValueOrDefault(options.MultiplexOptions))
if err != nil {
return nil, err
}

View File

@@ -58,7 +58,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
return nil, E.Cause(err, "create client transport: ", options.Transport.Type)
}
}
outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*trojanDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
outbound.multiplexDialer, err = mux.NewClientWithOptions((*trojanDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}

View File

@@ -233,7 +233,7 @@ func (g *URLTestGroup) Fallback(used adapter.Outbound) []adapter.Outbound {
outbounds = append(outbounds, detour)
}
}
sort.Slice(outbounds, func(i, j int) bool {
sort.SliceStable(outbounds, func(i, j int) bool {
oi := outbounds[i]
oj := outbounds[j]
hi := g.history.LoadURLTestHistory(RealTag(oi))

View File

@@ -6,6 +6,7 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/mux"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
@@ -24,13 +25,14 @@ var _ adapter.Outbound = (*VLESS)(nil)
type VLESS struct {
myOutboundAdapter
dialer N.Dialer
client *vless.Client
serverAddr M.Socksaddr
tlsConfig tls.Config
transport adapter.V2RayClientTransport
packetAddr bool
xudp bool
dialer N.Dialer
client *vless.Client
serverAddr M.Socksaddr
multiplexDialer *mux.Client
tlsConfig tls.Config
transport adapter.V2RayClientTransport
packetAddr bool
xudp bool
}
func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (*VLESS, error) {
@@ -75,10 +77,65 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
if err != nil {
return nil, err
}
outbound.multiplexDialer, err = mux.NewClientWithOptions((*vlessDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}
return outbound, nil
}
func (h *VLESS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if h.multiplexDialer == nil {
switch N.NetworkName(network) {
case N.NetworkTCP:
h.logger.InfoContext(ctx, "outbound connection to ", destination)
case N.NetworkUDP:
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
}
return (*vlessDialer)(h).DialContext(ctx, network, destination)
} else {
switch N.NetworkName(network) {
case N.NetworkTCP:
h.logger.InfoContext(ctx, "outbound multiplex connection to ", destination)
case N.NetworkUDP:
h.logger.InfoContext(ctx, "outbound multiplex packet connection to ", destination)
}
return h.multiplexDialer.DialContext(ctx, network, destination)
}
}
func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if h.multiplexDialer == nil {
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
return (*vlessDialer)(h).ListenPacket(ctx, destination)
} else {
h.logger.InfoContext(ctx, "outbound multiplex packet connection to ", destination)
return h.multiplexDialer.ListenPacket(ctx, destination)
}
}
func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
return NewConnection(ctx, h, conn, metadata)
}
func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return NewPacketConnection(ctx, h, conn, metadata)
}
func (h *VLESS) InterfaceUpdated() error {
if h.multiplexDialer != nil {
h.multiplexDialer.Reset()
}
return nil
}
func (h *VLESS) Close() error {
return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport)
}
type vlessDialer VLESS
func (h *vlessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
ctx, metadata := adapter.AppendContext(ctx)
metadata.Outbound = h.tag
metadata.Destination = destination
@@ -120,7 +177,7 @@ func (h *VLESS) DialContext(ctx context.Context, network string, destination M.S
}
}
func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
ctx, metadata := adapter.AppendContext(ctx)
metadata.Outbound = h.tag
@@ -154,15 +211,3 @@ func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.
return h.client.DialEarlyPacketConn(conn, destination)
}
}
func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
return NewConnection(ctx, h, conn, metadata)
}
func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return NewPacketConnection(ctx, h, conn, metadata)
}
func (h *VLESS) Close() error {
return common.Close(h.transport)
}

View File

@@ -59,7 +59,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
return nil, E.Cause(err, "create client transport: ", options.Transport.Type)
}
}
outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*vmessDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
outbound.multiplexDialer, err = mux.NewClientWithOptions((*vmessDialer)(outbound), common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}

View File

@@ -8,9 +8,7 @@ import (
"encoding/hex"
"fmt"
"net"
"os"
"strings"
"syscall"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
@@ -28,7 +26,7 @@ import (
)
var (
_ adapter.IPOutbound = (*WireGuard)(nil)
_ adapter.Outbound = (*WireGuard)(nil)
_ adapter.InterfaceUpdateListener = (*WireGuard)(nil)
)
@@ -36,7 +34,6 @@ type WireGuard struct {
myOutboundAdapter
bind *wireguard.ClientBind
device *device.Device
natDevice wireguard.NatDevice
tunDevice wireguard.Device
}
@@ -158,25 +155,17 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
if mtu == 0 {
mtu = 1408
}
var tunDevice wireguard.Device
var wireTunDevice wireguard.Device
var err error
if !options.SystemInterface && tun.WithGVisor {
tunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu, options.IPRewrite)
wireTunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu)
} else {
tunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, localPrefixes, mtu)
wireTunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, localPrefixes, mtu)
}
if err != nil {
return nil, E.Cause(err, "create WireGuard device")
}
natDevice, isNatDevice := tunDevice.(wireguard.NatDevice)
if !isNatDevice && router.NatRequired(tag) {
natDevice = wireguard.NewNATDevice(tunDevice, options.IPRewrite)
}
deviceInput := tunDevice
if natDevice != nil {
deviceInput = natDevice
}
wgDevice := device.NewDevice(deviceInput, outbound.bind, &device.Logger{
wgDevice := device.NewDevice(wireTunDevice, outbound.bind, &device.Logger{
Verbosef: func(format string, args ...interface{}) {
logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
},
@@ -192,8 +181,7 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
return nil, E.Cause(err, "setup wireguard")
}
outbound.device = wgDevice
outbound.natDevice = natDevice
outbound.tunDevice = tunDevice
outbound.tunDevice = wireTunDevice
return outbound, nil
}
@@ -232,27 +220,6 @@ func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn,
return NewPacketConnection(ctx, w, conn, metadata)
}
func (w *WireGuard) NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata adapter.InboundContext) (tun.DirectDestination, error) {
if w.natDevice == nil {
return nil, os.ErrInvalid
}
session := tun.RouteSession{
IPVersion: metadata.IPVersion,
Network: tun.NetworkFromName(metadata.Network),
Source: metadata.Source.AddrPort(),
Destination: metadata.Destination.AddrPort(),
}
switch session.Network {
case syscall.IPPROTO_TCP:
w.logger.InfoContext(ctx, "linked connection to ", metadata.Destination)
case syscall.IPPROTO_UDP:
w.logger.InfoContext(ctx, "linked packet connection to ", metadata.Destination)
default:
w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection to ", metadata.Destination.AddrString())
}
return w.natDevice.CreateDestination(session, conn), nil
}
func (w *WireGuard) Start() error {
return w.tunDevice.Start()
}

View File

@@ -50,7 +50,6 @@ type Router struct {
outbounds []adapter.Outbound
outboundByTag map[string]adapter.Outbound
rules []adapter.Rule
ipRules []adapter.IPRule
defaultDetour string
defaultOutboundForConnection adapter.Outbound
defaultOutboundForPacketConnection adapter.Outbound
@@ -99,7 +98,6 @@ func NewRouter(
dnsLogger: logFactory.NewLogger("dns"),
outboundByTag: make(map[string]adapter.Outbound),
rules: make([]adapter.Rule, 0, len(options.Rules)),
ipRules: make([]adapter.IPRule, 0, len(options.IPRules)),
dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)),
needGeoIPDatabase: hasRule(options.Rules, isGeoIPRule) || hasDNSRule(dnsOptions.Rules, isGeoIPDNSRule),
needGeositeDatabase: hasRule(options.Rules, isGeositeRule) || hasDNSRule(dnsOptions.Rules, isGeositeDNSRule),
@@ -113,7 +111,12 @@ func NewRouter(
defaultMark: options.DefaultMark,
platformInterface: platformInterface,
}
router.dnsClient = dns.NewClient(dnsOptions.DNSClientOptions.DisableCache, dnsOptions.DNSClientOptions.DisableExpire, router.dnsLogger)
router.dnsClient = dns.NewClient(dns.ClientOptions{
DisableCache: dnsOptions.DNSClientOptions.DisableCache,
DisableExpire: dnsOptions.DNSClientOptions.DisableExpire,
IndependentCache: dnsOptions.DNSClientOptions.IndependentCache,
Logger: router.dnsLogger,
})
for i, ruleOptions := range options.Rules {
routeRule, err := NewRule(router, router.logger, ruleOptions)
if err != nil {
@@ -121,13 +124,6 @@ func NewRouter(
}
router.rules = append(router.rules, routeRule)
}
for i, ipRuleOptions := range options.IPRules {
ipRule, err := NewIPRule(router, router.logger, ipRuleOptions)
if err != nil {
return nil, E.Cause(err, "parse ip rule[", i, "]")
}
router.ipRules = append(router.ipRules, ipRule)
}
for i, dnsRuleOptions := range dnsOptions.Rules {
dnsRule, err := NewDNSRule(router, router.logger, dnsRuleOptions)
if err != nil {
@@ -274,7 +270,8 @@ func NewRouter(
router.networkMonitor = networkMonitor
networkMonitor.RegisterCallback(router.interfaceFinder.update)
interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, tun.DefaultInterfaceMonitorOptions{
OverrideAndroidVPN: options.OverrideAndroidVPN,
OverrideAndroidVPN: options.OverrideAndroidVPN,
UnderNetworkExtension: platformInterface != nil && platformInterface.UnderNetworkExtension(),
})
if err != nil {
return nil, E.New("auto_detect_interface unsupported on current platform")
@@ -598,7 +595,8 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
switch metadata.Destination.Fqdn {
case mux.Destination.Fqdn:
r.logger.InfoContext(ctx, "inbound multiplex connection")
return mux.NewConnection(ctx, r, r, r.logger, conn, metadata)
handler := adapter.NewUpstreamHandler(metadata, r.RouteConnection, r.RoutePacketConnection, r)
return mux.HandleConnection(ctx, handler, r.logger, conn, adapter.UpstreamMetadata(metadata))
case vmess.MuxDestination.Fqdn:
r.logger.InfoContext(ctx, "inbound legacy multiplex connection")
return vmess.HandleMuxConnection(ctx, conn, adapter.NewUpstreamHandler(metadata, r.RouteConnection, r.RoutePacketConnection, r))
@@ -631,6 +629,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
Fqdn: domain,
Port: metadata.Destination.Port,
}
metadata.FakeIP = true
r.logger.DebugContext(ctx, "found fakeip domain: ", domain)
}
@@ -740,6 +739,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
Fqdn: domain,
Port: metadata.Destination.Port,
}
metadata.FakeIP = true
r.logger.DebugContext(ctx, "found fakeip domain: ", domain)
}
@@ -922,10 +922,6 @@ func (r *Router) Rules() []adapter.Rule {
return r.rules
}
func (r *Router) IPRules() []adapter.IPRule {
return r.ipRules
}
func (r *Router) NetworkMonitor() tun.NetworkUpdateMonitor {
return r.networkMonitor
}
@@ -987,9 +983,22 @@ func (r *Router) notifyNetworkUpdate(int) error {
r.logger.Info("updated default interface ", r.interfaceMonitor.DefaultInterfaceName(netip.IPv4Unspecified()), ", index ", r.interfaceMonitor.DefaultInterfaceIndex(netip.IPv4Unspecified()))
}
if conntrack.Enabled {
conntrack.Close()
}
conntrack.Close()
for _, outbound := range r.outbounds {
listener, isListener := outbound.(adapter.InterfaceUpdateListener)
if isListener {
err := listener.InterfaceUpdated()
if err != nil {
return err
}
}
}
return nil
}
func (r *Router) ResetNetwork() error {
conntrack.Close()
for _, outbound := range r.outbounds {
listener, isListener := outbound.(adapter.InterfaceUpdateListener)

View File

@@ -44,22 +44,27 @@ func (r *Router) matchDNS(ctx context.Context) (context.Context, dns.Transport,
}
for i, rule := range r.dnsRules {
if rule.Match(metadata) {
detour := rule.Outbound()
transport, loaded := r.transportMap[detour]
if !loaded {
r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour)
continue
}
if _, isFakeIP := transport.(adapter.FakeIPTransport); isFakeIP && metadata.FakeIP {
continue
}
r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
if rule.DisableCache() {
ctx = dns.ContextWithDisableCache(ctx, true)
}
if rewriteTTL := rule.RewriteTTL(); rewriteTTL != nil {
ctx = dns.ContextWithRewriteTTL(ctx, *rewriteTTL)
}
detour := rule.Outbound()
r.dnsLogger.DebugContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
if transport, loaded := r.transportMap[detour]; loaded {
if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded {
return ctx, transport, domainStrategy
} else {
return ctx, transport, r.defaultDomainStrategy
}
if domainStrategy, dsLoaded := r.transportDomainStrategy[transport]; dsLoaded {
return ctx, transport, domainStrategy
} else {
return ctx, transport, r.defaultDomainStrategy
}
r.dnsLogger.ErrorContext(ctx, "transport not found: ", detour)
}
}
if domainStrategy, dsLoaded := r.transportDomainStrategy[r.defaultTransport]; dsLoaded {
@@ -73,23 +78,31 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
if len(message.Question) > 0 {
r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String()))
}
ctx, metadata := adapter.AppendContext(ctx)
if len(message.Question) > 0 {
metadata.QueryType = message.Question[0].Qtype
switch metadata.QueryType {
case mDNS.TypeA:
metadata.IPVersion = 4
case mDNS.TypeAAAA:
metadata.IPVersion = 6
var (
response *mDNS.Msg
cached bool
err error
)
response, cached = r.dnsClient.ExchangeCache(ctx, message)
if !cached {
ctx, metadata := adapter.AppendContext(ctx)
if len(message.Question) > 0 {
metadata.QueryType = message.Question[0].Qtype
switch metadata.QueryType {
case mDNS.TypeA:
metadata.IPVersion = 4
case mDNS.TypeAAAA:
metadata.IPVersion = 6
}
metadata.Domain = fqdnToDomain(message.Question[0].Name)
}
ctx, transport, strategy := r.matchDNS(ctx)
ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout)
defer cancel()
response, err = r.dnsClient.Exchange(ctx, transport, message, strategy)
if err != nil && len(message.Question) > 0 {
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String())))
}
metadata.Domain = fqdnToDomain(message.Question[0].Name)
}
ctx, transport, strategy := r.matchDNS(ctx)
ctx, cancel := context.WithTimeout(ctx, C.DNSTimeout)
defer cancel()
response, err := r.dnsClient.Exchange(ctx, transport, message, strategy)
if err != nil && len(message.Question) > 0 {
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", formatQuestion(message.Question[0].String())))
}
if len(message.Question) > 0 && response != nil {
LogDNSAnswers(r.dnsLogger, ctx, message.Question[0].Name, response.Answer)

View File

@@ -1,79 +0,0 @@
package route
import (
"context"
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing-tun"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
)
func (r *Router) RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata adapter.InboundContext) tun.RouteAction {
if r.fakeIPStore != nil && r.fakeIPStore.Contains(metadata.Destination.Addr) {
domain, loaded := r.fakeIPStore.Lookup(metadata.Destination.Addr)
if !loaded {
r.logger.ErrorContext(ctx, "missing fakeip context")
return (*tun.ActionReturn)(nil)
}
metadata.Destination = M.Socksaddr{
Fqdn: domain,
Port: metadata.Destination.Port,
}
r.logger.DebugContext(ctx, "found fakeip domain: ", domain)
}
if r.dnsReverseMapping != nil && metadata.Domain == "" {
domain, loaded := r.dnsReverseMapping.Query(metadata.Destination.Addr)
if loaded {
metadata.Domain = domain
r.logger.DebugContext(ctx, "found reserve mapped domain: ", metadata.Domain)
}
}
if metadata.Destination.IsFqdn() && dns.DomainStrategy(metadata.InboundOptions.DomainStrategy) != dns.DomainStrategyAsIS {
addresses, err := r.Lookup(adapter.WithContext(ctx, &metadata), metadata.Destination.Fqdn, dns.DomainStrategy(metadata.InboundOptions.DomainStrategy))
if err != nil {
r.logger.ErrorContext(ctx, err)
return (*tun.ActionReturn)(nil)
}
metadata.DestinationAddresses = addresses
r.dnsLogger.DebugContext(ctx, "resolved [", strings.Join(F.MapToString(metadata.DestinationAddresses), " "), "]")
}
for i, rule := range r.ipRules {
if rule.Match(&metadata) {
if rule.Action() == tun.ActionTypeBlock {
r.logger.InfoContext(ctx, "match[", i, "] ", rule.String(), " => block")
return (*tun.ActionBlock)(nil)
}
detour := rule.Outbound()
r.logger.InfoContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
outbound, loaded := r.Outbound(detour)
if !loaded {
r.logger.ErrorContext(ctx, "outbound not found: ", detour)
break
}
ipOutbound, loaded := outbound.(adapter.IPOutbound)
if !loaded {
r.logger.ErrorContext(ctx, "outbound have no ip connection support: ", detour)
break
}
destination, err := ipOutbound.NewIPConnection(ctx, conn, metadata)
if err != nil {
r.logger.ErrorContext(ctx, err)
break
}
return &tun.ActionDirect{DirectDestination: destination}
}
}
return (*tun.ActionReturn)(nil)
}
func (r *Router) NatRequired(outbound string) bool {
for _, ipRule := range r.ipRules {
if ipRule.Outbound() == outbound {
return true
}
}
return false
}

View File

@@ -1,189 +0,0 @@
package route
import (
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
func NewIPRule(router adapter.Router, logger log.ContextLogger, options option.IPRule) (adapter.IPRule, error) {
switch options.Type {
case "", C.RuleTypeDefault:
if !options.DefaultOptions.IsValid() {
return nil, E.New("missing conditions")
}
if common.IsEmpty(options.DefaultOptions.Action) {
return nil, E.New("missing action")
}
return NewDefaultIPRule(router, logger, options.DefaultOptions)
case C.RuleTypeLogical:
if !options.LogicalOptions.IsValid() {
return nil, E.New("missing conditions")
}
if common.IsEmpty(options.DefaultOptions.Action) {
return nil, E.New("missing action")
}
return NewLogicalIPRule(router, logger, options.LogicalOptions)
default:
return nil, E.New("unknown rule type: ", options.Type)
}
}
var _ adapter.IPRule = (*DefaultIPRule)(nil)
type DefaultIPRule struct {
abstractDefaultRule
action tun.ActionType
}
func NewDefaultIPRule(router adapter.Router, logger log.ContextLogger, options option.DefaultIPRule) (*DefaultIPRule, error) {
rule := &DefaultIPRule{
abstractDefaultRule: abstractDefaultRule{
invert: options.Invert,
outbound: options.Outbound,
},
action: tun.ActionType(options.Action),
}
if len(options.Inbound) > 0 {
item := NewInboundRule(options.Inbound)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if options.IPVersion > 0 {
switch options.IPVersion {
case 4, 6:
item := NewIPVersionItem(options.IPVersion == 6)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
default:
return nil, E.New("invalid ip version: ", options.IPVersion)
}
}
if len(options.Network) > 0 {
item := NewNetworkItem(options.Network)
rule.items = append(rule.items, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 {
item := NewDomainItem(options.Domain, options.DomainSuffix)
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.DomainKeyword) > 0 {
item := NewDomainKeywordItem(options.DomainKeyword)
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.DomainRegex) > 0 {
item, err := NewDomainRegexItem(options.DomainRegex)
if err != nil {
return nil, E.Cause(err, "domain_regex")
}
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.Geosite) > 0 {
item := NewGeositeItem(router, logger, options.Geosite)
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.SourceGeoIP) > 0 {
item := NewGeoIPItem(router, logger, true, options.SourceGeoIP)
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.GeoIP) > 0 {
item := NewGeoIPItem(router, logger, false, options.GeoIP)
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.SourceIPCIDR) > 0 {
item, err := NewIPCIDRItem(true, options.SourceIPCIDR)
if err != nil {
return nil, E.Cause(err, "source_ipcidr")
}
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.IPCIDR) > 0 {
item, err := NewIPCIDRItem(false, options.IPCIDR)
if err != nil {
return nil, E.Cause(err, "ipcidr")
}
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.SourcePort) > 0 {
item := NewPortItem(true, options.SourcePort)
rule.sourcePortItems = append(rule.sourcePortItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.SourcePortRange) > 0 {
item, err := NewPortRangeItem(true, options.SourcePortRange)
if err != nil {
return nil, E.Cause(err, "source_port_range")
}
rule.sourcePortItems = append(rule.sourcePortItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.Port) > 0 {
item := NewPortItem(false, options.Port)
rule.destinationPortItems = append(rule.destinationPortItems, item)
rule.allItems = append(rule.allItems, item)
}
if len(options.PortRange) > 0 {
item, err := NewPortRangeItem(false, options.PortRange)
if err != nil {
return nil, E.Cause(err, "port_range")
}
rule.destinationPortItems = append(rule.destinationPortItems, item)
rule.allItems = append(rule.allItems, item)
}
return rule, nil
}
func (r *DefaultIPRule) Action() tun.ActionType {
return r.action
}
var _ adapter.IPRule = (*LogicalIPRule)(nil)
type LogicalIPRule struct {
abstractLogicalRule
action tun.ActionType
}
func NewLogicalIPRule(router adapter.Router, logger log.ContextLogger, options option.LogicalIPRule) (*LogicalIPRule, error) {
r := &LogicalIPRule{
abstractLogicalRule: abstractLogicalRule{
rules: make([]adapter.Rule, len(options.Rules)),
invert: options.Invert,
outbound: options.Outbound,
},
action: tun.ActionType(options.Action),
}
switch options.Mode {
case C.LogicalTypeAnd:
r.mode = C.LogicalTypeAnd
case C.LogicalTypeOr:
r.mode = C.LogicalTypeOr
default:
return nil, E.New("unknown logical mode: ", options.Mode)
}
for i, subRule := range options.Rules {
rule, err := NewDefaultIPRule(router, logger, subRule)
if err != nil {
return nil, E.Cause(err, "sub rule[", i, "]")
}
r.rules[i] = rule
}
return r, nil
}
func (r *LogicalIPRule) Action() tun.ActionType {
return r.action
}

View File

@@ -0,0 +1,53 @@
package main
import (
"net/netip"
"testing"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-shadowsocks2/shadowstream"
F "github.com/sagernet/sing/common/format"
)
func TestShadowsocksLegacy(t *testing.T) {
testShadowsocksLegacy(t, shadowstream.MethodList[0])
}
func testShadowsocksLegacy(t *testing.T, method string) {
startDockerContainer(t, DockerOptions{
Image: ImageShadowsocksLegacy,
Ports: []uint16{serverPort},
Env: []string{
"SS_MODULE=ss-server",
F.ToString("SS_CONFIG=-s 0.0.0.0 -u -p 10000 -m ", method, " -k FzcLbKs2dY9mhL"),
},
})
startInstance(t, option.Options{
Inbounds: []option.Inbound{
{
Type: C.TypeMixed,
MixedOptions: option.HTTPMixedInboundOptions{
ListenOptions: option.ListenOptions{
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
ListenPort: clientPort,
},
},
},
},
Outbounds: []option.Outbound{
{
Type: C.TypeShadowsocks,
ShadowsocksOptions: option.ShadowsocksOutboundOptions{
ServerOptions: option.ServerOptions{
Server: "127.0.0.1",
ServerPort: serverPort,
},
Method: method,
Password: "FzcLbKs2dY9mhL",
},
},
},
})
testSuitSimple(t, clientPort, testPort)
}

View File

@@ -40,6 +40,10 @@ func TestShadowsocks(t *testing.T) {
}
}
func TestShadowsocksNone(t *testing.T) {
testShadowsocksSelf(t, "none", "")
}
func TestShadowsocks2022(t *testing.T) {
for _, method16 := range []string{
"2022-blake3-aes-128-gcm",

View File

@@ -0,0 +1,37 @@
package fakeip
import (
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func (c *NATPacketConn) CreatePacketReadWaiter() (N.PacketReadWaiter, bool) {
waiter, created := bufio.CreatePacketReadWaiter(c.PacketConn)
if !created {
return nil, false
}
return &waitNATPacketConn{c, waiter}, true
}
type waitNATPacketConn struct {
*NATPacketConn
waiter N.PacketReadWaiter
}
func (c *waitNATPacketConn) InitializeReadWaiter(newBuffer func() *buf.Buffer) {
c.waiter.InitializeReadWaiter(newBuffer)
}
func (c *waitNATPacketConn) WaitReadPacket() (destination M.Socksaddr, err error) {
destination, err = c.waiter.WaitReadPacket()
if socksaddrWithoutPort(destination) == c.origin {
destination = M.Socksaddr{
Addr: c.destination.Addr,
Fqdn: c.destination.Fqdn,
Port: destination.Port,
}
}
return
}

View File

@@ -14,13 +14,16 @@ import (
mDNS "github.com/miekg/dns"
)
var _ dns.Transport = (*Server)(nil)
var (
_ dns.Transport = (*Transport)(nil)
_ adapter.FakeIPTransport = (*Transport)(nil)
)
func init() {
dns.RegisterTransport([]string{"fakeip"}, NewTransport)
}
type Server struct {
type Transport struct {
name string
router adapter.Router
store adapter.FakeIPStore
@@ -32,18 +35,18 @@ func NewTransport(name string, ctx context.Context, logger logger.ContextLogger,
if router == nil {
return nil, E.New("missing router in context")
}
return &Server{
return &Transport{
name: name,
router: router,
logger: logger,
}, nil
}
func (s *Server) Name() string {
func (s *Transport) Name() string {
return s.name
}
func (s *Server) Start() error {
func (s *Transport) Start() error {
s.store = s.router.FakeIPStore()
if s.store == nil {
return E.New("fakeip not enabled")
@@ -51,19 +54,19 @@ func (s *Server) Start() error {
return nil
}
func (s *Server) Close() error {
func (s *Transport) Close() error {
return nil
}
func (s *Server) Raw() bool {
func (s *Transport) Raw() bool {
return false
}
func (s *Server) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
func (s *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
return nil, os.ErrInvalid
}
func (s *Server) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) {
func (s *Transport) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) {
var addresses []netip.Addr
if strategy != dns.DomainStrategyUseIPv6 {
inet4Address, err := s.store.Create(domain, dns.DomainStrategyUseIPv4)
@@ -81,3 +84,7 @@ func (s *Server) Lookup(ctx context.Context, domain string, strategy dns.DomainS
}
return addresses, nil
}
func (s *Transport) Store() adapter.FakeIPStore {
return s.store
}

View File

@@ -42,11 +42,19 @@ func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, opt
if err != nil {
return nil, err
}
return tls.ClientHandshake(ctx, conn, tlsConfig)
tlsConn, err := tls.ClientHandshake(ctx, conn, tlsConfig)
if err != nil {
return nil, err
}
return &deadConn{tlsConn}, nil
}
} else {
wsDialer.NetDialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
if err != nil {
return nil, err
}
return &deadConn{conn}, nil
}
}
var uri url.URL

View File

@@ -0,0 +1,22 @@
package v2raywebsocket
import (
"net"
"time"
)
type deadConn struct {
net.Conn
}
func (c *deadConn) SetDeadline(t time.Time) error {
return nil
}
func (c *deadConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *deadConn) SetWriteDeadline(t time.Time) error {
return nil
}

View File

@@ -130,7 +130,7 @@ func WriteRequest(writer io.Writer, request Request, payload []byte) error {
var addonsLen int
if request.Flow != "" {
addonsLen += 1 // protobuf header
addonsLen += UvarintLen(uint64(len(request.Flow)))
addonsLen += rw.UVariantLen(uint64(len(request.Flow)))
addonsLen += len(request.Flow)
requestLen += addonsLen
}
@@ -150,8 +150,8 @@ func WriteRequest(writer io.Writer, request Request, payload []byte) error {
)
if addonsLen > 0 {
common.Must(buffer.WriteByte(10))
binary.PutUvarint(buffer.Extend(UvarintLen(uint64(len(request.Flow)))), uint64(len(request.Flow)))
common.Must(common.Error(buffer.Write([]byte(request.Flow))))
binary.PutUvarint(buffer.Extend(rw.UVariantLen(uint64(len(request.Flow)))), uint64(len(request.Flow)))
common.Must(common.Error(buffer.WriteString(request.Flow)))
}
common.Must(
buffer.WriteByte(request.Command),
@@ -174,7 +174,7 @@ func EncodeRequest(request Request, buffer *buf.Buffer) {
var addonsLen int
if request.Flow != "" {
addonsLen += 1 // protobuf header
addonsLen += UvarintLen(uint64(len(request.Flow)))
addonsLen += rw.UVariantLen(uint64(len(request.Flow)))
addonsLen += len(request.Flow)
requestLen += addonsLen
}
@@ -189,8 +189,8 @@ func EncodeRequest(request Request, buffer *buf.Buffer) {
)
if addonsLen > 0 {
common.Must(buffer.WriteByte(10))
binary.PutUvarint(buffer.Extend(UvarintLen(uint64(len(request.Flow)))), uint64(len(request.Flow)))
common.Must(common.Error(buffer.Write([]byte(request.Flow))))
binary.PutUvarint(buffer.Extend(rw.UVariantLen(uint64(len(request.Flow)))), uint64(len(request.Flow)))
common.Must(common.Error(buffer.WriteString(request.Flow)))
}
common.Must(
buffer.WriteByte(request.Command),
@@ -210,7 +210,7 @@ func RequestLen(request Request) int {
var addonsLen int
if request.Flow != "" {
addonsLen += 1 // protobuf header
addonsLen += UvarintLen(uint64(len(request.Flow)))
addonsLen += rw.UVariantLen(uint64(len(request.Flow)))
addonsLen += len(request.Flow)
requestLen += addonsLen
}
@@ -229,7 +229,7 @@ func WritePacketRequest(writer io.Writer, request Request, payload []byte) error
var addonsLen int
/*if request.Flow != "" {
addonsLen += 1 // protobuf header
addonsLen += UvarintLen(uint64(len(request.Flow)))
addonsLen += rw.UVariantLen(uint64(len(request.Flow)))
addonsLen += len(request.Flow)
requestLen += addonsLen
}*/
@@ -251,8 +251,8 @@ func WritePacketRequest(writer io.Writer, request Request, payload []byte) error
if addonsLen > 0 {
common.Must(buffer.WriteByte(10))
binary.PutUvarint(buffer.Extend(UvarintLen(uint64(len(request.Flow)))), uint64(len(request.Flow)))
common.Must(common.Error(buffer.Write([]byte(request.Flow))))
binary.PutUvarint(buffer.Extend(rw.UVariantLen(uint64(len(request.Flow)))), uint64(len(request.Flow)))
common.Must(common.Error(buffer.WriteString(request.Flow)))
}
common.Must(
@@ -290,8 +290,3 @@ func ReadResponse(reader io.Reader) error {
}
return nil
}
func UvarintLen(value uint64) int {
var buffer [binary.MaxVarintLen64]byte
return binary.PutUvarint(buffer[:], value)
}

View File

@@ -1,23 +1,13 @@
package wireguard
import (
"net/netip"
"github.com/sagernet/sing-tun"
N "github.com/sagernet/sing/common/network"
wgTun "github.com/sagernet/wireguard-go/tun"
"github.com/sagernet/wireguard-go/tun"
)
type Device interface {
wgTun.Device
tun.Device
N.Dialer
Start() error
Inet4Address() netip.Addr
Inet6Address() netip.Addr
// NewEndpoint() (stack.LinkEndpoint, error)
}
type NatDevice interface {
Device
CreateDestination(session tun.RouteSession, conn tun.RouteContext) tun.DirectDestination
}

View File

@@ -1,76 +0,0 @@
package wireguard
import (
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/buf"
)
var _ Device = (*natDeviceWrapper)(nil)
type natDeviceWrapper struct {
Device
outbound chan *buf.Buffer
mapping *tun.NatMapping
writer *tun.NatWriter
}
func NewNATDevice(upstream Device, ipRewrite bool) NatDevice {
wrapper := &natDeviceWrapper{
Device: upstream,
outbound: make(chan *buf.Buffer, 256),
mapping: tun.NewNatMapping(ipRewrite),
}
if ipRewrite {
wrapper.writer = tun.NewNatWriter(upstream.Inet4Address(), upstream.Inet6Address())
}
return wrapper
}
func (d *natDeviceWrapper) Read(bufs [][]byte, sizes []int, offset int) (count int, err error) {
select {
case packet := <-d.outbound:
defer packet.Release()
sizes[0] = copy(bufs[0][offset:], packet.Bytes())
return 1, nil
default:
}
return d.Device.Read(bufs, sizes, offset)
}
func (d *natDeviceWrapper) Write(bufs [][]byte, offset int) (count int, err error) {
packet := bufs[0][offset:]
handled, err := d.mapping.WritePacket(packet)
if handled {
return 1, err
}
return d.Device.Write(bufs, offset)
}
func (d *natDeviceWrapper) CreateDestination(session tun.RouteSession, conn tun.RouteContext) tun.DirectDestination {
d.mapping.CreateSession(session, conn)
return &natDestinationWrapper{d, session}
}
var _ tun.DirectDestination = (*natDestinationWrapper)(nil)
type natDestinationWrapper struct {
device *natDeviceWrapper
session tun.RouteSession
}
func (d *natDestinationWrapper) WritePacket(buffer *buf.Buffer) error {
if d.device.writer != nil {
d.device.writer.RewritePacket(buffer.Bytes())
}
d.device.outbound <- buffer
return nil
}
func (d *natDestinationWrapper) Close() error {
d.device.mapping.DeleteSession(d.session)
return nil
}
func (d *natDestinationWrapper) Timeout() bool {
return false
}

View File

@@ -1,27 +0,0 @@
//go:build with_gvisor
package wireguard
import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
func (d *natDestinationWrapper) WritePacketBuffer(buffer stack.PacketBufferPtr) error {
defer buffer.DecRef()
if d.device.writer != nil {
d.device.writer.RewritePacketBuffer(buffer)
}
var packetLen int
for _, slice := range buffer.AsSlices() {
packetLen += len(slice)
}
packet := buf.NewSize(packetLen)
for _, slice := range buffer.AsSlices() {
common.Must1(packet.Write(slice))
}
d.device.outbound <- packet
return nil
}

View File

@@ -15,19 +15,19 @@ import (
N "github.com/sagernet/sing/common/network"
wgTun "github.com/sagernet/wireguard-go/tun"
"gvisor.dev/gvisor/pkg/bufferv2"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
"github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv4"
"github.com/sagernet/gvisor/pkg/tcpip/network/ipv6"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/gvisor/pkg/tcpip/transport/icmp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
)
var _ NatDevice = (*StackDevice)(nil)
var _ Device = (*StackDevice)(nil)
const defaultNIC tcpip.NICID = 1
@@ -41,11 +41,9 @@ type StackDevice struct {
dispatcher stack.NetworkDispatcher
addr4 tcpip.Address
addr6 tcpip.Address
mapping *tun.NatMapping
writer *tun.NatWriter
}
func NewStackDevice(localAddresses []netip.Prefix, mtu uint32, ipRewrite bool) (*StackDevice, error) {
func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (*StackDevice, error) {
ipStack := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol, udp.NewProtocol, icmp.NewProtocol4, icmp.NewProtocol6},
@@ -58,14 +56,13 @@ func NewStackDevice(localAddresses []netip.Prefix, mtu uint32, ipRewrite bool) (
outbound: make(chan stack.PacketBufferPtr, 256),
packetOutbound: make(chan *buf.Buffer, 256),
done: make(chan struct{}),
mapping: tun.NewNatMapping(ipRewrite),
}
err := ipStack.CreateNIC(defaultNIC, (*wireEndpoint)(tunDevice))
if err != nil {
return nil, E.New(err.String())
}
for _, prefix := range localAddresses {
addr := tcpip.Address(prefix.Addr().AsSlice())
addr := tun.AddressFromAddr(prefix.Addr())
protoAddr := tcpip.ProtocolAddress{
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: addr,
@@ -84,9 +81,6 @@ func NewStackDevice(localAddresses []netip.Prefix, mtu uint32, ipRewrite bool) (
return nil, E.New("parse local address ", protoAddr.AddressWithPrefix, ": ", err.String())
}
}
if ipRewrite {
tunDevice.writer = tun.NewNatWriter(tunDevice.Inet4Address(), tunDevice.Inet6Address())
}
sOpt := tcpip.TCPSACKEnabled(true)
ipStack.SetTransportProtocolOption(tcp.ProtocolNumber, &sOpt)
cOpt := tcpip.CongestionControlOption("cubic")
@@ -104,7 +98,7 @@ func (w *StackDevice) DialContext(ctx context.Context, network string, destinati
addr := tcpip.FullAddress{
NIC: defaultNIC,
Port: destination.Port,
Addr: tcpip.Address(destination.Addr.AsSlice()),
Addr: tun.AddressFromAddr(destination.Addr),
}
bind := tcpip.FullAddress{
NIC: defaultNIC,
@@ -119,7 +113,7 @@ func (w *StackDevice) DialContext(ctx context.Context, network string, destinati
}
switch N.NetworkName(network) {
case N.NetworkTCP:
tcpConn, err := gonet.DialTCPWithBind(ctx, w.stack, bind, addr, networkProtocol)
tcpConn, err := DialTCPWithBind(ctx, w.stack, bind, addr, networkProtocol)
if err != nil {
return nil, err
}
@@ -140,7 +134,7 @@ func (w *StackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr)
NIC: defaultNIC,
}
var networkProtocol tcpip.NetworkProtocolNumber
if destination.IsIPv4() || w.addr6 == "" {
if destination.IsIPv4() {
networkProtocol = header.IPv4ProtocolNumber
bind.Addr = w.addr4
} else {
@@ -155,11 +149,11 @@ func (w *StackDevice) ListenPacket(ctx context.Context, destination M.Socksaddr)
}
func (w *StackDevice) Inet4Address() netip.Addr {
return M.AddrFromIP(net.IP(w.addr4))
return tun.AddrFromAddress(w.addr4)
}
func (w *StackDevice) Inet6Address() netip.Addr {
return M.AddrFromIP(net.IP(w.addr6))
return tun.AddrFromAddress(w.addr6)
}
func (w *StackDevice) Start() error {
@@ -203,14 +197,6 @@ func (w *StackDevice) Write(bufs [][]byte, offset int) (count int, err error) {
if len(b) == 0 {
continue
}
handled, err := w.mapping.WritePacket(b)
if handled {
count++
if err != nil {
return count, err
}
continue
}
var networkProtocol tcpip.NetworkProtocolNumber
switch header.IPVersion(b) {
case header.IPv4Version:
@@ -219,7 +205,7 @@ func (w *StackDevice) Write(bufs [][]byte, offset int) (count int, err error) {
networkProtocol = header.IPv6ProtocolNumber
}
packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: bufferv2.MakeWithData(b),
Payload: buffer.MakeWithData(b),
})
w.dispatcher.DeliverNetworkPacket(networkProtocol, packetBuffer)
packetBuffer.DecRef()
@@ -263,44 +249,6 @@ func (w *StackDevice) BatchSize() int {
return 1
}
func (w *StackDevice) CreateDestination(session tun.RouteSession, conn tun.RouteContext) tun.DirectDestination {
w.mapping.CreateSession(session, conn)
return &stackNatDestination{
device: w,
session: session,
}
}
type stackNatDestination struct {
device *StackDevice
session tun.RouteSession
}
func (d *stackNatDestination) WritePacket(buffer *buf.Buffer) error {
if d.device.writer != nil {
d.device.writer.RewritePacket(buffer.Bytes())
}
d.device.packetOutbound <- buffer
return nil
}
func (d *stackNatDestination) WritePacketBuffer(buffer stack.PacketBufferPtr) error {
if d.device.writer != nil {
d.device.writer.RewritePacketBuffer(buffer)
}
d.device.outbound <- buffer
return nil
}
func (d *stackNatDestination) Close() error {
d.device.mapping.DeleteSession(d.session)
return nil
}
func (d *stackNatDestination) Timeout() bool {
return false
}
var _ stack.LinkEndpoint = (*wireEndpoint)(nil)
type wireEndpoint StackDevice

View File

@@ -8,6 +8,6 @@ import (
"github.com/sagernet/sing-tun"
)
func NewStackDevice(localAddresses []netip.Prefix, mtu uint32, ipRewrite bool) (Device, error) {
func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (Device, error) {
return nil, tun.ErrGVisorNotIncluded
}

View File

@@ -0,0 +1,80 @@
//go:build with_gvisor
package wireguard
import (
"context"
"errors"
"fmt"
"net"
"net/netip"
"time"
"github.com/sagernet/sing-tun"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/adapters/gonet"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
"github.com/sagernet/gvisor/pkg/waiter"
)
func DialTCPWithBind(ctx context.Context, s *stack.Stack, localAddr, remoteAddr tcpip.FullAddress, network tcpip.NetworkProtocolNumber) (*gonet.TCPConn, error) {
// Create TCP endpoint, then connect.
var wq waiter.Queue
ep, err := s.NewEndpoint(tcp.ProtocolNumber, network, &wq)
if err != nil {
return nil, errors.New(err.String())
}
// Create wait queue entry that notifies a channel.
//
// We do this unconditionally as Connect will always return an error.
waitEntry, notifyCh := waiter.NewChannelEntry(waiter.WritableEvents)
wq.EventRegister(&waitEntry)
defer wq.EventUnregister(&waitEntry)
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
// Bind before connect if requested.
if localAddr != (tcpip.FullAddress{}) {
if err = ep.Bind(localAddr); err != nil {
return nil, fmt.Errorf("ep.Bind(%+v) = %s", localAddr, err)
}
}
err = ep.Connect(remoteAddr)
if _, ok := err.(*tcpip.ErrConnectStarted); ok {
select {
case <-ctx.Done():
ep.Close()
return nil, ctx.Err()
case <-notifyCh:
}
err = ep.LastError()
}
if err != nil {
ep.Close()
return nil, &net.OpError{
Op: "connect",
Net: "tcp",
Addr: M.SocksaddrFromNetIP(netip.AddrPortFrom(tun.AddrFromAddress(remoteAddr.Addr), remoteAddr.Port)).TCPAddr(),
Err: errors.New(err.String()),
}
}
// sing-box added: set keepalive
ep.SocketOptions().SetKeepAlive(true)
keepAliveIdle := tcpip.KeepaliveIdleOption(15 * time.Second)
ep.SetSockOpt(&keepAliveIdle)
keepAliveInterval := tcpip.KeepaliveIntervalOption(15 * time.Second)
ep.SetSockOpt(&keepAliveInterval)
return gonet.NewTCPConn(&wq, ep), nil
}