Add OpenVPN, TrustTunnel, Sudoku, inbound managers. Fixes

This commit is contained in:
Shtorm
2026-06-04 01:47:50 +03:00
parent 9b3da79c32
commit 195a33379d
164 changed files with 16665 additions and 1332 deletions

View File

@@ -0,0 +1,323 @@
package trusttunnel
import (
"context"
stdtls "crypto/tls"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"runtime"
"sync"
"sync/atomic"
"time"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/ntp"
"github.com/sagernet/quic-go"
"github.com/sagernet/quic-go/http3"
qtls "github.com/sagernet/sing-quic"
"golang.org/x/net/http2"
)
var (
appName = "sing-box"
appVersion = C.Version
tcpUserAgent = runtime.GOOS + " " + appName + "/" + appVersion
udpUserAgent = runtime.GOOS + " " + UDPMagicAddress
icmpUserAgent = runtime.GOOS + " " + ICMPMagicAddress
)
type Dialer interface {
Dial(ctx context.Context, host string) (net.Conn, error)
ListenPacket(ctx context.Context) (net.PacketConn, error)
Close() error
}
type ClientOptions struct {
TLSDialer tls.Dialer
QUICDialer N.Dialer
QUICTLSConfig tls.Config
Server M.Socksaddr
Username string
Password string
QUIC bool
CongestionControl string
CWND int
BBRProfile string
HealthCheck bool
MaxConnections int
MinStreams int
MaxStreams int
}
type Client struct {
ctx context.Context
cancel context.CancelFunc
server M.Socksaddr
serverString string
auth string
roundTripper http.RoundTripper
startOnce sync.Once
healthCheck bool
healthCheckTimer *time.Timer
count atomic.Int64
}
func NewClient(ctx context.Context, options ClientOptions) (*Client, error) {
ctx, cancel := context.WithCancel(ctx)
client := &Client{
ctx: ctx,
cancel: cancel,
server: options.Server,
serverString: options.Server.String(),
auth: buildAuth(options.Username, options.Password),
healthCheck: options.HealthCheck,
}
if options.QUIC {
congestionControlFactory, err := NewCongestionControl(options.CongestionControl, options.CWND, options.BBRProfile, ntp.TimeFuncFromContext(ctx))
if err != nil {
cancel()
return nil, err
}
client.roundTripper = &http3.Transport{
QUICConfig: &quic.Config{
MaxIdleTimeout: DefaultSessionTimeout * 2,
KeepAlivePeriod: DefaultHealthCheckTimeout,
},
Dial: func(ctx context.Context, addr string, tlsCfg *stdtls.Config, cfg *quic.Config) (*quic.Conn, error) {
udpConn, err := options.QUICDialer.DialContext(ctx, N.NetworkUDP, client.server)
if err != nil {
return nil, err
}
conn, err := qtls.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), options.QUICTLSConfig, cfg)
if err != nil {
return nil, err
}
conn.SetCongestionControl(congestionControlFactory(conn))
return conn, nil
},
}
} else {
client.roundTripper = &http2.Transport{
DialTLSContext: func(ctx context.Context, network, addr string, _ *stdtls.Config) (net.Conn, error) {
return options.TLSDialer.DialContext(ctx, network, client.server)
},
AllowHTTP: true,
}
}
return client, nil
}
func (c *Client) start() {
if c.healthCheck {
c.healthCheckTimer = time.NewTimer(DefaultHealthCheckTimeout)
go c.loopHealthCheck()
}
}
func (c *Client) loopHealthCheck() {
for {
select {
case <-c.healthCheckTimer.C:
case <-c.ctx.Done():
c.healthCheckTimer.Stop()
return
}
ctx, cancel := context.WithTimeout(c.ctx, DefaultHealthCheckTimeout)
_ = c.HealthCheck(ctx)
cancel()
}
}
func (c *Client) resetHealthCheckTimer() {
if c.healthCheckTimer == nil {
return
}
c.healthCheckTimer.Reset(DefaultHealthCheckTimeout)
}
func (c *Client) roundTrip(request *http.Request, conn *httpConn) {
c.startOnce.Do(c.start)
pipeReader, pipeWriter := io.Pipe()
request.Body = pipeReader
*conn = httpConn{writer: pipeWriter, created: make(chan struct{})}
c.count.Add(1)
conn.closeFn = sync.OnceFunc(func() { c.count.Add(-1) })
ctx, cancel := context.WithCancel(c.ctx)
conn.cancelFn = cancel
go func() {
timeout := time.AfterFunc(C.TCPTimeout, cancel)
defer timeout.Stop()
request = request.WithContext(ctx)
response, err := c.roundTripper.RoundTrip(request)
if err != nil {
_ = pipeWriter.CloseWithError(err)
_ = pipeReader.CloseWithError(err)
conn.setup(nil, err)
} else if response.StatusCode != http.StatusOK {
_ = response.Body.Close()
err = fmt.Errorf("unexpected status code: %d", response.StatusCode)
_ = pipeWriter.CloseWithError(err)
_ = pipeReader.CloseWithError(err)
conn.setup(nil, err)
} else {
c.resetHealthCheckTimer()
conn.setup(response.Body, nil)
}
}()
}
func (c *Client) newConnectRequest(host, userAgent string) *http.Request {
return &http.Request{
Method: http.MethodConnect,
URL: &url.URL{Scheme: "https", Host: c.serverString},
Header: http.Header{
"User-Agent": {userAgent},
"Proxy-Authorization": {c.auth},
},
Host: host,
}
}
func (c *Client) Dial(ctx context.Context, host string) (net.Conn, error) {
conn := &tcpConn{}
c.roundTrip(c.newConnectRequest(host, tcpUserAgent), &conn.httpConn)
return conn, nil
}
func (c *Client) ListenPacket(ctx context.Context) (net.PacketConn, error) {
conn := &clientPacketConn{}
c.roundTrip(c.newConnectRequest(UDPMagicAddress, udpUserAgent), &conn.httpConn)
return conn, nil
}
func (c *Client) Close() error {
c.cancel()
if closer, ok := c.roundTripper.(io.Closer); ok {
_ = closer.Close()
}
if t, ok := c.roundTripper.(*http2.Transport); ok {
t.CloseIdleConnections()
}
if c.healthCheckTimer != nil {
c.healthCheckTimer.Stop()
}
return nil
}
func (c *Client) HealthCheck(ctx context.Context) error {
defer c.resetHealthCheckTimer()
response, err := c.roundTripper.RoundTrip(c.newConnectRequest(HealthCheckMagicAddress, runtime.GOOS).WithContext(ctx))
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status code: %d", response.StatusCode)
}
return nil
}
type MultiplexClient struct {
mutex sync.Mutex
maxConnections int
minStreams int
maxStreams int
ctx context.Context
options ClientOptions
clients []*Client
}
func NewMultiplexClient(ctx context.Context, options ClientOptions) (*MultiplexClient, error) {
maxConnections := options.MaxConnections
minStreams := options.MinStreams
maxStreams := options.MaxStreams
if maxConnections == 0 && minStreams == 0 && maxStreams == 0 {
maxConnections = 8
minStreams = 5
}
client, err := NewClient(ctx, options)
if err != nil {
return nil, err
}
return &MultiplexClient{
maxConnections: maxConnections,
minStreams: minStreams,
maxStreams: maxStreams,
ctx: ctx,
options: options,
clients: []*Client{client},
}, nil
}
func (c *MultiplexClient) Dial(ctx context.Context, host string) (net.Conn, error) {
t, err := c.getClient()
if err != nil {
return nil, err
}
return t.Dial(ctx, host)
}
func (c *MultiplexClient) ListenPacket(ctx context.Context) (net.PacketConn, error) {
t, err := c.getClient()
if err != nil {
return nil, err
}
return t.ListenPacket(ctx)
}
func (c *MultiplexClient) Close() error {
c.mutex.Lock()
defer c.mutex.Unlock()
var errs []error
for _, t := range c.clients {
if err := t.Close(); err != nil {
errs = append(errs, err)
}
}
c.clients = nil
return errors.Join(errs...)
}
func (c *MultiplexClient) getClient() (*Client, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
var transport *Client
for _, t := range c.clients {
if transport == nil || t.count.Load() < transport.count.Load() {
transport = t
}
}
if transport == nil {
return c.newClientLocked()
}
numStreams := int(transport.count.Load())
if numStreams == 0 {
return transport, nil
}
if c.maxConnections > 0 {
if len(c.clients) >= c.maxConnections || numStreams < c.minStreams {
return transport, nil
}
} else if c.maxStreams > 0 && numStreams < c.maxStreams {
return transport, nil
}
return c.newClientLocked()
}
func (c *MultiplexClient) newClientLocked() (*Client, error) {
t, err := NewClient(c.ctx, c.options)
if err != nil {
return nil, err
}
c.clients = append(c.clients, t)
return t, nil
}

