github.com/xraypb/xray-core@v1.6.6/app/reverse/bridge.go (about) 1 package reverse 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/golang/protobuf/proto" 8 "github.com/xraypb/xray-core/common/mux" 9 "github.com/xraypb/xray-core/common/net" 10 "github.com/xraypb/xray-core/common/session" 11 "github.com/xraypb/xray-core/common/task" 12 "github.com/xraypb/xray-core/features/routing" 13 "github.com/xraypb/xray-core/transport" 14 "github.com/xraypb/xray-core/transport/pipe" 15 ) 16 17 // Bridge is a component in reverse proxy, that relays connections from Portal to local address. 18 type Bridge struct { 19 dispatcher routing.Dispatcher 20 tag string 21 domain string 22 workers []*BridgeWorker 23 monitorTask *task.Periodic 24 } 25 26 // NewBridge creates a new Bridge instance. 27 func NewBridge(config *BridgeConfig, dispatcher routing.Dispatcher) (*Bridge, error) { 28 if config.Tag == "" { 29 return nil, newError("bridge tag is empty") 30 } 31 if config.Domain == "" { 32 return nil, newError("bridge domain is empty") 33 } 34 35 b := &Bridge{ 36 dispatcher: dispatcher, 37 tag: config.Tag, 38 domain: config.Domain, 39 } 40 b.monitorTask = &task.Periodic{ 41 Execute: b.monitor, 42 Interval: time.Second * 2, 43 } 44 return b, nil 45 } 46 47 func (b *Bridge) cleanup() { 48 var activeWorkers []*BridgeWorker 49 50 for _, w := range b.workers { 51 if w.IsActive() { 52 activeWorkers = append(activeWorkers, w) 53 } 54 } 55 56 if len(activeWorkers) != len(b.workers) { 57 b.workers = activeWorkers 58 } 59 } 60 61 func (b *Bridge) monitor() error { 62 b.cleanup() 63 64 var numConnections uint32 65 var numWorker uint32 66 67 for _, w := range b.workers { 68 if w.IsActive() { 69 numConnections += w.Connections() 70 numWorker++ 71 } 72 } 73 74 if numWorker == 0 || numConnections/numWorker > 16 { 75 worker, err := NewBridgeWorker(b.domain, b.tag, b.dispatcher) 76 if err != nil { 77 newError("failed to create bridge worker").Base(err).AtWarning().WriteToLog() 78 return nil 79 } 80 b.workers = append(b.workers, worker) 81 } 82 83 return nil 84 } 85 86 func (b *Bridge) Start() error { 87 return b.monitorTask.Start() 88 } 89 90 func (b *Bridge) Close() error { 91 return b.monitorTask.Close() 92 } 93 94 type BridgeWorker struct { 95 tag string 96 worker *mux.ServerWorker 97 dispatcher routing.Dispatcher 98 state Control_State 99 } 100 101 func NewBridgeWorker(domain string, tag string, d routing.Dispatcher) (*BridgeWorker, error) { 102 ctx := context.Background() 103 ctx = session.ContextWithInbound(ctx, &session.Inbound{ 104 Tag: tag, 105 }) 106 link, err := d.Dispatch(ctx, net.Destination{ 107 Network: net.Network_TCP, 108 Address: net.DomainAddress(domain), 109 Port: 0, 110 }) 111 if err != nil { 112 return nil, err 113 } 114 115 w := &BridgeWorker{ 116 dispatcher: d, 117 tag: tag, 118 } 119 120 worker, err := mux.NewServerWorker(context.Background(), w, link) 121 if err != nil { 122 return nil, err 123 } 124 w.worker = worker 125 126 return w, nil 127 } 128 129 func (w *BridgeWorker) Type() interface{} { 130 return routing.DispatcherType() 131 } 132 133 func (w *BridgeWorker) Start() error { 134 return nil 135 } 136 137 func (w *BridgeWorker) Close() error { 138 return nil 139 } 140 141 func (w *BridgeWorker) IsActive() bool { 142 return w.state == Control_ACTIVE && !w.worker.Closed() 143 } 144 145 func (w *BridgeWorker) Connections() uint32 { 146 return w.worker.ActiveConnections() 147 } 148 149 func (w *BridgeWorker) handleInternalConn(link *transport.Link) { 150 go func() { 151 reader := link.Reader 152 for { 153 mb, err := reader.ReadMultiBuffer() 154 if err != nil { 155 break 156 } 157 for _, b := range mb { 158 var ctl Control 159 if err := proto.Unmarshal(b.Bytes(), &ctl); err != nil { 160 newError("failed to parse proto message").Base(err).WriteToLog() 161 break 162 } 163 if ctl.State != w.state { 164 w.state = ctl.State 165 } 166 } 167 } 168 }() 169 } 170 171 func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) { 172 if !isInternalDomain(dest) { 173 ctx = session.ContextWithInbound(ctx, &session.Inbound{ 174 Tag: w.tag, 175 }) 176 return w.dispatcher.Dispatch(ctx, dest) 177 } 178 179 opt := []pipe.Option{pipe.WithSizeLimit(16 * 1024)} 180 uplinkReader, uplinkWriter := pipe.New(opt...) 181 downlinkReader, downlinkWriter := pipe.New(opt...) 182 183 w.handleInternalConn(&transport.Link{ 184 Reader: downlinkReader, 185 Writer: uplinkWriter, 186 }) 187 188 return &transport.Link{ 189 Reader: uplinkReader, 190 Writer: downlinkWriter, 191 }, nil 192 } 193 194 func (w *BridgeWorker) DispatchLink(ctx context.Context, dest net.Destination, link *transport.Link) error { 195 if !isInternalDomain(dest) { 196 ctx = session.ContextWithInbound(ctx, &session.Inbound{ 197 Tag: w.tag, 198 }) 199 return w.dispatcher.DispatchLink(ctx, dest, link) 200 } 201 202 w.handleInternalConn(link) 203 204 return nil 205 }