github.com/xmplusdev/xmcore@v1.8.11-0.20240412132628-5518b55526af/proxy/loopback/loopback.go (about)

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