~bigbes/shroud

ref: 3394139df40339ebdae50f853d922c14fca77c35 shroud/internal/vless/server.go -rw-r--r-- 3.6 KiB
3394139d — Eugene Blikh docs: update README with VLESS+REALITY, CI/CD site deployment a month ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package vless

import (
	"context"
	"fmt"
	"log/slog"
	"net"
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/xtls/reality"

	"sourcecraft.dev/bigbes/shroud/internal/store"
)

// Config holds the VLESS+REALITY server configuration.
type Config struct {
	ListenAddr  string
	PrivateKey  []byte   // 32-byte raw x25519 private key
	PublicKey   []byte   // 32-byte raw x25519 public key (for share links)
	ShortID     [8]byte
	ServerNames []string
	Dest        string
	Show        bool
}

// Server is a VLESS proxy server with REALITY obfuscation.
type Server struct {
	cfg      Config
	listener net.Listener
	logger   *slog.Logger

	mu     sync.RWMutex
	uuids  map[[16]byte]string // UUID → access key ID
	cancel context.CancelFunc
}

// New creates a new VLESS+REALITY server.
func New(cfg Config, logger *slog.Logger) *Server {
	return &Server{
		cfg:    cfg,
		logger: logger.With("component", "vless"),
		uuids:  make(map[[16]byte]string),
	}
}

// Start initializes the REALITY listener and begins accepting connections.
func (s *Server) Start() error {
	shortIds := map[[8]byte]bool{s.cfg.ShortID: true}

	serverNames := make(map[string]bool, len(s.cfg.ServerNames))
	for _, name := range s.cfg.ServerNames {
		serverNames[name] = true
	}

	realityCfg := &reality.Config{
		PrivateKey:  s.cfg.PrivateKey,
		ShortIds:    shortIds,
		ServerNames: serverNames,
		Dest:        s.cfg.Dest,
		Show:        s.cfg.Show,
	}

	ln, err := reality.Listen("tcp", s.cfg.ListenAddr, realityCfg)
	if err != nil {
		return fmt.Errorf("reality listen: %w", err)
	}
	s.listener = ln

	ctx, cancel := context.WithCancel(context.Background())
	s.cancel = cancel

	go s.acceptLoop(ctx)

	return nil
}

// SyncKeys rebuilds the UUID authentication map from the given access keys.
func (s *Server) SyncKeys(keys []store.AccessKey) error {
	newUUIDs := make(map[[16]byte]string)
	for _, k := range keys {
		if k.VLESS == nil {
			continue
		}
		parsed, err := uuid.Parse(k.VLESS.UUID)
		if err != nil {
			s.logger.Warn("Skipping VLESS key with invalid UUID.", "keyID", k.ID, "err", err)
			continue
		}
		newUUIDs[parsed] = k.ID
	}

	s.mu.Lock()
	s.uuids = newUUIDs
	s.mu.Unlock()

	s.logger.Info("VLESS keys synced.", "count", len(newUUIDs))
	return nil
}

// Stop shuts down the server.
func (s *Server) Stop() {
	if s.cancel != nil {
		s.cancel()
	}
	if s.listener != nil {
		s.listener.Close()
	}
	s.logger.Info("VLESS server stopped.")
}

func (s *Server) acceptLoop(ctx context.Context) {
	for {
		conn, err := s.listener.Accept()
		if err != nil {
			select {
			case <-ctx.Done():
				return
			default:
				s.logger.Error("VLESS accept error.", "err", err)
				continue
			}
		}
		go s.handleConn(conn)
	}
}

func (s *Server) handleConn(conn net.Conn) {
	defer conn.Close()

	// Deadline for header parsing.
	conn.SetReadDeadline(time.Now().Add(10 * time.Second))

	req, err := ParseRequest(conn)
	if err != nil {
		s.logger.Debug("Failed to parse VLESS request.", "err", err)
		return
	}

	// Clear read deadline for relay.
	conn.SetReadDeadline(time.Time{})

	// Authenticate UUID.
	s.mu.RLock()
	keyID, ok := s.uuids[req.UUID]
	s.mu.RUnlock()

	if !ok {
		s.logger.Debug("Unknown VLESS UUID, closing connection.")
		return
	}

	target := req.Target()

	switch req.Command {
	case CmdTCP:
		s.logger.Debug("VLESS TCP relay.", "keyID", keyID, "target", target)
		if err := relayTCP(conn, target); err != nil {
			s.logger.Debug("VLESS TCP relay ended.", "keyID", keyID, "err", err)
		}

	case CmdUDP:
		s.logger.Debug("VLESS UDP relay.", "keyID", keyID, "target", target)
		if err := relayUDP(conn, target); err != nil {
			s.logger.Debug("VLESS UDP relay ended.", "keyID", keyID, "err", err)
		}
	}
}