github.com/v2fly/v2ray-core/v5@v5.16.2-0.20240507031116-8191faa6e095/app/reverse/portal.go (about) 1 package reverse 2 3 import ( 4 "context" 5 "sync" 6 "time" 7 8 "google.golang.org/protobuf/proto" 9 10 "github.com/v2fly/v2ray-core/v5/common" 11 "github.com/v2fly/v2ray-core/v5/common/buf" 12 "github.com/v2fly/v2ray-core/v5/common/mux" 13 "github.com/v2fly/v2ray-core/v5/common/net" 14 "github.com/v2fly/v2ray-core/v5/common/session" 15 "github.com/v2fly/v2ray-core/v5/common/task" 16 "github.com/v2fly/v2ray-core/v5/features/outbound" 17 "github.com/v2fly/v2ray-core/v5/transport" 18 "github.com/v2fly/v2ray-core/v5/transport/pipe" 19 ) 20 21 type Portal struct { 22 ctx context.Context 23 ohm outbound.Manager 24 tag string 25 domain string 26 picker *StaticMuxPicker 27 client *mux.ClientManager 28 } 29 30 func NewPortal(ctx context.Context, config *PortalConfig, ohm outbound.Manager) (*Portal, error) { 31 if config.Tag == "" { 32 return nil, newError("portal tag is empty") 33 } 34 35 if config.Domain == "" { 36 return nil, newError("portal domain is empty") 37 } 38 39 picker, err := NewStaticMuxPicker() 40 if err != nil { 41 return nil, err 42 } 43 44 return &Portal{ 45 ctx: ctx, 46 ohm: ohm, 47 tag: config.Tag, 48 domain: config.Domain, 49 picker: picker, 50 client: &mux.ClientManager{ 51 Picker: picker, 52 }, 53 }, nil 54 } 55 56 func (p *Portal) Start() error { 57 return p.ohm.AddHandler(p.ctx, &Outbound{ 58 portal: p, 59 tag: p.tag, 60 }) 61 } 62 63 func (p *Portal) Close() error { 64 return p.ohm.RemoveHandler(p.ctx, p.tag) 65 } 66 67 func (p *Portal) HandleConnection(ctx context.Context, link *transport.Link) error { 68 outboundMeta := session.OutboundFromContext(ctx) 69 if outboundMeta == nil { 70 return newError("outbound metadata not found").AtError() 71 } 72 73 if isDomain(outboundMeta.Target, p.domain) { 74 muxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{}) 75 if err != nil { 76 return newError("failed to create mux client worker").Base(err).AtWarning() 77 } 78 79 worker, err := NewPortalWorker(ctx, muxClient) 80 if err != nil { 81 return newError("failed to create portal worker").Base(err) 82 } 83 84 p.picker.AddWorker(worker) 85 return nil 86 } 87 88 return p.client.Dispatch(ctx, link) 89 } 90 91 type Outbound struct { 92 portal *Portal 93 tag string 94 } 95 96 func (o *Outbound) Tag() string { 97 return o.tag 98 } 99 100 func (o *Outbound) Dispatch(ctx context.Context, link *transport.Link) { 101 if err := o.portal.HandleConnection(ctx, link); err != nil { 102 newError("failed to process reverse connection").Base(err).WriteToLog(session.ExportIDToError(ctx)) 103 common.Interrupt(link.Writer) 104 } 105 } 106 107 func (o *Outbound) Start() error { 108 return nil 109 } 110 111 func (o *Outbound) Close() error { 112 return nil 113 } 114 115 type StaticMuxPicker struct { 116 access sync.Mutex 117 workers []*PortalWorker 118 cTask *task.Periodic 119 } 120 121 func NewStaticMuxPicker() (*StaticMuxPicker, error) { 122 p := &StaticMuxPicker{} 123 p.cTask = &task.Periodic{ 124 Execute: p.cleanup, 125 Interval: time.Second * 30, 126 } 127 p.cTask.Start() 128 return p, nil 129 } 130 131 func (p *StaticMuxPicker) cleanup() error { 132 p.access.Lock() 133 defer p.access.Unlock() 134 135 var activeWorkers []*PortalWorker 136 for _, w := range p.workers { 137 if !w.Closed() { 138 activeWorkers = append(activeWorkers, w) 139 } 140 } 141 142 if len(activeWorkers) != len(p.workers) { 143 p.workers = activeWorkers 144 } 145 146 return nil 147 } 148 149 func (p *StaticMuxPicker) PickAvailable() (*mux.ClientWorker, error) { 150 p.access.Lock() 151 defer p.access.Unlock() 152 153 if len(p.workers) == 0 { 154 return nil, newError("empty worker list") 155 } 156 157 minIdx := -1 158 var minConn uint32 = 9999 159 for i, w := range p.workers { 160 if w.draining { 161 continue 162 } 163 if w.client.Closed() { 164 continue 165 } 166 if w.client.ActiveConnections() < minConn { 167 minConn = w.client.ActiveConnections() 168 minIdx = i 169 } 170 } 171 172 if minIdx == -1 { 173 for i, w := range p.workers { 174 if w.IsFull() { 175 continue 176 } 177 if w.client.ActiveConnections() < minConn { 178 minConn = w.client.ActiveConnections() 179 minIdx = i 180 } 181 } 182 } 183 184 if minIdx != -1 { 185 return p.workers[minIdx].client, nil 186 } 187 188 return nil, newError("no mux client worker available") 189 } 190 191 func (p *StaticMuxPicker) AddWorker(worker *PortalWorker) { 192 p.access.Lock() 193 defer p.access.Unlock() 194 195 p.workers = append(p.workers, worker) 196 } 197 198 type PortalWorker struct { 199 client *mux.ClientWorker 200 control *task.Periodic 201 writer buf.Writer 202 reader buf.Reader 203 draining bool 204 } 205 206 func NewPortalWorker(ctx context.Context, client *mux.ClientWorker) (*PortalWorker, error) { 207 opt := []pipe.Option{pipe.WithSizeLimit(16 * 1024)} 208 uplinkReader, uplinkWriter := pipe.New(opt...) 209 downlinkReader, downlinkWriter := pipe.New(opt...) 210 211 ctx = session.ContextWithOutbound(ctx, &session.Outbound{ 212 Target: net.UDPDestination(net.DomainAddress(internalDomain), 0), 213 }) 214 f := client.Dispatch(ctx, &transport.Link{ 215 Reader: uplinkReader, 216 Writer: downlinkWriter, 217 }) 218 if !f { 219 return nil, newError("unable to dispatch control connection") 220 } 221 w := &PortalWorker{ 222 client: client, 223 reader: downlinkReader, 224 writer: uplinkWriter, 225 } 226 w.control = &task.Periodic{ 227 Execute: w.heartbeat, 228 Interval: time.Second * 2, 229 } 230 w.control.Start() 231 return w, nil 232 } 233 234 func (w *PortalWorker) heartbeat() error { 235 if w.client.Closed() { 236 return newError("client worker stopped") 237 } 238 239 if w.draining || w.writer == nil { 240 return newError("already disposed") 241 } 242 243 msg := &Control{} 244 msg.FillInRandom() 245 246 if w.client.TotalConnections() > 256 { 247 w.draining = true 248 msg.State = Control_DRAIN 249 250 defer func() { 251 common.Close(w.writer) 252 common.Interrupt(w.reader) 253 w.writer = nil 254 }() 255 } 256 257 b, err := proto.Marshal(msg) 258 common.Must(err) 259 mb := buf.MergeBytes(nil, b) 260 return w.writer.WriteMultiBuffer(mb) 261 } 262 263 func (w *PortalWorker) IsFull() bool { 264 return w.client.IsFull() 265 } 266 267 func (w *PortalWorker) Closed() bool { 268 return w.client.Closed() 269 }