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