View File

@@ -0,0 +1,62 @@
package trusttunnel
import (
"encoding/binary"
"net/netip"
"github.com/sagernet/sing/common/buf"
)
type IcmpConn struct {
httpConn
}
func (i *IcmpConn) WritePing(id uint16, destination netip.Addr, sequenceNumber uint16, ttl uint8, size uint16) error {
request := buf.NewSize(2 + 16 + 2 + 1 + 2)
defer request.Release()
must(binary.Write(request, binary.BigEndian, id))
destinationAddress := buildPaddingIP(destination)
must1(request.Write(destinationAddress[:]))
must(binary.Write(request, binary.BigEndian, sequenceNumber))
must(binary.Write(request, binary.BigEndian, ttl))
must(binary.Write(request, binary.BigEndian, size))
_, err := i.writeFlush(request.Bytes())
return err
}
func (i *IcmpConn) ReadPing() (id uint16, sourceAddress netip.Addr, icmpType uint8, code uint8, sequenceNumber uint16, err error) {
err = i.waitCreated()
if err != nil {
return
}
response := buf.NewSize(2 + 16 + 1 + 1 + 2)
defer response.Release()
_, err = response.ReadFullFrom(i.body, response.FreeLen())
if err != nil {
return
}
must(binary.Read(response, binary.BigEndian, &id))
var sourceAddressBuffer [16]byte
must1(response.Read(sourceAddressBuffer[:]))
sourceAddress = parse16BytesIP(sourceAddressBuffer)
must(binary.Read(response, binary.BigEndian, &icmpType))
must(binary.Read(response, binary.BigEndian, &code))
must(binary.Read(response, binary.BigEndian, &sequenceNumber))
return
}
func (i *IcmpConn) Close() error {
return i.httpConn.Close()
}
func must(err error) {
if err != nil {
panic(err)
}
}
func must1[T any](_ T, err error) {
if err != nil {
panic(err)
}
}

