refactor: Modular inbounds/outbounds

This commit is contained in:
世界
2024-11-02 00:39:02 +08:00
parent 3f143d6fc9
commit 6248abb6d4
151 changed files with 3115 additions and 2925 deletions

View File

@@ -0,0 +1,179 @@
package shadowsocks
import (
"context"
"net"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/common/listener"
"github.com/sagernet/sing-box/common/mux"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-shadowsocks"
"github.com/sagernet/sing-shadowsocks/shadowaead"
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
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/ntp"
)
func RegisterInbound(registry *inbound.Registry) {
inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocks, NewInbound)
}
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {
if len(options.Users) > 0 && len(options.Destinations) > 0 {
return nil, E.New("users and destinations options must not be combined")
}
if len(options.Users) > 0 {
return newMultiInbound(ctx, router, logger, tag, options)
} else if len(options.Destinations) > 0 {
return newRelayInbound(ctx, router, logger, tag, options)
} else {
return newInbound(ctx, router, logger, tag, options)
}
}
var _ adapter.TCPInjectableInbound = (*Inbound)(nil)
type Inbound struct {
inbound.Adapter
ctx context.Context
router adapter.ConnectionRouterEx
logger logger.ContextLogger
listener *listener.Listener
service shadowsocks.Service
}
func newInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*Inbound, error) {
inbound := &Inbound{
Adapter: inbound.NewAdapter(C.TypeShadowsocks, tag),
ctx: ctx,
router: uot.NewRouter(router, logger),
logger: logger,
}
var err error
inbound.router, err = mux.NewRouterWithOptions(router, logger, common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}
var udpTimeout time.Duration
if options.UDPTimeout != 0 {
udpTimeout = time.Duration(options.UDPTimeout)
} else {
udpTimeout = C.UDPTimeout
}
switch {
case options.Method == shadowsocks.MethodNone:
inbound.service = shadowsocks.NewNoneService(int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound))
case common.Contains(shadowaead.List, options.Method):
inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound))
case common.Contains(shadowaead_2022.List, options.Method):
inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), ntp.TimeFuncFromContext(ctx))
default:
err = E.New("unsupported method: ", options.Method)
}
inbound.listener = listener.New(listener.Options{
Context: ctx,
Logger: logger,
Network: options.Network.Build(),
Listen: options.ListenOptions,
ConnectionHandler: inbound,
PacketHandler: inbound,
ThreadUnsafePacketWriter: true,
})
return inbound, err
}
func (h *Inbound) Start() error {
return h.listener.Start()
}
func (h *Inbound) Close() error {
return h.listener.Close()
}
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata))
N.CloseOnHandshakeFailure(conn, onClose, err)
if err != nil {
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))
}
}
func (h *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {
err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source})
if err != nil {
h.logger.Error(E.Cause(err, "process packet from ", source))
}
}
func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
metadata.Inbound = h.Tag()
metadata.InboundType = h.Type()
return h.router.RouteConnection(ctx, conn, metadata)
}
func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
ctx = log.ContextWithNewID(ctx)
h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
metadata.Inbound = h.Tag()
metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
return h.router.RoutePacketConnection(ctx, conn, metadata)
}
var _ N.PacketConn = (*stubPacketConn)(nil)
type stubPacketConn struct {
N.PacketWriter
}
func (c *stubPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
panic("stub!")
}
func (c *stubPacketConn) Close() error {
return nil
}
func (c *stubPacketConn) LocalAddr() net.Addr {
panic("stub!")
}
func (c *stubPacketConn) SetDeadline(t time.Time) error {
panic("stub!")
}
func (c *stubPacketConn) SetReadDeadline(t time.Time) error {
panic("stub!")
}
func (c *stubPacketConn) SetWriteDeadline(t time.Time) error {
panic("stub!")
}
func (h *Inbound) NewError(ctx context.Context, err error) {
NewError(h.logger, ctx, err)
}
// Deprecated: remove
func NewError(logger logger.ContextLogger, ctx context.Context, err error) {
common.Close(err)
if E.IsClosedOrCanceled(err) {
logger.DebugContext(ctx, "connection closed: ", err)
return
}
logger.ErrorContext(ctx, err)
}

View File

