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