View File

@@ -0,0 +1,210 @@
package trusttunnel
import (
"encoding/binary"
"math"
"net"
"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"
)
var (
_ N.NetPacketConn = (*clientPacketConn)(nil)
_ N.FrontHeadroom = (*clientPacketConn)(nil)
)
type clientPacketConn struct {
httpConn
}
func (u *clientPacketConn) FrontHeadroom() int {
return 4 + 16 + 2 + 16 + 2 + 1 + math.MaxUint8
}
func (u *clientPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
err = u.waitCreated()
if err != nil {
return M.Socksaddr{}, err
}
return u.readPacketFromServer(buffer)
}
func (u *clientPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
buffer := buf.With(p)
destination, err := u.ReadPacket(buffer)
if err != nil {
return 0, nil, err
}
return buffer.Len(), destination.UDPAddr(), nil
}
func (u *clientPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
return u.writePacketToServer(buffer, destination)
}
func (u *clientPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
err = u.WritePacket(buf.As(p), M.SocksaddrFromNet(addr))
if err != nil {
return 0, err
}
return len(p), nil
}
func (u *clientPacketConn) readPacketFromServer(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
header := buf.NewSize(4 + 16 + 2 + 16 + 2)
defer header.Release()
_, err = header.ReadFullFrom(u.body, header.FreeLen())
if err != nil {
return
}
var length uint32
common.Must(binary.Read(header, binary.BigEndian, &length))
var sourceAddressBuffer [16]byte
common.Must1(header.Read(sourceAddressBuffer[:]))
destination.Addr = parse16BytesIP(sourceAddressBuffer)
common.Must(binary.Read(header, binary.BigEndian, &destination.Port))
common.Must(rw.SkipN(header, 16+2))
payloadLen := int(length) - (16 + 2 + 16 + 2)
if payloadLen < 0 {
return M.Socksaddr{}, E.New("invalid udp length: ", length)
}
_, err = buffer.ReadFullFrom(u.body, payloadLen)
return
}
func (u *clientPacketConn) writePacketToServer(buffer *buf.Buffer, source M.Socksaddr) error {
defer buffer.Release()
if !source.IsIP() {
return E.New("only support IP")
}
payloadLen := buffer.Len()
headerLen := 4 + 16 + 2 + 16 + 2 + 1 + len(appName)
lengthField := uint32(16 + 2 + 16 + 2 + 1 + len(appName) + payloadLen)
destinationAddress := buildPaddingIP(source.Addr)
header := buf.NewSize(headerLen)
defer header.Release()
common.Must(binary.Write(header, binary.BigEndian, lengthField))
common.Must(header.WriteZeroN(16 + 2))
common.Must1(header.Write(destinationAddress[:]))
common.Must(binary.Write(header, binary.BigEndian, source.Port))
common.Must(binary.Write(header, binary.BigEndian, uint8(len(appName))))
common.Must1(header.WriteString(appName))
_, err := u.writer.Write(header.Bytes())
if err != nil {
return err
}
_, err = u.writer.Write(buffer.Bytes())
if err != nil {
return err
}
if u.flusher != nil {
u.flusher.Flush()
}
return nil
}
var (
_ N.NetPacketConn = (*serverPacketConn)(nil)
_ N.FrontHeadroom = (*serverPacketConn)(nil)
)
type serverPacketConn struct {
httpConn
}
func (u *serverPacketConn) FrontHeadroom() int {
return 4 + 16 + 2 + 16 + 2
}
func (u *serverPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
err = u.waitCreated()
if err != nil {
return M.Socksaddr{}, err
}
return u.readPacketFromClient(buffer)
}
func (u *serverPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
buffer := buf.With(p)
destination, err := u.ReadPacket(buffer)
if err != nil {
return 0, nil, err
}
return buffer.Len(), destination.UDPAddr(), nil
}
func (u *serverPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
return u.writePacketToClient(buffer, destination)
}
func (u *serverPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
err = u.WritePacket(buf.As(p), M.SocksaddrFromNet(addr))
if err != nil {
return 0, err
}
return len(p), nil
}
func (u *serverPacketConn) readPacketFromClient(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
header := buf.NewSize(4 + 16 + 2 + 16 + 2 + 1)
defer header.Release()
_, err = header.ReadFullFrom(u.body, header.FreeLen())
if err != nil {
return
}
var length uint32
common.Must(binary.Read(header, binary.BigEndian, &length))
common.Must(rw.SkipN(header, 16+2))
var destinationAddressBuffer [16]byte
common.Must1(header.Read(destinationAddressBuffer[:]))
destination.Addr = parse16BytesIP(destinationAddressBuffer)
common.Must(binary.Read(header, binary.BigEndian, &destination.Port))
var appNameLen uint8
common.Must(binary.Read(header, binary.BigEndian, &appNameLen))
if appNameLen > 0 {
err = rw.SkipN(u.body, int(appNameLen))
if err != nil {
return M.Socksaddr{}, err
}
}
payloadLen := int(length) - (16 + 2 + 16 + 2 + 1 + int(appNameLen))
if payloadLen < 0 {
return M.Socksaddr{}, E.New("invalid udp length: ", length)
}
_, err = buffer.ReadFullFrom(u.body, payloadLen)
return
}
func (u *serverPacketConn) writePacketToClient(buffer *buf.Buffer, source M.Socksaddr) error {
defer buffer.Release()
if !source.IsIP() {
return E.New("only support IP")
}
payloadLen := buffer.Len()
headerLen := 4 + 16 + 2 + 16 + 2
lengthField := uint32(16 + 2 + 16 + 2 + payloadLen)
sourceAddress := buildPaddingIP(source.Addr)
header := buf.NewSize(headerLen)
defer header.Release()
common.Must(binary.Write(header, binary.BigEndian, lengthField))
common.Must1(header.Write(sourceAddress[:]))
common.Must(binary.Write(header, binary.BigEndian, source.Port))
common.Must(header.WriteZeroN(16 + 2))
_, err := u.writer.Write(header.Bytes())
if err != nil {
return err
}
_, err = u.writer.Write(buffer.Bytes())
if err != nil {
return err
}
if u.flusher != nil {
u.flusher.Flush()
}
return nil
}

