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  }