~bigbes/shroud

ref: 999bf1cb8045e0b245640d8f284d92f41b31b8a8 shroud/internal/awgserver/server.go -rw-r--r-- 5.4 KiB
999bf1cb — Eugene Blikh feat(reality): add autodetect command for decoy server 2 months 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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
package awgserver

import (
	"fmt"
	"log/slog"
	"strings"
	"sync"

	"github.com/amnezia-vpn/amneziawg-go/device"
	"github.com/amnezia-vpn/amneziawg-go/ipc"
	"github.com/amnezia-vpn/amneziawg-go/tun"
	"github.com/quic-go/quic-go/http3"

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

// Config holds all settings for the AmneziaWG server.
type Config struct {
	ListenPort   int
	TUNName      string
	Address      string // server CIDR, e.g. "10.14.0.1/24"
	MTU          int
	PrivateKey   string // server interface key, base64
	MuxEnabled   bool   // enable AWG+HTTP/3 muxer on the same port
	Domain       string
	CertCache    string
	ACMEHTTPPort int

	// Obfuscation parameters.
	Jc, Jmin, Jmax int
	S1, S2, S3, S4 int
	H1, H2, H3, H4 HeaderRange
}

// Server wraps an AmneziaWG device with MuxBind + HTTP/3 multiplexing.
type Server struct {
	cfg     Config
	dev     *device.Device
	tunDev  tun.Device
	muxBind *MuxBind
	h3srv   *http3.Server
	logger  *slog.Logger

	mu sync.Mutex
}

// New creates a new AWG server (not yet started).
func New(cfg Config, logger *slog.Logger) *Server {
	return &Server{
		cfg:    cfg,
		logger: logger,
	}
}

// Start creates the TUN device, AWG device with MuxBind, and HTTP/3 server.
func (s *Server) Start() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	// 1. Create TUN device.
	tunDev, err := tun.CreateTUN(s.cfg.TUNName, s.cfg.MTU)
	if err != nil {
		return fmt.Errorf("create TUN %q: %w", s.cfg.TUNName, err)
	}
	s.tunDev = tunDev
	s.logger.Info("TUN device created.", "name", s.cfg.TUNName, "mtu", s.cfg.MTU)

	// 2. Create MuxBind.
	quicCh := make(chan quicPacket, 1024)
	s.muxBind = NewMuxBind(&s.cfg, quicCh)

	// 3. Create AWG device.
	logLevel := device.LogLevelError
	devLogger := device.NewLogger(logLevel, "[awg] ")
	s.dev = device.NewDevice(tunDev, s.muxBind, devLogger)

	// 4. Configure via UAPI (interface only, peers added by SyncKeys).
	ifaceConf, err := s.buildInterfaceConfig()
	if err != nil {
		s.dev.Close()
		tunDev.Close()
		return fmt.Errorf("build UAPI interface config: %w", err)
	}
	if err := s.dev.IpcSet(ifaceConf); err != nil {
		s.dev.Close()
		tunDev.Close()
		return fmt.Errorf("IpcSet interface config: %w", err)
	}
	s.logger.Info("AWG device configured.")

	// 5. Bring up — triggers MuxBind.Open(), starts listening.
	s.dev.Up()
	s.logger.Info("AWG device is up.", "port", s.cfg.ListenPort)

	// 6. Set up UAPI socket for awg show/set.
	go s.setupUAPI()

	// 7. Get FilteredConn and start HTTP/3 server.
	fc := s.muxBind.FilteredConn()
	if s.cfg.MuxEnabled && s.cfg.Domain != "" {
		h3srv, err := startH3Server(fc, &s.cfg, s.logger)
		if err != nil {
			s.logger.Warn("HTTP/3 server failed to start (non-fatal).", "err", err)
		} else {
			s.h3srv = h3srv
		}
	}

	// 8. Configure TUN address.
	if s.cfg.Address != "" {
		if err := setTUNAddress(s.cfg.TUNName, s.cfg.Address, s.logger); err != nil {
			s.logger.Warn("Failed to configure TUN address (non-fatal).", "err", err)
		}
	}

	return nil
}

