package socks 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/common" 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/common/uot" "github.com/sagernet/sing/protocol/socks" ) func RegisterOutbound(registry *outbound.Registry) { outbound.Register[option.SOCKSOutboundOptions](registry, C.TypeSOCKS, NewOutbound) } var _ adapter.Outbound = (*Outbound)(nil) type Outbound struct { outbound.Adapter router adapter.Router logger logger.ContextLogger client *socks.Client resolve bool uotClient *uot.Client } func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SOCKSOutboundOptions) (adapter.Outbound, error) { var version socks.Version var err error if options.Version != "" { version, err = socks.ParseVersion(options.Version) } else { version = socks.Version5 } if err != nil { return nil, err } outboundDialer, err := dialer.New(ctx, options.DialerOptions) if err != nil { return nil, err } outbound := &Outbound{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSOCKS, tag, options.Network.Build(), options.DialerOptions), router: router, logger: logger, client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password), resolve: version == socks.Version4, } uotOptions := common.PtrValueOrDefault(options.UDPOverTCP) if uotOptions.Enabled { outbound.uotClient = &uot.Client{ Dialer: outbound.client, Version: uotOptions.Version, } } return outbound, nil } func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination switch N.NetworkName(network) { case N.NetworkTCP: h.logger.InfoContext(ctx, "outbound connection to ", destination) case N.NetworkUDP: if h.uotClient != nil { h.logger.InfoContext(ctx, "outbound UoT connect packet connection to ", destination) return h.uotClient.DialContext(ctx, network, destination) } h.logger.InfoContext(ctx, "outbound packet connection to ", destination) default: return nil, E.Extend(N.ErrUnknownNetwork, network) } if h.resolve && destination.IsFqdn() { destinationAddresses, err := h.router.LookupDefault(ctx, destination.Fqdn) if err != nil { return nil, err } return N.DialSerial(ctx, h.client, network, destination, destinationAddresses) } return h.client.DialContext(ctx, network, destination) } func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) metadata.Outbound = h.Tag() metadata.Destination = destination if h.uotClient != nil { h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) return h.uotClient.ListenPacket(ctx, destination) } if h.resolve && destination.IsFqdn() { destinationAddresses, err := h.router.LookupDefault(ctx, destination.Fqdn) if err != nil { return nil, err } packetConn, _, err := N.ListenSerial(ctx, h.client, destination, destinationAddresses) if err != nil { return nil, err } return packetConn, nil } h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return h.client.ListenPacket(ctx, destination) }