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