// SyncKeys rebuilds the AWG peer list from access keys that have AWG data.
func (s *Server) SyncKeys(keys []store.AccessKey) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.dev == nil {
		return fmt.Errorf("AWG device not started")
	}

	conf := s.buildPeerConfig(keys)
	if err := s.dev.IpcSet(conf); err != nil {
		return fmt.Errorf("IpcSet peers: %w", err)
	}

	count := 0
	for _, k := range keys {
		if k.AWG != nil {
			count++
		}
	}
	s.logger.Info("AWG peers synced.", "count", count)
	return nil
}

// Stop shuts down the AWG device, TUN, and HTTP/3 server.
func (s *Server) Stop() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.h3srv != nil {
		s.h3srv.Close()
	}
	if s.dev != nil {
		s.dev.Close()
	}
	if s.tunDev != nil {
		s.tunDev.Close()
	}
	s.logger.Info("AWG server stopped.")
	return nil
}

func (s *Server) buildInterfaceConfig() (string, error) {
	privHex, err := base64ToHex(s.cfg.PrivateKey)
	if err != nil {
		return "", fmt.Errorf("PrivateKey: %w", err)
	}

	var b strings.Builder
	fmt.Fprintf(&b, "private_key=%s\n", privHex)
	fmt.Fprintf(&b, "listen_port=%d\n", s.cfg.ListenPort)

	if s.cfg.Jc > 0 {
		fmt.Fprintf(&b, "jc=%d\n", s.cfg.Jc)
		fmt.Fprintf(&b, "jmin=%d\n", s.cfg.Jmin)
		fmt.Fprintf(&b, "jmax=%d\n", s.cfg.Jmax)
	}
	fmt.Fprintf(&b, "s1=%d\n", s.cfg.S1)
	fmt.Fprintf(&b, "s2=%d\n", s.cfg.S2)
	fmt.Fprintf(&b, "s3=%d\n", s.cfg.S3)
	fmt.Fprintf(&b, "s4=%d\n", s.cfg.S4)
	fmt.Fprintf(&b, "h1=%s\n", s.cfg.H1)
	fmt.Fprintf(&b, "h2=%s\n", s.cfg.H2)
	fmt.Fprintf(&b, "h3=%s\n", s.cfg.H3)
	fmt.Fprintf(&b, "h4=%s\n", s.cfg.H4)

	return b.String(), nil
}

func (s *Server) buildPeerConfig(keys []store.AccessKey) string {
	var b strings.Builder
	b.WriteString("replace_peers=true\n")

	for _, k := range keys {
		if k.AWG == nil {
			continue
		}
		pubHex, err := base64ToHex(k.AWG.PublicKey)
		if err != nil {
			s.logger.Warn("Skipping AWG peer: bad public key.", "keyID", k.ID, "err", err)
			continue
		}
		fmt.Fprintf(&b, "public_key=%s\n", pubHex)
		fmt.Fprintf(&b, "allowed_ip=%s\n", k.AWG.AllowedIP)
	}

	return b.String()
}

func (s *Server) setupUAPI() {
	fileUAPI, err := ipc.UAPIOpen(s.cfg.TUNName)
	if err != nil {
		s.logger.Debug("UAPI open (non-fatal).", "err", err)
		return
	}
	uapiListener, err := ipc.UAPIListen(s.cfg.TUNName, fileUAPI)
	if err != nil {
		s.logger.Debug("UAPI listen (non-fatal).", "err", err)
		return
	}
	s.logger.Info("UAPI listening.", "interface", s.cfg.TUNName)
	for {
		conn, err := uapiListener.Accept()
		if err != nil {
			return
		}
		go s.dev.IpcHandle(conn)
	}
}