package outbound import ( "context" "crypto/tls" "net" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" 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/v2ray" "github.com/sagernet/sing/common" "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/protocol/trojan" ) var _ adapter.Outbound = (*Trojan)(nil) type Trojan struct { myOutboundAdapter dialer N.Dialer serverAddr M.Socksaddr key [56]byte multiplexDialer N.Dialer tlsConfig *tls.Config transport adapter.V2RayClientTransport } func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (*Trojan, error) { outbound := &Trojan{ myOutboundAdapter: myOutboundAdapter{ protocol: C.TypeTrojan, network: options.Network.Build(), router: router, logger: logger, tag: tag, }, dialer: dialer.New(router, options.DialerOptions), serverAddr: options.ServerOptions.Build(), key: trojan.Key(options.Password), } var err error if options.TLS != nil { outbound.tlsConfig, err = dialer.TLSConfig(options.Server, common.PtrValueOrDefault(options.TLS)) if err != nil { return nil, err } } if options.Transport != nil { outbound.transport, err = v2ray.NewClientTransport(ctx, outbound.dialer, outbound.serverAddr, common.PtrValueOrDefault(options.Transport), outbound.tlsConfig) if err != nil { return nil, E.Cause(err, "create client transport: ", options.Transport.Type) } } outbound.multiplexDialer, err = mux.NewClientWithOptions(ctx, (*trojanDialer)(outbound), common.PtrValueOrDefault(options.Multiplex)) if err != nil { return nil, err } return outbound, nil } func (h *Trojan) 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 (*trojanDialer)(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 *Trojan) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.multiplexDialer == nil { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return (*trojanDialer)(h).ListenPacket(ctx, destination) } else { h.logger.InfoContext(ctx, "outbound multiplex packet connection to ", destination) return h.multiplexDialer.ListenPacket(ctx, destination) } } func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { return NewEarlyConnection(ctx, h, conn, metadata) } func (h *Trojan) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { return NewPacketConnection(ctx, h, conn, metadata) } type trojanDialer Trojan func (h *trojanDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.AppendContext(ctx) metadata.Outbound = h.tag metadata.Destination = destination var conn net.Conn var err error if h.transport != nil { conn, err = h.transport.DialContext(ctx) } else { conn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr) if err == nil && h.tlsConfig != nil { conn, err = dialer.TLSClient(ctx, conn, h.tlsConfig) } } if err != nil { return nil, err } switch N.NetworkName(network) { case N.NetworkTCP: return trojan.NewClientConn(conn, h.key, destination), nil case N.NetworkUDP: return &bufio.BindPacketConn{PacketConn: trojan.NewClientPacketConn(conn, h.key), Addr: destination}, nil default: return nil, E.Extend(N.ErrUnknownNetwork, network) } } func (h *trojanDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { conn, err := h.DialContext(ctx, N.NetworkUDP, destination) if err != nil { return nil, err } return conn.(net.PacketConn), nil }