feat(sol): UDP + HTTP listener

This commit is contained in:
Björn Benouarets
2025-11-11 17:19:49 +01:00
commit 8ed56f7ba0
12 changed files with 565 additions and 0 deletions

91
server/api.go Normal file
View File

@@ -0,0 +1,91 @@
package server
import (
"fmt"
"net/http"
"time"
"wol-sol-agent/auth"
"wol-sol-agent/system"
"git.secnex.io/secnex/masterlog"
)
// APIServer handles HTTP API requests
type APIServer struct {
port int
authenticator *auth.Authenticator
}
// NewAPIServer creates a new API server
func NewAPIServer(port int, authenticator *auth.Authenticator) *APIServer {
return &APIServer{
port: port,
authenticator: authenticator,
}
}
// Start starts the API server
func (s *APIServer) Start(done chan bool) {
mux := http.NewServeMux()
mux.HandleFunc("/shutdown", s.handleShutdown)
server := &http.Server{
Addr: fmt.Sprintf(":%d", s.port),
Handler: mux,
}
masterlog.Info("API server listening on port", map[string]interface{}{
"port": s.port,
})
// Start server in goroutine
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
masterlog.Error("API server error", map[string]interface{}{
"error": err,
})
}
}()
// Wait for shutdown signal
<-done
server.Close()
}
func (s *APIServer) handleShutdown(w http.ResponseWriter, r *http.Request) {
// Check authentication
username, password, ok := r.BasicAuth()
if !ok {
w.Header().Set("WWW-Authenticate", `Basic realm="SOL Agent"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
masterlog.Error("Unauthorized API request", map[string]interface{}{
"address": r.RemoteAddr,
"error": "no auth",
})
return
}
// Verify credentials
if !s.authenticator.Verify(username, password) {
w.Header().Set("WWW-Authenticate", `Basic realm="SOL Agent"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
masterlog.Error("Unauthorized API request", map[string]interface{}{
"address": r.RemoteAddr,
"error": "invalid credentials",
})
return
}
masterlog.Info("Authenticated shutdown request", map[string]interface{}{
"address": r.RemoteAddr,
})
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Shutting down system...\n")
// Shutdown in a goroutine to allow response to be sent
go func() {
time.Sleep(500 * time.Millisecond)
system.Shutdown()
}()
}

107
server/udp.go Normal file
View File

@@ -0,0 +1,107 @@
package server
import (
"fmt"
"net"
"time"
"wol-sol-agent/auth"
"wol-sol-agent/magicpacket"
"wol-sol-agent/system"
"git.secnex.io/secnex/masterlog"
)
// UDPServer handles UDP connections for Magic Packets
type UDPServer struct {
port int
authenticator *auth.Authenticator
}
// NewUDPServer creates a new UDP server
func NewUDPServer(port int, authenticator *auth.Authenticator) *UDPServer {
return &UDPServer{
port: port,
authenticator: authenticator,
}
}
// Start starts the UDP server
func (s *UDPServer) Start(done chan bool) {
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", s.port))
if err != nil {
masterlog.Error("Failed to resolve UDP address", map[string]interface{}{
"error": err,
})
return
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
masterlog.Error("Failed to listen on UDP port", map[string]interface{}{
"error": err,
"port": s.port,
})
return
}
defer conn.Close()
masterlog.Info("UDP server listening on port", map[string]interface{}{
"port": s.port,
})
buffer := make([]byte, 2048)
for {
select {
case <-done:
return
default:
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
n, clientAddr, err := conn.ReadFromUDP(buffer)
if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue
}
masterlog.Error("Error reading UDP packet", map[string]interface{}{
"error": err,
})
continue
}
masterlog.Info("Received UDP packet", map[string]interface{}{
"bytes": n,
"address": clientAddr,
})
// Check authentication and magic packet
if s.isValidAuthenticatedMagicPacket(buffer[:n]) {
masterlog.Info("Authenticated Magic Packet detected! Shutting down system...", map[string]interface{}{
"address": clientAddr,
})
system.Shutdown()
return
}
masterlog.Error("Invalid or unauthenticated packet", map[string]interface{}{
"address": clientAddr,
})
}
}
}
// isValidAuthenticatedMagicPacket checks if the packet contains valid authentication
// followed by a valid Magic Packet
func (s *UDPServer) isValidAuthenticatedMagicPacket(data []byte) bool {
username, password, magicPacketData, ok := magicpacket.ExtractAuthFromPacket(data)
if !ok {
return false
}
// Verify authentication
if !s.authenticator.Verify(username, password) {
return false
}
// Check if it's a valid magic packet
return magicpacket.IsMagicPacket(magicPacketData)
}