Add call protocol, Rmux. Update AmneziaWG. Fixes and improvements

This commit is contained in:
Shtorm
2026-08-06 14:19:34 +03:00
parent 051e928b01
commit d6b9f693c4
89 changed files with 15671 additions and 132 deletions

122
protocol/call/inbound.go Normal file
View File

@@ -0,0 +1,122 @@
package call
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/call"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
)
func RegisterInbound(registry *inbound.Registry) {
inbound.Register[option.CallInboundOptions](registry, C.TypeCall, NewInbound)
}
type Inbound struct {
inbound.Adapter
ctx context.Context
router adapter.ConnectionRouterEx
logger logger.ContextLogger
options option.CallInboundOptions
dialer N.Dialer
bridge *call.Bridge
}
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.CallInboundOptions) (adapter.Inbound, error) {
if options.Platform == "" {
return nil, E.New("missing platform")
}
outboundDialer, err := dialer.New(ctx, options.DialerOptions, true)
if err != nil {
return nil, err
}
return &Inbound{
Adapter: inbound.NewAdapter(C.TypeCall, tag),
ctx: ctx,
router: router,
logger: logger,
options: options,
dialer: outboundDialer,
}, nil
}
func (h *Inbound) Start(stage adapter.StartStage) error {
if stage != adapter.StartStatePostStart {
return nil
}
go h.run()
return nil
}
func (h *Inbound) Close() error {
if h.bridge == nil {
return nil
}
return h.bridge.Close()
}
func (h *Inbound) run() {
dnsRouter := service.FromContext[adapter.DNSRouter](h.ctx)
bridge, err := call.Connect(h.ctx, call.Config{
Platform: h.options.Platform,
Mode: h.options.Mode,
JoinLink: h.options.JoinLink,
CookieString: h.options.Cookies.Header(),
Email: h.options.Email,
Password: h.options.Password,
ReadBuffer: h.options.ReadBuffer,
Role: call.RoleCreator,
Dialer: h.dialer,
DNSRouter: dnsRouter,
Logger: h.logger,
})
if err != nil {
h.logger.ErrorContext(h.ctx, err)
return
}
h.bridge = bridge
bridge.SetAcceptHandler(func(conn net.Conn, destination string) {
h.handleConnection(conn, M.ParseSocksaddr(destination))
})
bridge.SetUDPAcceptHandler(func(conn net.Conn, destination string) {
h.handlePacketConnection(bufio.NewUnbindPacketConnWithAddr(conn, M.ParseSocksaddr(destination)), M.ParseSocksaddr(destination))
})
}
func (h *Inbound) handleConnection(conn net.Conn, destination M.Socksaddr) {
ctx := log.ContextWithNewID(h.ctx)
var metadata adapter.InboundContext
metadata.Inbound = h.Tag()
metadata.InboundType = h.Type()
metadata.Source = M.Socksaddr{}
metadata.Destination = destination
h.logger.InfoContext(ctx, "inbound connection to ", destination)
h.router.RouteConnectionEx(ctx, deadline.NewConn(conn), metadata, N.OnceClose(func(it error) {
conn.Close()
}))
}
func (h *Inbound) handlePacketConnection(conn N.PacketConn, destination M.Socksaddr) {
ctx := log.ContextWithNewID(h.ctx)
var metadata adapter.InboundContext
metadata.Inbound = h.Tag()
metadata.InboundType = h.Type()
metadata.Source = M.Socksaddr{}
metadata.Destination = destination
h.logger.InfoContext(ctx, "inbound packet connection to ", destination)
h.router.RoutePacketConnectionEx(ctx, conn, metadata, N.OnceClose(func(it error) {
conn.Close()
}))
}

134
protocol/call/outbound.go Normal file
View File

@@ -0,0 +1,134 @@
package call
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/call"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
)
func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.CallOutboundOptions](registry, C.TypeCall, NewOutbound)
}
type Outbound struct {
outbound.Adapter
ctx context.Context
logger logger.ContextLogger
options option.CallOutboundOptions
dialer N.Dialer
bridge *call.Bridge
startHandler func()
await chan struct{}
}
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.CallOutboundOptions) (adapter.Outbound, error) {
if options.JoinLink == "" {
return nil, E.New("missing join_link")
}
if options.Platform == "" {
return nil, E.New("missing platform")
}
outboundDialer, err := dialer.New(ctx, options.DialerOptions, true)
if err != nil {
return nil, err
}
ob := &Outbound{
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeCall, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),
ctx: ctx,
logger: logger,
options: options,
dialer: outboundDialer,
await: make(chan struct{}),
}
dnsRouter := service.FromContext[adapter.DNSRouter](ctx)
ob.startHandler = func() {
defer close(ob.await)
bridge, err := call.Connect(ctx, call.Config{
Platform: options.Platform,
Mode: options.Mode,
JoinLink: options.JoinLink,
CookieString: options.Cookies.Header(),
ReadBuffer: options.ReadBuffer,
Role: call.RoleJoiner,
Dialer: outboundDialer,
DNSRouter: dnsRouter,
Logger: logger,
})
if err != nil {
logger.ErrorContext(ctx, err)
return
}
ob.bridge = bridge
}
return ob, nil
}
func (o *Outbound) Start(stage adapter.StartStage) error {
if stage != adapter.StartStatePostStart {
return nil
}
go o.startHandler()
return nil
}
func (o *Outbound) Close() error {
if o.bridge == nil {
return nil
}
return o.bridge.Close()
}
func (o *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if err := o.awaitBridge(ctx); err != nil {
return nil, err
}
switch N.NetworkName(network) {
case N.NetworkTCP:
o.logger.InfoContext(ctx, "outbound connection to ", destination)
conn, err := o.bridge.DialContext(ctx, destination.String())
if err != nil {
return nil, err
}
return deadline.NewConn(conn), nil
default:
return nil, E.New("call: unsupported network: ", network)
}
}
func (o *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if err := o.awaitBridge(ctx); err != nil {
return nil, err
}
o.logger.InfoContext(ctx, "outbound packet connection to ", destination)
conn, err := o.bridge.ListenPacket(ctx, destination.String())
if err != nil {
return nil, err
}
return bufio.NewUnbindPacketConnWithAddr(conn, destination), nil
}
func (o *Outbound) awaitBridge(ctx context.Context) error {
select {
case <-o.await:
case <-ctx.Done():
return ctx.Err()
}
if o.bridge == nil {
return E.New("call: tunnel not initialized")
}
return nil
}

