diff --git a/transport/internet/tlsmirror/interface.go b/transport/internet/tlsmirror/interface.go index 24f3c6a20..40767ae05 100644 --- a/transport/internet/tlsmirror/interface.go +++ b/transport/internet/tlsmirror/interface.go @@ -88,3 +88,7 @@ type ConnectionEnrollmentConfirmationServerInstanceConfig struct { type ConnectionEnrollmentConfirmationServerInstanceConfigReceiver interface { OnConnectionEnrollmentConfirmationServerInstanceConfigReady(ConnectionEnrollmentConfirmationServerInstanceConfig) } + +type ConnectionLoopbackPrevention struct { + Key string +} diff --git a/transport/internet/tlsmirror/mirrorcommon/loopback_protect.go b/transport/internet/tlsmirror/mirrorcommon/loopback_protect.go new file mode 100644 index 000000000..120426f50 --- /dev/null +++ b/transport/internet/tlsmirror/mirrorcommon/loopback_protect.go @@ -0,0 +1,19 @@ +package mirrorcommon + +import ( + "context" + + "github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror" +) + +func SetLoopbackProtectionFlagForContext(ctx context.Context, enrollmentID []byte) context.Context { + loopbackProtectionKey := tlsmirror.ConnectionLoopbackPrevention{Key: string(enrollmentID)} + return context.WithValue(ctx, loopbackProtectionKey, true) +} + +func IsLoopbackProtectionEnabled(ctx context.Context, enrollmentID []byte) bool { + loopbackProtectionKey := tlsmirror.ConnectionLoopbackPrevention{Key: string(enrollmentID)} + val := ctx.Value(loopbackProtectionKey) + enabled, ok := val.(bool) + return ok && enabled +} diff --git a/transport/internet/tlsmirror/mirrorenrollment/client.go b/transport/internet/tlsmirror/mirrorenrollment/client.go index e21cbdb7e..e7b291103 100644 --- a/transport/internet/tlsmirror/mirrorenrollment/client.go +++ b/transport/internet/tlsmirror/mirrorenrollment/client.go @@ -4,11 +4,13 @@ import ( "context" "net" + "github.com/v2fly/v2ray-core/v5/common" "github.com/v2fly/v2ray-core/v5/common/environment" "github.com/v2fly/v2ray-core/v5/common/environment/envctx" v2net "github.com/v2fly/v2ray-core/v5/common/net" "github.com/v2fly/v2ray-core/v5/common/serial" "github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror" + "github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/mirrorcommon" "github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/mirrorenrollment/httpenrollmentconfirmation" ) @@ -78,7 +80,9 @@ func (c *EnrollmentConfirmationClient) init() error { return nil, newError("failed to parse destination address").Base(err).AtError() } dest.Network = v2net.Network_TCP - return dialer(c.ctx, dest, c.config.PrimaryEgressOutbound) + + loopbackProtectedCtx := mirrorcommon.SetLoopbackProtectionFlagForContext(c.ctx, c.serverIdentity) + return dialer(loopbackProtectedCtx, dest, c.config.PrimaryEgressOutbound) }, c.serverIdentity) if err != nil { return newError("failed to create HTTP round tripper for enrollment confirmation").Base(err).AtError() @@ -93,7 +97,14 @@ func (c *EnrollmentConfirmationClient) init() error { if err != nil { return newError("failed to get instance of bootstrap enrollment confirmation config").Base(err).AtError() } - enrollmentConfirmation, ok := enrollment.(tlsmirror.ConnectionEnrollmentConfirmation) + + loopbackProtectedCtx := mirrorcommon.SetLoopbackProtectionFlagForContext(c.ctx, c.serverIdentity) + enrollmentInst, err := common.CreateObject(loopbackProtectedCtx, enrollment) + if err != nil { + return newError("failed to create bootstrap enrollment confirmation config").Base(err).AtError() + } + + enrollmentConfirmation, ok := enrollmentInst.(tlsmirror.ConnectionEnrollmentConfirmation) if !ok { return newError("bootstrap enrollment confirmation config is not a valid ConnectionEnrollmentConfirmation") diff --git a/transport/internet/tlsmirror/mirrorenrollment/httpenrollmentconfirmation/client.go b/transport/internet/tlsmirror/mirrorenrollment/httpenrollmentconfirmation/client.go index b8f7ea277..07a7c557d 100644 --- a/transport/internet/tlsmirror/mirrorenrollment/httpenrollmentconfirmation/client.go +++ b/transport/internet/tlsmirror/mirrorenrollment/httpenrollmentconfirmation/client.go @@ -37,10 +37,14 @@ func (c *client) VerifyConnectionEnrollment(req *tlsmirror.EnrollmentConfirmatio return nil, newError("failed to create HTTP request").Base(err) } httpResp, err := c.httpRoundTripper.RoundTrip(httpReq) + defer func() { + if httpResp != nil && httpResp.Body != nil { + httpResp.Body.Close() + } + }() if err != nil { return nil, newError("failed to send HTTP request").Base(err) } - defer httpResp.Body.Close() if httpResp.StatusCode != http.StatusOK { return nil, newError("unexpected HTTP response status: ", httpResp.StatusCode) } diff --git a/transport/internet/tlsmirror/mirrorenrollment/httpenrollmentconfirmation/clientbuilder.go b/transport/internet/tlsmirror/mirrorenrollment/httpenrollmentconfirmation/clientbuilder.go index e32bd4746..b5bacfaf0 100644 --- a/transport/internet/tlsmirror/mirrorenrollment/httpenrollmentconfirmation/clientbuilder.go +++ b/transport/internet/tlsmirror/mirrorenrollment/httpenrollmentconfirmation/clientbuilder.go @@ -52,17 +52,19 @@ func (c *clientRoundtripper) RoundTrip(request *http.Request) (*http.Response, e resp, err := c.currentConn.RoundTrip(request) if err != nil { defer func() { + c.currentConnLock.RUnlock() c.currentConnLock.Lock() - defer c.currentConnLock.Unlock() if c.currentConn != nil { c.currentConnInnerConn.Close() c.currentConnInnerConn = nil c.currentConn = nil } + c.currentConnLock.Unlock() + c.currentConnLock.RLock() }() return nil, newError("unable to roundtrip for enrollment verification").Base(err) } - return resp, err + return resp, nil } func (c *clientRoundtripper) createNewConnection() error { diff --git a/transport/internet/tlsmirror/mirrorenrollment/roundtripperenrollmentconfirmation/client.go b/transport/internet/tlsmirror/mirrorenrollment/roundtripperenrollmentconfirmation/client.go index 036acba84..1c226d910 100644 --- a/transport/internet/tlsmirror/mirrorenrollment/roundtripperenrollmentconfirmation/client.go +++ b/transport/internet/tlsmirror/mirrorenrollment/roundtripperenrollmentconfirmation/client.go @@ -26,11 +26,16 @@ func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) { return nil, newError("config cannot be nil") } - rttClientI, err := serial.GetInstanceOf(config.RoundTripperClient) + rttClientConfig, err := serial.GetInstanceOf(config.RoundTripperClient) if err != nil { return nil, newError("failed to get instance of RoundTripperClient").Base(err) } + rttClientI, err := common.CreateObject(ctx, rttClientConfig) + if err != nil { + return nil, newError("failed to create RoundTripperClient").Base(err) + } + rttClient, ok := rttClientI.(request.RoundTripperClient) if !ok { return nil, newError("RoundTripperClient is not a valid request.RoundTripperClient") diff --git a/transport/internet/tlsmirror/mirrorenrollment/roundtripperenrollmentconfirmation/server.go b/transport/internet/tlsmirror/mirrorenrollment/roundtripperenrollmentconfirmation/server.go index a326b238a..560eb37ce 100644 --- a/transport/internet/tlsmirror/mirrorenrollment/roundtripperenrollmentconfirmation/server.go +++ b/transport/internet/tlsmirror/mirrorenrollment/roundtripperenrollmentconfirmation/server.go @@ -4,6 +4,7 @@ import ( "context" "net" + "github.com/v2fly/v2ray-core/v5/common/serial" "google.golang.org/protobuf/proto" "github.com/v2fly/v2ray-core/v5/common" @@ -14,12 +15,16 @@ import ( "github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror" ) -func NewServer(ctx context.Context, config *ServerConfig) *Server { +func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) { s := &Server{ ctx: ctx, config: config, } - return s + + if err := s.init(); err != nil { + return nil, newError("failed to initialize RoundTripperEnrollmentConfirmation server").Base(err).AtError() + } + return s, nil } type Server struct { @@ -29,6 +34,10 @@ type Server struct { rttServer request.RoundTripperServer } +func (s *Server) OnConnectionEnrollmentConfirmationServerInstanceConfigReady(config tlsmirror.ConnectionEnrollmentConfirmationServerInstanceConfig) { + s.enrollmentProcessor = config.EnrollmentProcessor +} + func (s *Server) Listen(ctx context.Context) (v2net.Listener, error) { transportEnvironment := envctx.EnvironmentFromContext(s.ctx).(environment.TransportEnvironment) listener := transportEnvironment.Listener() @@ -75,8 +84,35 @@ func (s *Server) AutoImplListener() request.Listener { return s } +func (s *Server) init() error { + if s.config == nil { + return newError("nil ServerConfig") + } + if s.config.RoundTripperServer == nil { + return newError("nil RoundTripperServer in ServerConfig") + } + RoundTripperServerConfig, err := serial.GetInstanceOf(s.config.RoundTripperServer) + if err != nil { + return newError("failed to get instance of RoundTripperServer").Base(err).AtError() + } + RoundTripperServerObj, err := common.CreateObject(s.ctx, RoundTripperServerConfig) + if err != nil { + return newError("failed to create RoundTripperServer").Base(err).AtError() + } + RoundTripperServerTyped, ok := RoundTripperServerObj.(request.RoundTripperServer) + if !ok { + return newError("RoundTripperServer is not a valid request.RoundTripperServer") + } + s.rttServer = RoundTripperServerTyped + s.rttServer.OnTransportServerAssemblyReady(s) + if err := s.rttServer.Start(); err != nil { + return newError("failed to start RoundTripperServer").Base(err).AtError() + } + return nil +} + func init() { common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { - return NewServer(ctx, config.(*ServerConfig)), nil + return NewServer(ctx, config.(*ServerConfig)) })) } diff --git a/transport/internet/tlsmirror/mirrorenrollment/server.go b/transport/internet/tlsmirror/mirrorenrollment/server.go index 45b33e8db..ac7270b99 100644 --- a/transport/internet/tlsmirror/mirrorenrollment/server.go +++ b/transport/internet/tlsmirror/mirrorenrollment/server.go @@ -4,6 +4,7 @@ import ( "context" "net" + "github.com/v2fly/v2ray-core/v5/common" "github.com/v2fly/v2ray-core/v5/common/serial" "github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror" "github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/mirrorenrollment/httpenrollmentconfirmation" @@ -36,6 +37,10 @@ func NewEnrollmentConfirmationServer(ctx context.Context, config *Config, enroll primaryIngressConnectionHandler: primaryIngressConnectionHandler, } + if err = s.init(); err != nil { + return nil, newError("failed to initialize enrollment confirmation server").Base(err).AtError() + } + return s, nil } @@ -59,21 +64,25 @@ func (s *EnrollmentConfirmationServer) HandlePrimaryIngressConnection(ctx contex } func (s *EnrollmentConfirmationServer) init() error { - for _, handler := range s.config.BootstrapEgressConfig { + for _, handler := range s.config.BootstrapIngressConfig { bootstrapEnrollmentHandler, err := serial.GetInstanceOf(handler) if err != nil { return newError("failed to get instance of bootstrap enrollment handler").Base(err).AtError() } - bootstrapEnrollmentHandlerTyped, ok := bootstrapEnrollmentHandler.(tlsmirror.ConnectionEnrollmentConfirmationServerInstanceConfigReceiver) + bootstrapEnrollmentHandlerObj, err := common.CreateObject(s.ctx, bootstrapEnrollmentHandler) + if err != nil { + return newError("failed to create bootstrap enrollment handler").Base(err).AtError() + } + bootstrapEnrollmentHandlerObjTyped, ok := bootstrapEnrollmentHandlerObj.(tlsmirror.ConnectionEnrollmentConfirmationServerInstanceConfigReceiver) if !ok { return newError("bootstrap enrollment handler is not a valid ConnectionEnrollmentConfirmationServerInstanceConfigReceiver") } - bootstrapEnrollmentHandlerTyped.OnConnectionEnrollmentConfirmationServerInstanceConfigReady( + bootstrapEnrollmentHandlerObjTyped.OnConnectionEnrollmentConfirmationServerInstanceConfigReady( tlsmirror.ConnectionEnrollmentConfirmationServerInstanceConfig{ EnrollmentProcessor: s.enrollmentProcessor, }) - s.bootstrapIngressConnectionHandlers = append(s.bootstrapIngressConnectionHandlers, bootstrapEnrollmentHandlerTyped) + s.bootstrapIngressConnectionHandlers = append(s.bootstrapIngressConnectionHandlers, bootstrapEnrollmentHandlerObjTyped) } return nil } diff --git a/transport/internet/tlsmirror/server/client.go b/transport/internet/tlsmirror/server/client.go index 45d8413bf..25e6fe7fe 100644 --- a/transport/internet/tlsmirror/server/client.go +++ b/transport/internet/tlsmirror/server/client.go @@ -7,6 +7,7 @@ import ( "time" "github.com/golang/protobuf/proto" + "github.com/v2fly/v2ray-core/v5/transport/internet/tlsmirror/mirrorcommon" core "github.com/v2fly/v2ray-core/v5" "github.com/v2fly/v2ray-core/v5/common" @@ -58,6 +59,8 @@ type persistentMirrorTLSDialer struct { explicitNonceCiphersuiteLookup *ciphersuiteLookuper enrollmentConfirmationClient *mirrorenrollment.EnrollmentConfirmationClient + + enrollmentServerIdentifier []byte } func (d *persistentMirrorTLSDialer) init(ctx context.Context, config *Config) error { @@ -147,6 +150,7 @@ func (d *persistentMirrorTLSDialer) init(ctx context.Context, config *Config) er if err != nil { return newError("failed to derive enrollment server identifier").Base(err).AtError() } + d.enrollmentServerIdentifier = enrollmentServerIdentifier d.enrollmentConfirmationClient, err = mirrorenrollment.NewEnrollmentConfirmationClient(d.ctx, d.config.ConnectionEnrolment, enrollmentServerIdentifier) if err != nil { return newError("failed to create enrollment confirmation client").Base(err).AtError() @@ -259,6 +263,12 @@ func (d *persistentMirrorTLSDialer) handleIncomingReadyConnection(conn internet. func (d *persistentMirrorTLSDialer) Dial(ctx context.Context, dest net.Destination, settings *internet.MemoryStreamConfig, ) (internet.Connection, error) { + if d.enrollmentServerIdentifier != nil && len(d.enrollmentServerIdentifier) > 0 { + if mirrorcommon.IsLoopbackProtectionEnabled(ctx, d.enrollmentServerIdentifier) { + return nil, newError("loopback protection: refusing to dial to self") + } + } + var recvConn net.Conn select { case conn := <-d.incomingConnections: