package vless 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/mux" "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/v2ray" "github.com/sagernet/sing-vmess/packetaddr" "github.com/sagernet/sing-vmess/vless" "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" ) func RegisterOutbound(registry *outbound.Registry) { outbound.Register[option.VLESSOutboundOptions](registry, C.TypeVLESS, NewOutbound) } type Outbound struct { outbound.Adapter logger logger.ContextLogger dialer N.Dialer client *vless.Client serverAddr M.Socksaddr multiplexDialer *mux.Client tlsConfig tls.Config transport adapter.V2RayClientTransport packetAddr bool xudp bool } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } outbound := &Outbound{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVLESS, options.Network.Build(), tag, options.DialerOptions), logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), } if options.TLS != nil { outbound.tlsConfig, err = tls.NewClient(ctx, 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) } } if options.PacketEncoding == nil { outbound.xudp = true } else { switch *options.PacketEncoding { case "": case "packetaddr": outbound.packetAddr = true case "xudp": outbound.xudp = true default: return nil, E.New("unknown packet encoding: ", options.PacketEncoding) } } outbound.client, err = vless.NewClient(options.UUID, options.Flow, logger) if err != nil { return nil, err } outbound.multiplexDialer, err = mux.NewClientWithOptions((*vlessDialer)(outbound), logger, common.PtrValueOrDefault(options.Multiplex)) if err != nil { return nil, err } return outbound, nil } func (h *Outbound) 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 (*vlessDialer)(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 *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.multiplexDialer == nil { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return (*vlessDialer)(h).ListenPacket(ctx, destination) } else { h.logger.InfoContext(ctx, "outbound multiplex packet connection to ", destination) return h.multiplexDialer.ListenPacket(ctx, destination) } } func (h *Outbound) InterfaceUpdated() { if h.transport != nil { h.transport.Close() } if h.multiplexDialer != nil { h.multiplexDialer.Reset() } return } func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) } type vlessDialer Outbound func (h *vlessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(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 = tls.ClientHandshake(ctx, conn, h.tlsConfig) } } if err != nil { return nil, err } switch N.NetworkName(network) { case N.NetworkTCP: h.logger.InfoContext(ctx, "outbound connection to ", destination) return h.client.DialEarlyConn(conn, destination) case N.NetworkUDP: h.logger.InfoContext(ctx, "outbound packet connection to ", destination) if h.xudp { return h.client.DialEarlyXUDPPacketConn(conn, destination) } else if h.packetAddr { if destination.IsFqdn() { return nil, E.New("packetaddr: domain destination is not supported") } packetConn, err := h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}) if err != nil { return nil, err } return bufio.NewBindPacketConn(packetaddr.NewConn(packetConn, destination), destination), nil } else { return h.client.DialEarlyPacketConn(conn, destination) } default: return nil, E.Extend(N.ErrUnknownNetwork, network) } } func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) ctx, metadata := adapter.ExtendContext(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 = tls.ClientHandshake(ctx, conn, h.tlsConfig) } } if err != nil { common.Close(conn) return nil, err } if h.xudp { return h.client.DialEarlyXUDPPacketConn(conn, destination) } else if h.packetAddr { if destination.IsFqdn() { return nil, E.New("packetaddr: domain destination is not supported") } conn, err := h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}) if err != nil { return nil, err } return packetaddr.NewConn(conn, destination), nil } else { return h.client.DialEarlyPacketConn(conn, destination) } }