1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-11-17 18:06:15 -05:00
v2fly/app/reverse/portal.go

266 lines
5.3 KiB
Go
Raw Normal View History

2018-10-27 18:03:11 -04:00
package reverse
import (
"context"
"sync"
"time"
"github.com/golang/protobuf/proto"
"v2ray.com/core/common"
"v2ray.com/core/common/buf"
"v2ray.com/core/common/mux"
2018-10-28 02:27:07 -04:00
"v2ray.com/core/common/net"
2018-10-27 18:03:11 -04:00
"v2ray.com/core/common/session"
"v2ray.com/core/common/task"
"v2ray.com/core/common/vio"
"v2ray.com/core/features/outbound"
"v2ray.com/core/transport/pipe"
)
type Portal struct {
ohm outbound.Manager
tag string
domain string
picker *StaticMuxPicker
client *mux.ClientManager
}
func NewPortal(config *PortalConfig, ohm outbound.Manager) (*Portal, error) {
if len(config.Tag) == 0 {
return nil, newError("portal tag is empty")
}
if len(config.Domain) == 0 {
return nil, newError("portal domain is empty")
}
picker, err := NewStaticMuxPicker()
if err != nil {
return nil, err
}
return &Portal{
ohm: ohm,
tag: config.Tag,
domain: config.Domain,
picker: picker,
client: &mux.ClientManager{
Picker: picker,
},
}, nil
}
func (p *Portal) Start() error {
return p.ohm.AddHandler(context.Background(), &Outbound{
portal: p,
2018-10-28 02:27:07 -04:00
tag: p.tag,
2018-10-27 18:03:11 -04:00
})
}
func (p *Portal) Close() error {
return p.ohm.RemoveHandler(context.Background(), p.tag)
}
func (s *Portal) HandleConnection(ctx context.Context, link *vio.Link) error {
outboundMeta := session.OutboundFromContext(ctx)
if outboundMeta == nil {
return newError("outbound metadata not found").AtError()
}
2018-10-28 02:27:07 -04:00
if isDomain(outboundMeta.Target, s.domain) {
2018-10-28 04:08:43 -04:00
muxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{})
2018-10-27 18:03:11 -04:00
if err != nil {
return newError("failed to create mux client worker").Base(err).AtWarning()
}
worker, err := NewPortalWorker(muxClient)
if err != nil {
return newError("failed to create portal worker").Base(err)
}
s.picker.AddWorker(worker)
return nil
}
return s.client.Dispatch(ctx, link)
}
type Outbound struct {
portal *Portal
tag string
}
func (o *Outbound) Tag() string {
return o.tag
}
func (o *Outbound) Dispatch(ctx context.Context, link *vio.Link) {
if err := o.portal.HandleConnection(ctx, link); err != nil {
newError("failed to process reverse connection").Base(err).WriteToLog(session.ExportIDToError(ctx))
pipe.CloseError(link.Writer)
}
}
func (o *Outbound) Start() error {
return nil
}
func (o *Outbound) Close() error {
return nil
}
type StaticMuxPicker struct {
access sync.Mutex
workers []*PortalWorker
cTask *task.Periodic
}
func NewStaticMuxPicker() (*StaticMuxPicker, error) {
p := &StaticMuxPicker{}
p.cTask = &task.Periodic{
Execute: p.cleanup,
Interval: time.Second * 30,
}
p.cTask.Start()
return p, nil
}
func (p *StaticMuxPicker) cleanup() error {
p.access.Lock()
defer p.access.Unlock()
var activeWorkers []*PortalWorker
for _, w := range p.workers {
if !w.Closed() {
activeWorkers = append(activeWorkers, w)
}
}
if len(activeWorkers) != len(p.workers) {
p.workers = activeWorkers
}
return nil
}
func (p *StaticMuxPicker) PickAvailable() (*mux.ClientWorker, error) {
p.access.Lock()
defer p.access.Unlock()
2018-10-28 02:27:07 -04:00
if len(p.workers) == 0 {
2018-10-27 18:03:11 -04:00
return nil, newError("empty worker list")
}
2018-10-28 02:27:07 -04:00
var minIdx int = -1
var minConn uint32 = 9999
for i, w := range p.workers {
2018-10-28 04:08:43 -04:00
if w.draining {
2018-10-28 02:27:07 -04:00
continue
2018-10-27 18:03:11 -04:00
}
2018-10-28 02:27:07 -04:00
if w.client.ActiveConnections() < minConn {
minConn = w.client.ActiveConnections()
minIdx = i
}
}
2018-10-28 04:08:43 -04:00
if minIdx == -1 {
for i, w := range p.workers {
if w.IsFull() {
continue
}
if w.client.ActiveConnections() < minConn {
minConn = w.client.ActiveConnections()
minIdx = i
}
}
}
2018-10-28 02:27:07 -04:00
if minIdx != -1 {
return p.workers[minIdx].client, nil
2018-10-27 18:03:11 -04:00
}
return nil, newError("no mux client worker available")
}
func (p *StaticMuxPicker) AddWorker(worker *PortalWorker) {
p.access.Lock()
defer p.access.Unlock()
p.workers = append(p.workers, worker)
}
type PortalWorker struct {
2018-10-28 04:08:43 -04:00
client *mux.ClientWorker
control *task.Periodic
writer buf.Writer
reader buf.Reader
draining bool
2018-10-27 18:03:11 -04:00
}
func NewPortalWorker(client *mux.ClientWorker) (*PortalWorker, error) {
opt := []pipe.Option{pipe.WithSizeLimit(16 * 1024)}
uplinkReader, uplinkWriter := pipe.New(opt...)
downlinkReader, downlinkWriter := pipe.New(opt...)
2018-10-28 02:27:07 -04:00
ctx := context.Background()
ctx = session.ContextWithOutbound(ctx, &session.Outbound{
Target: net.UDPDestination(net.DomainAddress(internalDomain), 0),
})
f := client.Dispatch(ctx, &vio.Link{
2018-10-27 18:03:11 -04:00
Reader: uplinkReader,
Writer: downlinkWriter,
})
if !f {
return nil, newError("unable to dispatch control connection")
}
w := &PortalWorker{
client: client,
reader: downlinkReader,
writer: uplinkWriter,
}
w.control = &task.Periodic{
Execute: w.heartbeat,
Interval: time.Second * 2,
}
w.control.Start()
return w, nil
}
func (w *PortalWorker) heartbeat() error {
if w.client.Closed() {
return newError("client worker stopped")
}
2018-10-28 04:08:43 -04:00
if w.draining || w.writer == nil {
2018-10-27 18:03:11 -04:00
return newError("already disposed")
}
msg := &Control{}
msg.FillInRandom()
2018-10-28 04:08:43 -04:00
if w.client.TotalConnections() > 256 {
w.draining = true
2018-10-27 18:03:11 -04:00
msg.State = Control_DRAIN
defer func() {
common.Close(w.writer)
pipe.CloseError(w.reader)
w.writer = nil
}()
}
b, err := proto.Marshal(msg)
common.Must(err)
var mb buf.MultiBuffer
common.Must2(mb.Write(b))
return w.writer.WriteMultiBuffer(mb)
}
func (w *PortalWorker) IsFull() bool {
return w.client.IsFull()
}
func (w *PortalWorker) Closed() bool {
return w.client.Closed()
}