@@ -0,0 +1,167 @@
package shadowsocks
import (
"context"
"net"
"os"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/common/listener"
"github.com/sagernet/sing-box/common/mux"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-shadowsocks"
"github.com/sagernet/sing-shadowsocks/shadowaead"
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"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/ntp"
)
var _ adapter.TCPInjectableInbound = (*MultiInbound)(nil)
type MultiInbound struct {
inbound.Adapter
ctx context.Context
router adapter.ConnectionRouterEx
logger logger.ContextLogger
listener *listener.Listener
service shadowsocks.MultiService[int]
users []option.ShadowsocksUser
}
func newMultiInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*MultiInbound, error) {
inbound := &MultiInbound{
Adapter: inbound.NewAdapter(C.TypeShadowsocks, tag),
ctx: ctx,
router: uot.NewRouter(router, logger),
logger: logger,
}
var err error
inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}
var udpTimeout time.Duration
if options.UDPTimeout != 0 {
udpTimeout = time.Duration(options.UDPTimeout)
} else {
udpTimeout = C.UDPTimeout
}
var service shadowsocks.MultiService[int]
if common.Contains(shadowaead_2022.List, options.Method) {
service, err = shadowaead_2022.NewMultiServiceWithPassword[int](
options.Method,
options.Password,
int64(udpTimeout.Seconds()),
adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound),
ntp.TimeFuncFromContext(ctx),
)
} else if common.Contains(shadowaead.List, options.Method) {
service, err = shadowaead.NewMultiService[int](
options.Method,
int64(udpTimeout.Seconds()),
adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound),
)
} else {
return nil, E.New("unsupported method: " + options.Method)
}
if err != nil {
return nil, err
}
err = service.UpdateUsersWithPasswords(common.MapIndexed(options.Users, func(index int, user option.ShadowsocksUser) int {
return index
}), common.Map(options.Users, func(user option.ShadowsocksUser) string {
return user.Password
}))
if err != nil {
return nil, err
}
inbound.service = service
inbound.users = options.Users
inbound.listener = listener.New(listener.Options{
Context: ctx,
Logger: logger,
Network: options.Network.Build(),
Listen: options.ListenOptions,
ConnectionHandler: inbound,
PacketHandler: inbound,
ThreadUnsafePacketWriter: true,
})
return inbound, err
}
func (h *MultiInbound) Start() error {
return h.listener.Start()
}
func (h *MultiInbound) Close() error {
return h.listener.Close()
}
func (h *MultiInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata))
N.CloseOnHandshakeFailure(conn, onClose, err)
if err != nil {
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))
}
}
func (h *MultiInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {
err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source})
if err != nil {
h.logger.Error(E.Cause(err, "process packet from ", source))
}
}
func (h *MultiInbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
userIndex, loaded := auth.UserFromContext[int](ctx)
if !loaded {
return os.ErrInvalid
}
user := h.users[userIndex].Name
if user == "" {
user = F.ToString(userIndex)
} else {
metadata.User = user
}
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
metadata.Inbound = h.Tag()
metadata.InboundType = h.Type()
return h.router.RouteConnection(ctx, conn, metadata)
}
func (h *MultiInbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
userIndex, loaded := auth.UserFromContext[int](ctx)
if !loaded {
return os.ErrInvalid
}
user := h.users[userIndex].Name
if user == "" {
user = F.ToString(userIndex)
} else {
metadata.User = user
}
ctx = log.ContextWithNewID(ctx)
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection from ", metadata.Source)
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
metadata.Inbound = h.Tag()
metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
return h.router.RoutePacketConnection(ctx, conn, metadata)
}
func (h *MultiInbound) NewError(ctx context.Context, err error) {
NewError(h.logger, ctx, err)
}

View File

@@ -0,0 +1,152 @@
package shadowsocks
import (
"context"
"net"
"os"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/common/listener"
"github.com/sagernet/sing-box/common/mux"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
var _ adapter.TCPInjectableInbound = (*RelayInbound)(nil)
type RelayInbound struct {
inbound.Adapter
ctx context.Context
router adapter.ConnectionRouterEx
logger logger.ContextLogger
listener *listener.Listener
service *shadowaead_2022.RelayService[int]
destinations []option.ShadowsocksDestination
}
func newRelayInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*RelayInbound, error) {
inbound := &RelayInbound{
Adapter: inbound.NewAdapter(C.TypeShadowsocks, tag),
ctx: ctx,
router: uot.NewRouter(router, logger),
logger: logger,
destinations: options.Destinations,
}
var err error
inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}
var udpTimeout time.Duration
if options.UDPTimeout != 0 {
udpTimeout = time.Duration(options.UDPTimeout)
} else {
udpTimeout = C.UDPTimeout
}
service, err := shadowaead_2022.NewRelayServiceWithPassword[int](
options.Method,
options.Password,
int64(udpTimeout.Seconds()),
adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound),
)
if err != nil {
return nil, err
}
err = service.UpdateUsersWithPasswords(common.MapIndexed(options.Destinations, func(index int, user option.ShadowsocksDestination) int {
return index
}), common.Map(options.Destinations, func(user option.ShadowsocksDestination) string {
return user.Password
}), common.Map(options.Destinations, option.ShadowsocksDestination.Build))
if err != nil {
return nil, err
}
inbound.service = service
inbound.listener = listener.New(listener.Options{
Context: ctx,
Logger: logger,
Network: options.Network.Build(),
Listen: options.ListenOptions,
ConnectionHandler: inbound,
PacketHandler: inbound,
ThreadUnsafePacketWriter: true,
})
return inbound, err
}
func (h *RelayInbound) Start() error {
return h.listener.Start()
}
func (h *RelayInbound) Close() error {
return h.listener.Close()
}
func (h *RelayInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata))
N.CloseOnHandshakeFailure(conn, onClose, err)
if err != nil {
h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source))
}
}
func (h *RelayInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {
err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source})
if err != nil {
h.logger.Error(E.Cause(err, "process packet from ", source))
}
}
func (h *RelayInbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
destinationIndex, loaded := auth.UserFromContext[int](ctx)
if !loaded {
return os.ErrInvalid
}
destination := h.destinations[destinationIndex].Name
if destination == "" {
destination = F.ToString(destinationIndex)
} else {
metadata.User = destination
}
h.logger.InfoContext(ctx, "[", destination, "] inbound connection to ", metadata.Destination)
metadata.Inbound = h.Tag()
metadata.InboundType = h.Type()
return h.router.RouteConnection(ctx, conn, metadata)
}
func (h *RelayInbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
destinationIndex, loaded := auth.UserFromContext[int](ctx)
if !loaded {
return os.ErrInvalid
}
destination := h.destinations[destinationIndex].Name
if destination == "" {
destination = F.ToString(destinationIndex)
} else {
metadata.User = destination
}
ctx = log.ContextWithNewID(ctx)
h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection from ", metadata.Source)
h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection to ", metadata.Destination)
metadata.Inbound = h.Tag()
metadata.InboundType = h.Type()
metadata.InboundDetour = h.listener.ListenOptions().Detour
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
return h.router.RoutePacketConnection(ctx, conn, metadata)
}
func (h *RelayInbound) NewError(ctx context.Context, err error) {
NewError(h.logger, ctx, err)
}

