package trusttunnel import ( "context" "net" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/tls" 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/trusttunnel" "github.com/sagernet/sing/common" "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" "golang.org/x/net/http2" ) func RegisterOutbound(registry *outbound.Registry) { outbound.Register[option.TrustTunnelOutboundOptions](registry, C.TypeTrustTunnel, NewOutbound) } type Outbound struct { outbound.Adapter logger logger.ContextLogger client trusttunnel.Dialer } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrustTunnelOutboundOptions) (adapter.Outbound, error) { if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired } outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) if err != nil { return nil, err } serverAddr := options.ServerOptions.Build() networkList := options.Network.Build() clientOpts := trusttunnel.ClientOptions{ Server: serverAddr, Username: options.Username, Password: options.Password, QUIC: options.QUIC, CongestionControl: options.CongestionController, CWND: options.CWND, BBRProfile: options.BBRProfile, HealthCheck: options.HealthCheck, } if options.QUIC { tlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } if len(tlsConfig.NextProtos()) == 0 { tlsConfig.SetNextProtos([]string{"h3"}) } clientOpts.QUICDialer = outboundDialer clientOpts.QUICTLSConfig = tlsConfig } else { tlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } if len(tlsConfig.NextProtos()) == 0 { tlsConfig.SetNextProtos([]string{http2.NextProtoTLS}) } clientOpts.TLSDialer = tls.NewDialer(outboundDialer, tlsConfig) } var client trusttunnel.Dialer if options.Multiplex != nil && options.Multiplex.Enabled { clientOpts.MaxConnections = options.Multiplex.MaxConnections clientOpts.MinStreams = options.Multiplex.MinStreams clientOpts.MaxStreams = options.Multiplex.MaxStreams client, err = trusttunnel.NewMultiplexClient(ctx, clientOpts) } else { client, err = trusttunnel.NewClient(ctx, clientOpts) } if err != nil { return nil, err } return &Outbound{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTrustTunnel, tag, networkList, options.DialerOptions), logger: logger, client: client, }, nil } func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch N.NetworkName(network) { case N.NetworkTCP: h.logger.InfoContext(ctx, "outbound connection to ", destination) return h.client.Dial(ctx, destination.String()) case N.NetworkUDP: h.logger.InfoContext(ctx, "outbound packet connection to ", destination) conn, err := h.client.ListenPacket(ctx) if err != nil { return nil, err } return bufio.NewBindPacketConn(conn, destination), nil default: return nil, E.New("unsupported network: ", network) } } func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return h.client.ListenPacket(ctx) } func (h *Outbound) Close() error { return h.client.Close() }