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 }