● Notice //GitHub (@shehzade) is temporarily suspended pending an automated false-positive review. All tools are hosted on this Code Mirror and available on request (contact); GitHub links will return once reinstated.
PUBLIC MIRRORGo v1.0

Multi-protocol network egress assessment and DLP verification framework featuring automated L4 sweeps, corporate proxy traversal, and covert protocol smuggling across 20+ channels.

$ git clone https://shehzade.io/repos/contraband.git Copy
Source ZIP (1.93 MB)
Code 174 Commits 1
contraband / pkg / server / multiplexer / multiplexer.go
← Back to directory
433 lines · 11.7 KB
Raw
1package multiplexer
2
3import (
4 "context"
5 "encoding/binary"
6 "fmt"
7 "net"
8 "sync"
9 "sync/atomic"
10 "time"
11
12 "github.com/shehzade/contraband/pkg/common"
13 "github.com/shehzade/contraband/pkg/server"
14)
15
16// PortBindStatus categorizes the binding outcome of an individual port.
17type PortBindStatus string
18
19const (
20 PortStatusBound PortBindStatus = "BOUND"
21 PortStatusOccupiedSkipped PortBindStatus = "OCCUPIED_SKIPPED"
22 PortStatusPermissionDenied PortBindStatus = "PERMISSION_DENIED"
23 PortStatusFailed PortBindStatus = "FAILED"
24 PortStatusClosed PortBindStatus = "CLOSED"
25)
26
27// ProbeType indicates the classification of an incoming connection.
28type ProbeType string
29
30const (
31 ProbeTypeSweep ProbeType = "SWEEP_PROBE"
32 ProbeTypeSmuggling ProbeType = "SMUGGLING_SESSION"
33 ProbeTypeUnknown ProbeType = "UNKNOWN"
34)
35
36// MultiplexerConfig defines runtime parameters for the multi-port reflector.
37type MultiplexerConfig struct {
38 BindIP string `json:"bind_ip"` // e.g. "0.0.0.0" or "127.0.0.1"
39 Ports string `json:"ports"` // "quick", "standard", "full", or "80,443,8000-8100"
40 EnableTCP bool `json:"enable_tcp"` // Enable TCP listeners (default: true)
41 EnableUDP bool `json:"enable_udp"` // Enable UDP listeners (default: true)
42 EnableEBPF bool `json:"enable_ebpf"` // Attempt Linux eBPF/nftables full redirection
43 PeekTimeout time.Duration `json:"peek_timeout"` // TCP initial byte read deadline (default: 100ms)
44 SocketTimeout time.Duration `json:"socket_timeout"` // Inactive connection teardown deadline (default: 3s)
45}
46
47// PortReport summarizes binding state across the requested port matrix.
48type PortReport struct {
49 Port int `json:"port"`
50 Protocol string `json:"protocol"` // "TCP" or "UDP"
51 Status PortBindStatus `json:"status"`
52 Error string `json:"error,omitempty"`
53}
54
55// MultiplexerMetrics provides live observability counters.
56type MultiplexerMetrics struct {
57 RequestedPorts int `json:"requested_ports"`
58 BoundTCP int `json:"bound_tcp"`
59 BoundUDP int `json:"bound_udp"`
60 SkippedPorts int `json:"skipped_ports"`
61 ReflectedTCP int64 `json:"reflected_tcp"`
62 ReflectedUDP int64 `json:"reflected_udp"`
63 DemuxSmuggling int64 `json:"demux_smuggling"`
64}
65
66// DemuxHandler defines the callback invoked when an incoming connection
67// carries a data smuggling payload instead of a sweep probe.
68type DemuxHandler = server.DemuxHandler
69
70// PortListener represents an active multiplexer port listener.
71type PortListener struct {
72 Port int
73 Protocol string
74 Status PortBindStatus
75 TCP net.Listener
76 UDP net.PacketConn
77}
78
79// Multiplexer defines the master interface for multi-port reflection.
80type Multiplexer interface {
81 Start(ctx context.Context) error
82 Shutdown(ctx context.Context) error
83 GetPortReports() []PortReport
84 GetMetrics() MultiplexerMetrics
85 RegisterDemuxHandler(magic uint32, handler DemuxHandler)
86 GetPortListeners() []PortListener
87}
88
89// UserspaceMultiplexer manages concurrent TCP net.Listener and UDP net.PacketConn instances.
90type UserspaceMultiplexer struct {
91 mu sync.RWMutex
92 config MultiplexerConfig
93 ledger server.Ledger
94 logger common.Logger
95 safety *SafetyChecker
96 reflector *ReflectionEngine
97 demuxer *HeaderDemuxer
98 redirector *LinuxKernelRedirector
99
100 tcpListeners map[int]net.Listener
101 udpConns map[int]net.PacketConn
102 portReports map[string]PortReport // "port/proto" -> PortReport
103 demuxHandlers map[uint32]DemuxHandler
104
105 ctx context.Context
106 cancel context.CancelFunc
107 wg sync.WaitGroup
108 running atomic.Bool
109
110 // Atomic telemetry counters
111 reflectedTCP atomic.Int64
112 reflectedUDP atomic.Int64
113 demuxSmuggling atomic.Int64
114}
115
116// NewUserspaceMultiplexer constructs a production-grade multiplexer instance.
117func NewUserspaceMultiplexer(cfg MultiplexerConfig, ledger server.Ledger, logger common.Logger) (*UserspaceMultiplexer, error) {
118 if cfg.BindIP == "" {
119 cfg.BindIP = common.DefaultServerBind
120 }
121 if cfg.PeekTimeout <= 0 {
122 cfg.PeekTimeout = 100 * time.Millisecond
123 }
124 if cfg.SocketTimeout <= 0 {
125 cfg.SocketTimeout = 3 * time.Second
126 }
127 if logger == nil {
128 logger = common.GetLogger()
129 }
130
131 safety := NewSafetyChecker(logger)
132 reflector := NewReflectionEngine(ledger, logger)
133 demuxer := NewHeaderDemuxer(cfg.PeekTimeout)
134
135 return &UserspaceMultiplexer{
136 config: cfg,
137 ledger: ledger,
138 logger: logger,
139 safety: safety,
140 reflector: reflector,
141 demuxer: demuxer,
142 tcpListeners: make(map[int]net.Listener),
143 udpConns: make(map[int]net.PacketConn),
144 portReports: make(map[string]PortReport),
145 demuxHandlers: make(map[uint32]DemuxHandler),
146 }, nil
147}
148
149// Start parses port presets, executes safe binding, and spawns worker loops.
150func (u *UserspaceMultiplexer) Start(ctx context.Context) error {
151 u.mu.Lock()
152 defer u.mu.Unlock()
153
154 if u.running.Load() {
155 return fmt.Errorf("multiplexer already running")
156 }
157
158 ports, err := common.ParsePortsField(u.config.Ports)
159 if err != nil {
160 return fmt.Errorf("failed to parse ports %q: %w", u.config.Ports, err)
161 }
162
163 u.ctx, u.cancel = context.WithCancel(ctx)
164 u.running.Store(true)
165
166 boundTCP := 0
167 boundUDP := 0
168 skipped := 0
169
170 // 1. Bind TCP Listeners
171 if u.config.EnableTCP {
172 for _, port := range ports {
173 ln, status, err := u.safety.SafeListenTCP(u.config.BindIP, port)
174 key := fmt.Sprintf("%d/TCP", port)
175 if err != nil {
176 u.portReports[key] = PortReport{Port: port, Protocol: "TCP", Status: status, Error: err.Error()}
177 continue
178 }
179 u.portReports[key] = PortReport{Port: port, Protocol: "TCP", Status: status}
180 if status == PortStatusBound {
181 u.tcpListeners[port] = ln
182 boundTCP++
183 u.wg.Add(1)
184 go u.tcpAcceptLoop(ln, port)
185 } else {
186 skipped++
187 }
188 }
189 }
190
191 // 2. Bind UDP Packet Connections
192 if u.config.EnableUDP {
193 for _, port := range ports {
194 pc, status, err := u.safety.SafeListenUDP(u.config.BindIP, port)
195 key := fmt.Sprintf("%d/UDP", port)
196 if err != nil {
197 u.portReports[key] = PortReport{Port: port, Protocol: "UDP", Status: status, Error: err.Error()}
198 continue
199 }
200 u.portReports[key] = PortReport{Port: port, Protocol: "UDP", Status: status}
201 if status == PortStatusBound {
202 u.udpConns[port] = pc
203 boundUDP++
204 u.wg.Add(1)
205 go u.udpReadLoop(pc, port)
206 } else {
207 skipped++
208 }
209 }
210 }
211
212 // 3. Optional Linux Kernel Redirection (eBPF / nftables)
213 if u.config.EnableEBPF {
214 targetPort := SelectTargetPort(ports)
215 u.redirector = NewLinuxKernelRedirector(targetPort, u.logger)
216 if err := u.redirector.Enable(); err != nil {
217 u.logger.Warnf("Kernel redirector initialization skipped (%v); operating in pure userspace multiplexer mode", err)
218 }
219 }
220
221 u.logger.Infof("Multiplexer active: bound %d TCP and %d UDP listeners (%d occupied/privileged ports skipped safely)",
222 boundTCP, boundUDP, skipped)
223
224 return nil
225}
226
227// SelectTargetPort selects the first non-protected port from candidate ports.
228// If all candidate ports are protected or candidate list is empty, it returns default fallback port 8080.
229func SelectTargetPort(ports []int) int {
230 for _, p := range ports {
231 if !IsProtectedPort(p) {
232 return p
233 }
234 }
235 return 8080
236}
237
238// tcpAcceptLoop handles incoming TCP connections on a designated port.
239func (u *UserspaceMultiplexer) tcpAcceptLoop(ln net.Listener, port int) {
240 defer u.wg.Done()
241 for {
242 conn, err := ln.Accept()
243 if err != nil {
244 select {
245 case <-u.ctx.Done():
246 return
247 default:
248 // Listener closed or transient error
249 return
250 }
251 }
252
253 u.wg.Add(1)
254 go func(c net.Conn) {
255 defer u.wg.Done()
256 u.handleTCPConn(c, port)
257 }(conn)
258 }
259}
260
261// handleTCPConn classifies connection via peeking and executes reflection or demux.
262func (u *UserspaceMultiplexer) handleTCPConn(conn net.Conn, port int) {
263 defer conn.Close()
264 _ = conn.SetDeadline(time.Now().Add(u.config.SocketTimeout))
265
266 probeType, peeked, err := u.demuxer.Demux(conn)
267 if err != nil && probeType == ProbeTypeUnknown {
268 return
269 }
270
271 switch probeType {
272 case ProbeTypeSmuggling:
273 u.demuxSmuggling.Add(1)
274 if len(peeked) >= 4 {
275 magic := binary.BigEndian.Uint32(peeked[0:4])
276 u.mu.RLock()
277 handler, exists := u.demuxHandlers[magic]
278 u.mu.RUnlock()
279 if exists {
280 _ = handler(conn, peeked, port)
281 return
282 }
283 }
284 // If no dedicated smuggling handler registered, emit reflection fallback
285 _ = u.reflector.ReflectTCP(conn, port)
286 u.reflectedTCP.Add(1)
287
288 case ProbeTypeSweep, ProbeTypeUnknown:
289 _ = u.reflector.ReflectTCP(conn, port)
290 u.reflectedTCP.Add(1)
291 }
292}
293
294// udpReadLoop reads incoming datagrams on a designated UDP port.
295func (u *UserspaceMultiplexer) udpReadLoop(pc net.PacketConn, port int) {
296 defer u.wg.Done()
297 buf := make([]byte, 4096)
298
299 for {
300 n, rAddr, err := pc.ReadFrom(buf)
301 if err != nil {
302 select {
303 case <-u.ctx.Done():
304 return
305 default:
306 return
307 }
308 }
309
310 if n > 0 {
311 pkt := make([]byte, n)
312 copy(pkt, buf[:n])
313
314 u.wg.Add(1)
315 go func(data []byte, addr net.Addr) {
316 defer u.wg.Done()
317 _ = u.reflector.ReflectUDP(pc, addr, port, data)
318 u.reflectedUDP.Add(1)
319 }(pkt, rAddr)
320 }
321 }
322}
323
324// Shutdown gracefully stops all listeners and waits for in-flight workers.
325func (u *UserspaceMultiplexer) Shutdown(ctx context.Context) error {
326 if !u.running.CompareAndSwap(true, false) {
327 return nil
328 }
329
330 u.mu.Lock()
331 if u.redirector != nil {
332 _ = u.redirector.Disable()
333 }
334 if u.cancel != nil {
335 u.cancel()
336 }
337
338 for port, ln := range u.tcpListeners {
339 _ = ln.Close()
340 key := fmt.Sprintf("%d/TCP", port)
341 if rep, ok := u.portReports[key]; ok {
342 rep.Status = PortStatusClosed
343 u.portReports[key] = rep
344 }
345 }
346 for port, pc := range u.udpConns {
347 _ = pc.Close()
348 key := fmt.Sprintf("%d/UDP", port)
349 if rep, ok := u.portReports[key]; ok {
350 rep.Status = PortStatusClosed
351 u.portReports[key] = rep
352 }
353 }
354 u.mu.Unlock()
355
356 // Wait for goroutines with context timeout
357 done := make(chan struct{})
358 go func() {
359 u.wg.Wait()
360 close(done)
361 }()
362
363 select {
364 case <-done:
365 u.logger.Info("Multiplexer shutdown complete: all sockets closed cleanly")
366 return nil
367 case <-ctx.Done():
368 return fmt.Errorf("multiplexer shutdown timed out: %w", ctx.Err())
369 }
370}
371
372// RegisterDemuxHandler binds a magic-prefixed handler for data smuggling sessions.
373func (u *UserspaceMultiplexer) RegisterDemuxHandler(magic uint32, handler DemuxHandler) {
374 u.mu.Lock()
375 defer u.mu.Unlock()
376 u.demuxHandlers[magic] = handler
377}
378
379// GetPortReports returns a snapshot of port binding states.
380func (u *UserspaceMultiplexer) GetPortReports() []PortReport {
381 u.mu.RLock()
382 defer u.mu.RUnlock()
383 reports := make([]PortReport, 0, len(u.portReports))
384 for _, r := range u.portReports {
385 reports = append(reports, r)
386 }
387 return reports
388}
389
390// GetPortListeners returns a list of active port listeners.
391func (u *UserspaceMultiplexer) GetPortListeners() []PortListener {
392 u.mu.RLock()
393 defer u.mu.RUnlock()
394 listeners := make([]PortListener, 0)
395 for port, ln := range u.tcpListeners {
396 listeners = append(listeners, PortListener{
397 Port: port,
398 Protocol: "TCP",
399 Status: PortStatusBound,
400 TCP: ln,
401 })
402 }
403 for port, pc := range u.udpConns {
404 listeners = append(listeners, PortListener{
405 Port: port,
406 Protocol: "UDP",
407 Status: PortStatusBound,
408 UDP: pc,
409 })
410 }
411 return listeners
412}
413
414// GetMetrics returns real-time reflection and demux counts.
415func (u *UserspaceMultiplexer) GetMetrics() MultiplexerMetrics {
416 u.mu.RLock()
417 defer u.mu.RUnlock()
418 skipped := 0
419 for _, rep := range u.portReports {
420 if rep.Status == PortStatusOccupiedSkipped || rep.Status == PortStatusPermissionDenied {
421 skipped++
422 }
423 }
424 return MultiplexerMetrics{
425 RequestedPorts: len(u.portReports),
426 BoundTCP: len(u.tcpListeners),
427 BoundUDP: len(u.udpConns),
428 SkippedPorts: skipped,
429 ReflectedTCP: u.reflectedTCP.Load(),
430 ReflectedUDP: u.reflectedUDP.Load(),
431 DemuxSmuggling: u.demuxSmuggling.Load(),
432 }
433}