mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-15 04:53:00 +08:00
254 lines
5.5 KiB
Go
254 lines
5.5 KiB
Go
package proxy
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"UAmask/internal/ingress"
|
|
"UAmask/internal/model"
|
|
"UAmask/internal/session"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const acceptRetryDelay = 5 * time.Millisecond
|
|
|
|
type Server struct {
|
|
poolSize int
|
|
ingress ingress.Ingress
|
|
manager session.Manager
|
|
}
|
|
|
|
func NewServer(poolSize int, ingress ingress.Ingress, manager session.Manager) *Server {
|
|
return &Server{
|
|
poolSize: poolSize,
|
|
ingress: ingress,
|
|
manager: manager,
|
|
}
|
|
}
|
|
|
|
func (s *Server) Run() error {
|
|
return s.RunContext(context.Background())
|
|
}
|
|
|
|
func (s *Server) RunContext(ctx context.Context) error {
|
|
if s.ingress == nil || s.manager == nil {
|
|
return fmt.Errorf("server dependencies are not configured")
|
|
}
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
sessionCtx, cancelSessions := context.WithCancel(context.WithoutCancel(ctx))
|
|
|
|
tracker := newSessionTracker()
|
|
shutdownStarted := make(chan struct{})
|
|
var shutdownOnce sync.Once
|
|
shutdown := func() {
|
|
shutdownOnce.Do(func() {
|
|
close(shutdownStarted)
|
|
_ = s.ingress.Close()
|
|
cancelSessions()
|
|
tracker.CloseAll()
|
|
})
|
|
}
|
|
watchDone := make(chan struct{})
|
|
go func() {
|
|
select {
|
|
case <-ctx.Done():
|
|
shutdown()
|
|
case <-watchDone:
|
|
}
|
|
}()
|
|
|
|
logrus.Infof("UA-Mask ingress server ready")
|
|
var runErr error
|
|
if s.poolSize > 0 {
|
|
runErr = s.runWithPool(sessionCtx, tracker, shutdownStarted, shutdown)
|
|
} else {
|
|
runErr = s.runDefault(sessionCtx, tracker, shutdownStarted)
|
|
}
|
|
shutdown()
|
|
close(watchDone)
|
|
tracker.Wait()
|
|
return runErr
|
|
}
|
|
|
|
func (s *Server) runWithPool(
|
|
ctx context.Context,
|
|
tracker *sessionTracker,
|
|
shutdownStarted <-chan struct{},
|
|
shutdown func(),
|
|
) error {
|
|
logrus.Infof("Starting in Worker Pool Mode (size: %d)", s.poolSize)
|
|
|
|
sessionChan := make(chan *model.AcceptedSession, s.poolSize)
|
|
var workers sync.WaitGroup
|
|
for i := 0; i < s.poolSize; i++ {
|
|
workers.Add(1)
|
|
go func(workerID int) {
|
|
defer workers.Done()
|
|
for accepted := range sessionChan {
|
|
if ctx.Err() != nil {
|
|
closeAccepted(accepted)
|
|
tracker.Done(accepted)
|
|
continue
|
|
}
|
|
logrus.Debugf("[server] Worker %d processing connection for %s", workerID, accepted.Target.Address)
|
|
s.handleSession(ctx, accepted)
|
|
tracker.Done(accepted)
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
err := s.acceptLoop(ctx, shutdownStarted, func(accepted *model.AcceptedSession) bool {
|
|
if !tracker.Add(accepted) {
|
|
closeAccepted(accepted)
|
|
return false
|
|
}
|
|
select {
|
|
case sessionChan <- accepted:
|
|
return true
|
|
case <-ctx.Done():
|
|
closeAccepted(accepted)
|
|
tracker.Done(accepted)
|
|
return false
|
|
}
|
|
})
|
|
shutdown()
|
|
close(sessionChan)
|
|
workers.Wait()
|
|
return err
|
|
}
|
|
|
|
func (s *Server) runDefault(ctx context.Context, tracker *sessionTracker, shutdownStarted <-chan struct{}) error {
|
|
logrus.Info("Starting in Default Mode (one goroutine per connection)")
|
|
return s.acceptLoop(ctx, shutdownStarted, func(accepted *model.AcceptedSession) bool {
|
|
if !tracker.Add(accepted) {
|
|
closeAccepted(accepted)
|
|
return false
|
|
}
|
|
go func() {
|
|
defer tracker.Done(accepted)
|
|
s.handleSession(ctx, accepted)
|
|
}()
|
|
return true
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleSession(ctx context.Context, accepted *model.AcceptedSession) {
|
|
if manager, ok := s.manager.(session.ContextManager); ok {
|
|
manager.HandleContext(ctx, accepted)
|
|
return
|
|
}
|
|
s.manager.Handle(accepted)
|
|
}
|
|
|
|
func (s *Server) acceptLoop(
|
|
ctx context.Context,
|
|
shutdownStarted <-chan struct{},
|
|
submit func(*model.AcceptedSession) bool,
|
|
) error {
|
|
for {
|
|
accepted, err := s.ingress.Accept()
|
|
if err != nil {
|
|
if ctx.Err() != nil || channelClosed(shutdownStarted) {
|
|
return nil
|
|
}
|
|
if !isTemporaryAcceptError(err) {
|
|
return fmt.Errorf("accept session: %w", err)
|
|
}
|
|
logrus.Warnf("Accept error: %v; retrying...", err)
|
|
if !waitForRetry(ctx) {
|
|
return nil
|
|
}
|
|
continue
|
|
}
|
|
if accepted == nil {
|
|
return errors.New("accept session: ingress returned nil session")
|
|
}
|
|
if !submit(accepted) {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func channelClosed(channel <-chan struct{}) bool {
|
|
select {
|
|
case <-channel:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isTemporaryAcceptError(err error) bool {
|
|
var temporary interface{ Temporary() bool }
|
|
return errors.As(err, &temporary) && temporary.Temporary()
|
|
}
|
|
|
|
func waitForRetry(ctx context.Context) bool {
|
|
timer := time.NewTimer(acceptRetryDelay)
|
|
defer timer.Stop()
|
|
select {
|
|
case <-timer.C:
|
|
return true
|
|
case <-ctx.Done():
|
|
return false
|
|
}
|
|
}
|
|
|
|
type sessionTracker struct {
|
|
mu sync.Mutex
|
|
active map[*model.AcceptedSession]struct{}
|
|
closed bool
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
func newSessionTracker() *sessionTracker {
|
|
return &sessionTracker{active: make(map[*model.AcceptedSession]struct{})}
|
|
}
|
|
|
|
func (tracker *sessionTracker) Add(accepted *model.AcceptedSession) bool {
|
|
tracker.mu.Lock()
|
|
defer tracker.mu.Unlock()
|
|
if tracker.closed {
|
|
return false
|
|
}
|
|
tracker.active[accepted] = struct{}{}
|
|
tracker.wg.Add(1)
|
|
return true
|
|
}
|
|
|
|
func (tracker *sessionTracker) Done(accepted *model.AcceptedSession) {
|
|
tracker.mu.Lock()
|
|
delete(tracker.active, accepted)
|
|
tracker.mu.Unlock()
|
|
tracker.wg.Done()
|
|
}
|
|
|
|
func (tracker *sessionTracker) CloseAll() {
|
|
tracker.mu.Lock()
|
|
tracker.closed = true
|
|
accepted := make([]*model.AcceptedSession, 0, len(tracker.active))
|
|
for session := range tracker.active {
|
|
accepted = append(accepted, session)
|
|
}
|
|
tracker.mu.Unlock()
|
|
for _, session := range accepted {
|
|
closeAccepted(session)
|
|
}
|
|
}
|
|
|
|
func (tracker *sessionTracker) Wait() {
|
|
tracker.wg.Wait()
|
|
}
|
|
|
|
func closeAccepted(accepted *model.AcceptedSession) {
|
|
if accepted != nil && accepted.ClientConn != nil {
|
|
_ = accepted.ClientConn.Close()
|
|
}
|
|
}
|