github.com/xtls/xray-core@v1.8.12-0.20240518155711-3168d27b0bdb/proxy/loopback/loopback.go (about)

     1  package loopback
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/xtls/xray-core/common"
     7  	"github.com/xtls/xray-core/common/buf"
     8  	"github.com/xtls/xray-core/common/net"
     9  	"github.com/xtls/xray-core/common/net/cnc"
    10  	"github.com/xtls/xray-core/common/retry"
    11  	"github.com/xtls/xray-core/common/session"
    12  	"github.com/xtls/xray-core/common/task"
    13  	"github.com/xtls/xray-core/core"
    14  	"github.com/xtls/xray-core/features/routing"
    15  	"github.com/xtls/xray-core/transport"
    16  	"github.com/xtls/xray-core/transport/internet"
    17  )
    18  
    19  type Loopback struct {
    20  	config             *Config
    21  	dispatcherInstance routing.Dispatcher
    22  }
    23  
    24  func (l *Loopback) Process(ctx context.Context, link *transport.Link, _ internet.Dialer) error {
    25  	outbounds := session.OutboundsFromContext(ctx)
    26  	ob := outbounds[len(outbounds) - 1]
    27  	if !ob.Target.IsValid() {
    28  		return newError("target not specified.")
    29  	}
    30  	ob.Name = "loopback"
    31  	destination := ob.Target
    32  
    33  	newError("opening connection to ", destination).WriteToLog(session.ExportIDToError(ctx))
    34  
    35  	input := link.Reader
    36  	output := link.Writer
    37  
    38  	var conn net.Conn
    39  	err := retry.ExponentialBackoff(2, 100).On(func() error {
    40  		dialDest := destination
    41  
    42  		content := new(session.Content)
    43  		content.SkipDNSResolve = true
    44  
    45  		ctx = session.ContextWithContent(ctx, content)
    46  
    47  		inbound := session.InboundFromContext(ctx)
    48  
    49  		inbound.Tag = l.config.InboundTag
    50  
    51  		ctx = session.ContextWithInbound(ctx, inbound)
    52  
    53  		rawConn, err := l.dispatcherInstance.Dispatch(ctx, dialDest)
    54  		if err != nil {
    55  			return err
    56  		}
    57  
    58  		var readerOpt cnc.ConnectionOption
    59  		if dialDest.Network == net.Network_TCP {
    60  			readerOpt = cnc.ConnectionOutputMulti(rawConn.Reader)
    61  		} else {
    62  			readerOpt = cnc.ConnectionOutputMultiUDP(rawConn.Reader)
    63  		}
    64  
    65  		conn = cnc.NewConnection(cnc.ConnectionInputMulti(rawConn.Writer), readerOpt)
    66  		return nil
    67  	})
    68  	if err != nil {
    69  		return newError("failed to open connection to ", destination).Base(err)
    70  	}
    71  	defer conn.Close()
    72  
    73  	requestDone := func() error {
    74  		var writer buf.Writer
    75  		if destination.Network == net.Network_TCP {
    76  			writer = buf.NewWriter(conn)
    77  		} else {
    78  			writer = &buf.SequentialWriter{Writer: conn}
    79  		}
    80  
    81  		if err := buf.Copy(input, writer); err != nil {
    82  			return newError("failed to process request").Base(err)
    83  		}
    84  
    85  		return nil
    86  	}
    87  
    88  	responseDone := func() error {
    89  		var reader buf.Reader
    90  		if destination.Network == net.Network_TCP {
    91  			reader = buf.NewReader(conn)
    92  		} else {
    93  			reader = buf.NewPacketReader(conn)
    94  		}
    95  		if err := buf.Copy(reader, output); err != nil {
    96  			return newError("failed to process response").Base(err)
    97  		}
    98  
    99  		return nil
   100  	}
   101  
   102  	if err := task.Run(ctx, requestDone, task.OnSuccess(responseDone, task.Close(output))); err != nil {
   103  		return newError("connection ends").Base(err)
   104  	}
   105  
   106  	return nil
   107  }
   108  
   109  func (l *Loopback) init(config *Config, dispatcherInstance routing.Dispatcher) error {
   110  	l.dispatcherInstance = dispatcherInstance
   111  	l.config = config
   112  	return nil
   113  }
   114  
   115  func init() {
   116  	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
   117  		l := new(Loopback)
   118  		err := core.RequireFeatures(ctx, func(dispatcherInstance routing.Dispatcher) error {
   119  			return l.init(config.(*Config), dispatcherInstance)
   120  		})
   121  		return l, err
   122  	}))
   123  }