mirror of https://github.com/XTLS/Xray-core
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
4.3 KiB
205 lines
4.3 KiB
package reverse |
|
|
|
import ( |
|
"context" |
|
"time" |
|
|
|
"github.com/xtls/xray-core/common/mux" |
|
"github.com/xtls/xray-core/common/net" |
|
"github.com/xtls/xray-core/common/session" |
|
"github.com/xtls/xray-core/common/task" |
|
"github.com/xtls/xray-core/features/routing" |
|
"github.com/xtls/xray-core/transport" |
|
"github.com/xtls/xray-core/transport/pipe" |
|
"google.golang.org/protobuf/proto" |
|
) |
|
|
|
// Bridge is a component in reverse proxy, that relays connections from Portal to local address. |
|
type Bridge struct { |
|
dispatcher routing.Dispatcher |
|
tag string |
|
domain string |
|
workers []*BridgeWorker |
|
monitorTask *task.Periodic |
|
} |
|
|
|
// NewBridge creates a new Bridge instance. |
|
func NewBridge(config *BridgeConfig, dispatcher routing.Dispatcher) (*Bridge, error) { |
|
if config.Tag == "" { |
|
return nil, newError("bridge tag is empty") |
|
} |
|
if config.Domain == "" { |
|
return nil, newError("bridge domain is empty") |
|
} |
|
|
|
b := &Bridge{ |
|
dispatcher: dispatcher, |
|
tag: config.Tag, |
|
domain: config.Domain, |
|
} |
|
b.monitorTask = &task.Periodic{ |
|
Execute: b.monitor, |
|
Interval: time.Second * 2, |
|
} |
|
return b, nil |
|
} |
|
|
|
func (b *Bridge) cleanup() { |
|
var activeWorkers []*BridgeWorker |
|
|
|
for _, w := range b.workers { |
|
if w.IsActive() { |
|
activeWorkers = append(activeWorkers, w) |
|
} |
|
} |
|
|
|
if len(activeWorkers) != len(b.workers) { |
|
b.workers = activeWorkers |
|
} |
|
} |
|
|
|
func (b *Bridge) monitor() error { |
|
b.cleanup() |
|
|
|
var numConnections uint32 |
|
var numWorker uint32 |
|
|
|
for _, w := range b.workers { |
|
if w.IsActive() { |
|
numConnections += w.Connections() |
|
numWorker++ |
|
} |
|
} |
|
|
|
if numWorker == 0 || numConnections/numWorker > 16 { |
|
worker, err := NewBridgeWorker(b.domain, b.tag, b.dispatcher) |
|
if err != nil { |
|
newError("failed to create bridge worker").Base(err).AtWarning().WriteToLog() |
|
return nil |
|
} |
|
b.workers = append(b.workers, worker) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func (b *Bridge) Start() error { |
|
return b.monitorTask.Start() |
|
} |
|
|
|
func (b *Bridge) Close() error { |
|
return b.monitorTask.Close() |
|
} |
|
|
|
type BridgeWorker struct { |
|
tag string |
|
worker *mux.ServerWorker |
|
dispatcher routing.Dispatcher |
|
state Control_State |
|
} |
|
|
|
func NewBridgeWorker(domain string, tag string, d routing.Dispatcher) (*BridgeWorker, error) { |
|
ctx := context.Background() |
|
ctx = session.ContextWithInbound(ctx, &session.Inbound{ |
|
Tag: tag, |
|
}) |
|
link, err := d.Dispatch(ctx, net.Destination{ |
|
Network: net.Network_TCP, |
|
Address: net.DomainAddress(domain), |
|
Port: 0, |
|
}) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
w := &BridgeWorker{ |
|
dispatcher: d, |
|
tag: tag, |
|
} |
|
|
|
worker, err := mux.NewServerWorker(context.Background(), w, link) |
|
if err != nil { |
|
return nil, err |
|
} |
|
w.worker = worker |
|
|
|
return w, nil |
|
} |
|
|
|
func (w *BridgeWorker) Type() interface{} { |
|
return routing.DispatcherType() |
|
} |
|
|
|
func (w *BridgeWorker) Start() error { |
|
return nil |
|
} |
|
|
|
func (w *BridgeWorker) Close() error { |
|
return nil |
|
} |
|
|
|
func (w *BridgeWorker) IsActive() bool { |
|
return w.state == Control_ACTIVE && !w.worker.Closed() |
|
} |
|
|
|
func (w *BridgeWorker) Connections() uint32 { |
|
return w.worker.ActiveConnections() |
|
} |
|
|
|
func (w *BridgeWorker) handleInternalConn(link *transport.Link) { |
|
go func() { |
|
reader := link.Reader |
|
for { |
|
mb, err := reader.ReadMultiBuffer() |
|
if err != nil { |
|
break |
|
} |
|
for _, b := range mb { |
|
var ctl Control |
|
if err := proto.Unmarshal(b.Bytes(), &ctl); err != nil { |
|
newError("failed to parse proto message").Base(err).WriteToLog() |
|
break |
|
} |
|
if ctl.State != w.state { |
|
w.state = ctl.State |
|
} |
|
} |
|
} |
|
}() |
|
} |
|
|
|
func (w *BridgeWorker) Dispatch(ctx context.Context, dest net.Destination) (*transport.Link, error) { |
|
if !isInternalDomain(dest) { |
|
ctx = session.ContextWithInbound(ctx, &session.Inbound{ |
|
Tag: w.tag, |
|
}) |
|
return w.dispatcher.Dispatch(ctx, dest) |
|
} |
|
|
|
opt := []pipe.Option{pipe.WithSizeLimit(16 * 1024)} |
|
uplinkReader, uplinkWriter := pipe.New(opt...) |
|
downlinkReader, downlinkWriter := pipe.New(opt...) |
|
|
|
w.handleInternalConn(&transport.Link{ |
|
Reader: downlinkReader, |
|
Writer: uplinkWriter, |
|
}) |
|
|
|
return &transport.Link{ |
|
Reader: uplinkReader, |
|
Writer: downlinkWriter, |
|
}, nil |
|
} |
|
|
|
func (w *BridgeWorker) DispatchLink(ctx context.Context, dest net.Destination, link *transport.Link) error { |
|
if !isInternalDomain(dest) { |
|
ctx = session.ContextWithInbound(ctx, &session.Inbound{ |
|
Tag: w.tag, |
|
}) |
|
return w.dispatcher.DispatchLink(ctx, dest, link) |
|
} |
|
|
|
w.handleInternalConn(link) |
|
|
|
return nil |
|
}
|
|
|