View File

@@ -20,19 +20,14 @@ func newConnWithUploadTrafficLimiter(ctx context.Context, conn net.Conn, limiter
}
func (conn *connWithTrafficLimiter) Write(p []byte) (int, error) {
err := conn.limiter.Can(uint64(len(p)))
if err != nil {
reserved, err := conn.limiter.Reserve(uint64(len(p)))
if reserved < uint64(len(p)) {
conn.limiter.Commit(reserved, 0)
return 0, err
}
n, err := conn.Conn.Write(p)
if err != nil {
return 0, err
}
err = conn.limiter.Add(uint64(n))
if err != nil {
return 0, err
}
return n, nil
conn.limiter.Commit(reserved, uint64(n))
return n, err
}
type connWithUploadTrafficLimiter struct {
@@ -42,19 +37,16 @@ type connWithUploadTrafficLimiter struct {
}
func (conn *connWithUploadTrafficLimiter) Read(p []byte) (int, error) {
err := conn.limiter.Can(1)
if err != nil {
reserved, err := conn.limiter.Reserve(uint64(len(p)))
if reserved == 0 {
return 0, err
}
if reserved < uint64(len(p)) {
p = p[:reserved]
}
n, err := conn.Conn.Read(p)
if err != nil {
return 0, err
}
err = conn.limiter.Add(uint64(n))
if err != nil {
return 0, err
}
return n, nil
conn.limiter.Commit(reserved, uint64(n))
return n, err
}
type packetConnWithTrafficLimiter struct {
@@ -72,19 +64,14 @@ func newPacketConnWithUploadTrafficLimiter(ctx context.Context, conn net.PacketC
}
func (conn *packetConnWithTrafficLimiter) WriteTo(p []byte, addr net.Addr) (int, error) {
err := conn.limiter.Can(uint64(len(p)))
if err != nil {
reserved, err := conn.limiter.Reserve(uint64(len(p)))
if reserved < uint64(len(p)) {
conn.limiter.Commit(reserved, 0)
return 0, err
}
n, err := conn.PacketConn.WriteTo(p, addr)
if err != nil {
return 0, err
}
err = conn.limiter.Add(uint64(n))
if err != nil {
return 0, err
}
return n, nil
conn.limiter.Commit(reserved, uint64(n))
return n, err
}
type packetConnWithUploadTrafficLimiter struct {
@@ -94,19 +81,16 @@ type packetConnWithUploadTrafficLimiter struct {
}
func (conn *packetConnWithUploadTrafficLimiter) ReadFrom(p []byte) (int, net.Addr, error) {
err := conn.limiter.Can(1)
if err != nil {
reserved, err := conn.limiter.Reserve(uint64(len(p)))
if reserved == 0 {
return 0, nil, err
}
if reserved < uint64(len(p)) {
p = p[:reserved]
}
n, addr, err := conn.PacketConn.ReadFrom(p)
if err != nil {
return n, nil, err
}
err = conn.limiter.Add(uint64(n))
if err != nil {
return 0, nil, err
}
return n, addr, nil
conn.limiter.Commit(reserved, uint64(n))
return n, addr, err
}
func connWithDownloadTrafficWrapper(ctx context.Context, conn net.Conn, limiter TrafficLimiter, reverse bool) net.Conn {

View File

@@ -1,6 +1,6 @@
package traffic
type TrafficLimiter interface {
Can(n uint64) error
Add(n uint64) error
Reserve(n uint64) (uint64, error)
Commit(reserved uint64, n uint64)
}

View File

@@ -39,7 +39,7 @@ func (s *DefaultWrapStrategy) wrapConn(ctx context.Context, conn net.Conn, metad
if err != nil {
return conn, err
}
err = limiter.Can(1)
_, err = limiter.Reserve(0)
if err != nil {
return conn, err
}
@@ -51,7 +51,7 @@ func (s *DefaultWrapStrategy) wrapPacketConn(ctx context.Context, conn net.Packe
if err != nil {
return conn, err
}
err = limiter.Can(1)
_, err = limiter.Reserve(0)
if err != nil {
return conn, err
}

View File

@@ -47,7 +47,7 @@ type Outbound struct {
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.MASQUEOutboundOptions) (adapter.Outbound, error) {
outbound := &Outbound{
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeMASQUE, tag, []string{N.NetworkTCP, N.NetworkUDP, N.NetworkICMP}, options.DialerOptions),
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeMASQUE, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),
ctx: ctx,
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
logger: logger,