View File

@@ -0,0 +1,174 @@
package trusttunnel
import (
"bytes"
"encoding/base64"
"io"
"net"
"net/http"
"net/netip"
"strings"
"sync"
"time"
)
const (
UDPMagicAddress = "_udp2"
ICMPMagicAddress = "_icmp"
HealthCheckMagicAddress = "_check"
DefaultConnectionTimeout = 30 * time.Second
DefaultHealthCheckTimeout = 7 * time.Second
DefaultSessionTimeout = 30 * time.Second
)
func buildAuth(username string, password string) string {
return "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password))
}
func parseBasicAuth(auth string) (username, password string, ok bool) {
const prefix = "Basic "
if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) {
return "", "", false
}
c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
if err != nil {
return "", "", false
}
cs := string(c)
username, password, ok = strings.Cut(cs, ":")
return
}
func parse16BytesIP(buffer [16]byte) netip.Addr {
var zeroPrefix [12]byte
isIPv4 := bytes.HasPrefix(buffer[:], zeroPrefix[:])
isIPv4 = isIPv4 && !(buffer[12] == 0 && buffer[13] == 0 && buffer[14] == 0 && buffer[15] == 1)
if isIPv4 {
return netip.AddrFrom4([4]byte(buffer[12:16]))
}
return netip.AddrFrom16(buffer)
}
func buildPaddingIP(addr netip.Addr) (buffer [16]byte) {
if addr.Is6() {
return addr.As16()
}
ipv4 := addr.As4()
copy(buffer[12:16], ipv4[:])
return buffer
}
type httpConn struct {
writer io.Writer
flusher http.Flusher
body io.ReadCloser
setupOnce sync.Once
created chan struct{}
createErr error
cancelFn func()
closeFn func()
remoteAddr net.Addr
localAddr net.Addr
deadline *time.Timer
done chan struct{}
}
func (h *httpConn) setup(body io.ReadCloser, err error) {
h.setupOnce.Do(func() {
h.body = body
h.createErr = err
close(h.created)
})
if h.createErr != nil && body != nil {
_ = body.Close()
}
}
func (h *httpConn) waitCreated() error {
<-h.created
if h.body != nil {
return nil
}
return h.createErr
}
func (h *httpConn) Close() error {
h.setup(nil, net.ErrClosed)
if closer, ok := h.writer.(io.Closer); ok {
_ = closer.Close()
}
if h.body != nil {
_ = h.body.Close()
}
if h.cancelFn != nil {
h.cancelFn()
}
if h.closeFn != nil {
h.closeFn()
}
if h.done != nil {
select {
case <-h.done:
default:
close(h.done)
}
}
return nil
}
func (h *httpConn) writeFlush(p []byte) (n int, err error) {
n, err = h.writer.Write(p)
if h.flusher != nil {
h.flusher.Flush()
}
return n, err
}
func (h *httpConn) RemoteAddr() net.Addr {
if h.remoteAddr != nil {
return h.remoteAddr
}
return &net.TCPAddr{}
}
func (h *httpConn) LocalAddr() net.Addr {
if h.localAddr != nil {
return h.localAddr
}
return &net.TCPAddr{}
}
func (h *httpConn) SetDeadline(t time.Time) error {
if t.IsZero() {
if h.deadline != nil {
h.deadline.Stop()
h.deadline = nil
}
return nil
}
d := time.Until(t)
if h.deadline != nil {
h.deadline.Reset(d)
return nil
}
h.deadline = time.AfterFunc(d, func() { h.Close() })
return nil
}
func (h *httpConn) SetReadDeadline(t time.Time) error { return h.SetDeadline(t) }
func (h *httpConn) SetWriteDeadline(t time.Time) error { return h.SetDeadline(t) }
var _ net.Conn = (*tcpConn)(nil)
type tcpConn struct{ httpConn }
func (t *tcpConn) Read(b []byte) (n int, err error) {
if err = t.waitCreated(); err != nil {
return 0, err
}
return t.body.Read(b)
}
func (t *tcpConn) Write(b []byte) (int, error) {
return t.writeFlush(b)
}

