github.com/EagleQL/Xray-core@v1.4.3/app/reverse/bridge.go (about)

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