diff --git a/app/dispatcher/default.go b/app/dispatcher/default.go index e6e21f0ba..a1f58db18 100644 --- a/app/dispatcher/default.go +++ b/app/dispatcher/default.go @@ -294,6 +294,19 @@ func sniffer(ctx context.Context, cReader *cachedReader, metadataOnly bool) (Sni func (d *DefaultDispatcher) routedDispatch(ctx context.Context, link *transport.Link, destination net.Destination) { var handler outbound.Handler + if forcedOutboundTag := session.GetForcedOutboundTagFromContext(ctx); forcedOutboundTag != "" { + session.SetForcedOutboundTagToContext(ctx, "") + if h := d.ohm.GetHandler(forcedOutboundTag); h != nil { + newError("taking platform initialized detour [", forcedOutboundTag, "] for [", destination, "]").WriteToLog(session.ExportIDToError(ctx)) + handler = h + } else { + newError("non existing tag for platform initialized detour: ", forcedOutboundTag).AtError().WriteToLog(session.ExportIDToError(ctx)) + common.Close(link.Writer) + common.Interrupt(link.Reader) + return + } + } + if d.router != nil { if route, err := d.router.PickRoute(routing_session.AsRoutingContext(ctx)); err == nil { tag := route.GetOutboundTag() diff --git a/app/proxyman/outbound/handler.go b/app/proxyman/outbound/handler.go index 5e5ba1216..66477c2d4 100644 --- a/app/proxyman/outbound/handler.go +++ b/app/proxyman/outbound/handler.go @@ -159,7 +159,7 @@ func (h *Handler) Address() net.Address { // Dial implements internet.Dialer. func (h *Handler) Dial(ctx context.Context, dest net.Destination) (internet.Connection, error) { if h.senderSettings != nil { - if h.senderSettings.ProxySettings.HasTag() { + if h.senderSettings.ProxySettings.HasTag() && !h.senderSettings.ProxySettings.TransportLayerProxy { tag := h.senderSettings.ProxySettings.Tag handler := h.outboundManager.GetHandler(tag) if handler != nil { @@ -196,6 +196,12 @@ func (h *Handler) Dial(ctx context.Context, dest net.Destination) (internet.Conn } } + if h.senderSettings.ProxySettings.HasTag() && h.senderSettings.ProxySettings.TransportLayerProxy { + tag := h.senderSettings.ProxySettings.Tag + newError("transport layer proxying to ", tag, " for dest ", dest).AtDebug().WriteToLog(session.ExportIDToError(ctx)) + session.SetTransportLayerProxyTagToContext(ctx, tag) + } + conn, err := internet.Dial(ctx, dest, h.streamSettings) return h.getStatCouterConnection(conn), err } diff --git a/common/session/context.go b/common/session/context.go index 7f1d7df9b..116c52a90 100644 --- a/common/session/context.go +++ b/common/session/context.go @@ -84,3 +84,19 @@ func SockoptFromContext(ctx context.Context) *Sockopt { } return nil } + +func GetTransportLayerProxyTagFromContext(ctx context.Context) string { + return ContentFromContext(ctx).Attribute("transportLayerOutgoingTag") +} + +func SetTransportLayerProxyTagToContext(ctx context.Context, tag string) { + ContentFromContext(ctx).SetAttribute("transportLayerOutgoingTag", tag) +} + +func GetForcedOutboundTagFromContext(ctx context.Context) string { + return ContentFromContext(ctx).Attribute("forcedOutboundTag") +} + +func SetForcedOutboundTagToContext(ctx context.Context, tag string) { + ContentFromContext(ctx).SetAttribute("forcedOutboundTag", tag) +} diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go index 9a1a04d06..301f324de 100644 --- a/infra/conf/transport_internet.go +++ b/infra/conf/transport_internet.go @@ -485,7 +485,8 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) { } type ProxyConfig struct { - Tag string `json:"tag"` + Tag string `json:"tag"` + TransportLayerProxy bool `json:"transportLayer"` } // Build implements Buildable. @@ -494,6 +495,7 @@ func (v *ProxyConfig) Build() (*internet.ProxyConfig, error) { return nil, newError("Proxy tag is not set.") } return &internet.ProxyConfig{ - Tag: v.Tag, + Tag: v.Tag, + TransportLayerProxy: v.TransportLayerProxy, }, nil } diff --git a/transport/internet/dialer.go b/transport/internet/dialer.go index 78a45fdf8..8a93bf3ad 100644 --- a/transport/internet/dialer.go +++ b/transport/internet/dialer.go @@ -2,6 +2,8 @@ package internet import ( "context" + core "github.com/v2fly/v2ray-core/v4" + "github.com/v2fly/v2ray-core/v4/features/routing" "github.com/v2fly/v2ray-core/v4/common/net" "github.com/v2fly/v2ray-core/v4/common/session" @@ -68,5 +70,37 @@ func DialSystem(ctx context.Context, dest net.Destination, sockopt *SocketConfig if outbound := session.OutboundFromContext(ctx); outbound != nil { src = outbound.Gateway } + + if transportLayerOutgoingTag := session.GetTransportLayerProxyTagFromContext(ctx); transportLayerOutgoingTag != "" { + return DialTaggedOutbound(ctx, dest, transportLayerOutgoingTag) + } + return effectiveSystemDialer.Dial(ctx, src, dest, sockopt) } + +func DialTaggedOutbound(ctx context.Context, dest net.Destination, tag string) (net.Conn, error) { + var dispatcher routing.Dispatcher + if err := core.RequireFeatures(ctx, func(dispatcherInstance routing.Dispatcher) { + dispatcher = dispatcherInstance + }); err != nil { + return nil, newError("Required Feature dispatcher not resolved").Base(err) + } + + content := new(session.Content) + content.SkipDNSResolve = true + session.SetForcedOutboundTagToContext(ctx, tag) + + ctx = session.ContextWithContent(ctx, content) + + r, err := dispatcher.Dispatch(ctx, dest) + if err != nil { + return nil, err + } + var readerOpt net.ConnectionOption + if dest.Network == net.Network_TCP { + readerOpt = net.ConnectionOutputMulti(r.Reader) + } else { + readerOpt = net.ConnectionOutputMultiUDP(r.Reader) + } + return net.NewConnection(net.ConnectionInputMulti(r.Writer), readerOpt), nil +}