Files
sing-box-extended/protocol/call/outbound.go

135 lines
3.6 KiB
Go

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
}