View File

@@ -0,0 +1,179 @@
package shadowsocks
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"
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/sip003"
"github.com/sagernet/sing-shadowsocks2"
"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"
"github.com/sagernet/sing/common/uot"
)
func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.ShadowsocksOutboundOptions](registry, C.TypeShadowsocks, NewOutbound)
}
type Outbound struct {
outbound.Adapter
logger logger.ContextLogger
dialer N.Dialer
method shadowsocks.Method
serverAddr M.Socksaddr
plugin sip003.Plugin
uotClient *uot.Client
multiplexDialer *mux.Client
}
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (adapter.Outbound, error) {
method, err := shadowsocks.CreateMethod(ctx, options.Method, shadowsocks.MethodOptions{
Password: options.Password,
})
if err != nil {
return nil, err
}
outboundDialer, err := dialer.New(router, options.DialerOptions)
if err != nil {
return nil, err
}
outbound := &Outbound{
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowsocks, options.Network.Build(), tag, options.DialerOptions),
logger: logger,
dialer: outboundDialer,
method: method,
serverAddr: options.ServerOptions.Build(),
}
if options.Plugin != "" {
outbound.plugin, err = sip003.CreatePlugin(ctx, options.Plugin, options.PluginOptions, router, outbound.dialer, outbound.serverAddr)
if err != nil {
return nil, err
}
}
uotOptions := common.PtrValueOrDefault(options.UDPOverTCP)
if !uotOptions.Enabled {
outbound.multiplexDialer, err = mux.NewClientWithOptions((*shadowsocksDialer)(outbound), logger, common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}
}
if uotOptions.Enabled {
outbound.uotClient = &uot.Client{
Dialer: (*shadowsocksDialer)(outbound),
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
if h.multiplexDialer == nil {
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)
} else {
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
}
}
return (*shadowsocksDialer)(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) {
ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag()
metadata.Destination = destination
if h.multiplexDialer == nil {
if h.uotClient != nil {
h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination)
return h.uotClient.ListenPacket(ctx, destination)
} else {
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
}
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
return (*shadowsocksDialer)(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.multiplexDialer != nil {
h.multiplexDialer.Reset()
}
return
}
func (h *Outbound) Close() error {
return common.Close(common.PtrOrNil(h.multiplexDialer))
}
var _ N.Dialer = (*shadowsocksDialer)(nil)
type shadowsocksDialer Outbound
func (h *shadowsocksDialer) 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:
var outConn net.Conn
var err error
if h.plugin != nil {
outConn, err = h.plugin.DialContext(ctx)
} else {
outConn, err = h.dialer.DialContext(ctx, N.NetworkTCP, h.serverAddr)
}
if err != nil {
return nil, err
}
return h.method.DialEarlyConn(outConn, destination), nil
case N.NetworkUDP:
outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr)
if err != nil {
return nil, err
}
return bufio.NewBindPacketConn(h.method.DialPacketConn(outConn), destination), nil
default:
return nil, E.Extend(N.ErrUnknownNetwork, network)
}
}
func (h *shadowsocksDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.Tag()
metadata.Destination = destination
outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr)
if err != nil {
return nil, err
}
return h.method.DialPacketConn(outConn), nil
}