View File

@@ -0,0 +1,140 @@
package trusttunnel
import (
"context"
"errors"
"net"
"time"
"github.com/sagernet/quic-go"
"github.com/sagernet/quic-go/congestion"
"github.com/sagernet/quic-go/http3"
"github.com/sagernet/sing-box/common/tls"
E "github.com/sagernet/sing/common/exceptions"
qtls "github.com/sagernet/sing-quic"
"github.com/sagernet/sing-quic/congestion_bbr1"
"github.com/sagernet/sing-quic/congestion_bbr2"
congestion_meta1 "github.com/sagernet/sing-quic/congestion_meta1"
congestion_meta2 "github.com/sagernet/sing-quic/congestion_meta2"
"github.com/sagernet/sing/common/ntp"
)
func NewCongestionControl(name string, cwnd int, bbrProfile string, timeFunc func() time.Time) (func(conn *quic.Conn) congestion.CongestionControl, error) {
if timeFunc == nil {
timeFunc = time.Now
}
if cwnd == 0 {
cwnd = 32
}
switch name {
case "", "bbr":
return func(conn *quic.Conn) congestion.CongestionControl {
return congestion_meta2.NewBbrSender(
congestion_meta2.DefaultClock{TimeFunc: timeFunc},
congestion.ByteCount(conn.Config().InitialPacketSize),
congestion.ByteCount(cwnd)*congestion.ByteCount(conn.Config().InitialPacketSize),
)
}, nil
case "bbr_standard":
return func(conn *quic.Conn) congestion.CongestionControl {
return congestion_bbr1.NewBbrSender(
congestion_bbr1.DefaultClock{TimeFunc: timeFunc},
congestion.ByteCount(conn.Config().InitialPacketSize),
congestion_bbr1.InitialCongestionWindowPackets,
congestion_bbr1.MaxCongestionWindowPackets,
)
}, nil
case "bbr2":
return func(conn *quic.Conn) congestion.CongestionControl {
return congestion_bbr2.NewBBR2Sender(
congestion_bbr2.DefaultClock{TimeFunc: timeFunc},
congestion.ByteCount(conn.Config().InitialPacketSize),
0,
false,
)
}, nil
case "bbr2_variant":
return func(conn *quic.Conn) congestion.CongestionControl {
return congestion_bbr2.NewBBR2Sender(
congestion_bbr2.DefaultClock{TimeFunc: timeFunc},
congestion.ByteCount(conn.Config().InitialPacketSize),
32*congestion.ByteCount(conn.Config().InitialPacketSize),
true,
)
}, nil
case "cubic":
return func(conn *quic.Conn) congestion.CongestionControl {
return congestion_meta1.NewCubicSender(
congestion_meta1.DefaultClock{TimeFunc: timeFunc},
congestion.ByteCount(conn.Config().InitialPacketSize),
false,
)
}, nil
case "reno":
return func(conn *quic.Conn) congestion.CongestionControl {
return congestion_meta1.NewCubicSender(
congestion_meta1.DefaultClock{TimeFunc: timeFunc},
congestion.ByteCount(conn.Config().InitialPacketSize),
true,
)
}, nil
default:
return nil, E.New("unknown congestion control: ", name)
}
}
type QUICService struct {
service *Service
h3Server *http3.Server
udpConn net.PacketConn
congestionControl string
cwnd int
bbrProfile string
}
func NewQUICService(service *Service, congestionControl string, cwnd int, bbrProfile string) *QUICService {
return &QUICService{
service: service,
congestionControl: congestionControl,
cwnd: cwnd,
bbrProfile: bbrProfile,
}
}
func (s *QUICService) Start(ctx context.Context, udpConn net.PacketConn, tlsConfig tls.ServerConfig) error {
s.udpConn = udpConn
congestionControlFactory, err := NewCongestionControl(s.congestionControl, s.cwnd, s.bbrProfile, ntp.TimeFuncFromContext(ctx))
if err != nil {
return err
}
s.h3Server = &http3.Server{
Handler: s.service,
ConnContext: func(ctx context.Context, conn *quic.Conn) context.Context {
conn.SetCongestionControl(congestionControlFactory(conn))
return ctx
},
}
quicListener, err := qtls.ListenEarly(udpConn, tlsConfig, &quic.Config{
MaxIdleTimeout: DefaultSessionTimeout * 2,
MaxIncomingStreams: 1 << 60,
Allow0RTT: true,
})
if err != nil {
return err
}
go func() {
_ = s.h3Server.ServeListener(quicListener)
}()
return nil
}
func (s *QUICService) Close() error {
var errs []error
if s.h3Server != nil {
errs = append(errs, s.h3Server.Close())
}
if s.udpConn != nil {
errs = append(errs, s.udpConn.Close())
}
return errors.Join(errs...)
}

View File

@@ -0,0 +1,218 @@
package trusttunnel
import (
"context"
"io"
"net"
"net/http"
"sync"
"github.com/sagernet/sing/common/auth"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type Handler interface {
N.TCPConnectionHandler
N.UDPConnectionHandler
}
type ServiceOptions struct {
Ctx context.Context
Logger logger.ContextLogger
Handler Handler
}
type Service struct {
ctx context.Context
logger logger.ContextLogger
users map[string]string
handler Handler
conns map[string][]io.Closer
mu sync.RWMutex
}
func NewService(options ServiceOptions) *Service {
return &Service{
ctx: options.Ctx,
logger: options.Logger,
handler: options.Handler,
conns: make(map[string][]io.Closer),
}
}
func (s *Service) UpdateUsers(users map[string]string) {
s.mu.Lock()
s.users = users
var closedConns []io.Closer
for user, conns := range s.conns {
if _, exists := users[user]; !exists {
closedConns = append(closedConns, conns...)
delete(s.conns, user)
}
}
s.mu.Unlock()
for _, conn := range closedConns {
conn.Close()
}
}
func (s *Service) trackConn(username string, conn io.Closer) {
s.mu.Lock()
s.conns[username] = append(s.conns[username], conn)
s.mu.Unlock()
}
func (s *Service) untrackConn(username string, conn io.Closer) {
s.mu.Lock()
conns := s.conns[username]
for i, c := range conns {
if c == conn {
s.conns[username] = append(conns[:i], conns[i+1:]...)
break
}
}
s.mu.Unlock()
}
func (s *Service) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
authorization := request.Header.Get("Proxy-Authorization")
username, loaded := s.verify(authorization)
if !loaded {
writer.WriteHeader(http.StatusProxyAuthRequired)
s.badRequest(request.Context(), request, E.New("authorization failed"))
return
}
if request.Method != http.MethodConnect {
writer.WriteHeader(http.StatusMethodNotAllowed)
s.badRequest(request.Context(), request, E.New("unexpected HTTP method ", request.Method))
return
}
ctx := request.Context()
ctx = auth.ContextWithUser(ctx, username)
switch request.Host {
case UDPMagicAddress:
writer.WriteHeader(http.StatusOK)
flusher, isFlusher := writer.(http.Flusher)
if isFlusher {
flusher.Flush()
}
done := make(chan struct{})
conn := &serverPacketConn{
httpConn: httpConn{
writer: writer,
flusher: flusher,
created: make(chan struct{}),
done: done,
remoteAddr: parseRemoteAddr(request.RemoteAddr),
},
}
conn.setup(request.Body, nil)
firstPacket := buf.NewPacket()
destination, err := conn.ReadPacket(firstPacket)
if err != nil {
firstPacket.Release()
_ = conn.Close()
s.logger.ErrorContext(ctx, E.Cause(err, "read first packet from ", request.RemoteAddr))
return
}
destination = destination.Unwrap()
cachedConn := bufio.NewCachedPacketConn(conn, firstPacket, destination)
s.trackConn(username, conn)
_ = s.handler.NewPacketConnection(ctx, cachedConn, M.Metadata{
Protocol: "trusttunnel",
Source: M.ParseSocksaddr(request.RemoteAddr),
Destination: destination,
})
<-done
s.untrackConn(username, conn)
case HealthCheckMagicAddress:
writer.WriteHeader(http.StatusOK)
if flusher, isFlusher := writer.(http.Flusher); isFlusher {
flusher.Flush()
}
_ = request.Body.Close()
default:
writer.WriteHeader(http.StatusOK)
flusher, isFlusher := writer.(http.Flusher)
if isFlusher {
flusher.Flush()
}
done := make(chan struct{})
conn := &tcpConn{
httpConn{
writer: writer,
flusher: flusher,
created: make(chan struct{}),
done: done,
remoteAddr: parseRemoteAddr(request.RemoteAddr),
},
}
conn.setup(request.Body, nil)
wrapper := &h2ConnWrapper{Conn: conn}
s.trackConn(username, wrapper)
_ = s.handler.NewConnection(ctx, wrapper, M.Metadata{
Protocol: "trusttunnel",
Source: M.ParseSocksaddr(request.RemoteAddr),
Destination: M.ParseSocksaddr(request.Host).Unwrap(),
})
<-done
s.untrackConn(username, wrapper)
wrapper.CloseWrapper()
}
}
func (s *Service) verify(authorization string) (username string, loaded bool) {
username, password, loaded := parseBasicAuth(authorization)
if !loaded {
return "", false
}
s.mu.RLock()
recordedPassword, loaded := s.users[username]
s.mu.RUnlock()
if !loaded {
return "", false
}
if password != recordedPassword {
return "", false
}
return username, true
}
func (s *Service) badRequest(ctx context.Context, request *http.Request, err error) {
s.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", request.RemoteAddr))
}
func parseRemoteAddr(addr string) net.Addr {
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
return nil
}
return tcpAddr
}
type h2ConnWrapper struct {
net.Conn
access sync.Mutex
closed bool
}
func (w *h2ConnWrapper) Write(p []byte) (n int, err error) {
w.access.Lock()
defer w.access.Unlock()
if w.closed {
return 0, net.ErrClosed
}
return w.Conn.Write(p)
}
func (w *h2ConnWrapper) CloseWrapper() {
w.access.Lock()
defer w.access.Unlock()
w.closed = true
}