Files
wol-sol-agent/magicpacket/magicpacket.go
2025-11-11 20:38:49 +01:00

117 lines
3.3 KiB
Go

package magicpacket
import (
"bytes"
"strings"
)
const (
// Magic packet format: 6 bytes of 0xFF followed by 16 repetitions of the MAC address
MagicPacketHeader = 6
MacAddressRepetitions = 16
MacAddressLength = 6
// Authentication format for UDP: "AUTH:username:password:" followed by magic packet
AuthPrefix = "AUTH:"
)
// IsMagicPacket checks if the received data is a valid Wake-on-LAN Magic Packet
// Format: 6 bytes of 0xFF followed by 16 repetitions of the MAC address (6 bytes each)
func IsMagicPacket(data []byte) bool {
// Minimum size: 6 (header) + 16*6 (MAC repetitions) = 102 bytes
minSize := MagicPacketHeader + (MacAddressRepetitions * MacAddressLength)
if len(data) < minSize {
return false
}
// Check for 6 bytes of 0xFF at the beginning
header := data[:MagicPacketHeader]
for _, b := range header {
if b != 0xFF {
return false
}
}
// Extract the MAC address (first 6 bytes after header)
macAddr := data[MagicPacketHeader : MagicPacketHeader+MacAddressLength]
// Check if the MAC address is repeated 16 times
for i := 1; i < MacAddressRepetitions; i++ {
offset := MagicPacketHeader + (i * MacAddressLength)
if offset+MacAddressLength > len(data) {
return false
}
repeatedMac := data[offset : offset+MacAddressLength]
if !bytes.Equal(macAddr, repeatedMac) {
return false
}
}
return true
}
// ExtractAuthFromPacket extracts username and password from an authenticated packet
// Format: "AUTH:username:password:" followed by standard magic packet
// Returns username, password, magicPacketData, and success status
func ExtractAuthFromPacket(data []byte) (string, string, []byte, bool) {
dataStr := string(data)
// Check for authentication prefix
if !strings.HasPrefix(dataStr, AuthPrefix) {
return "", "", nil, false
}
// Find the first colon after "AUTH:" (end of username)
searchStart := len(AuthPrefix)
firstColon := strings.Index(dataStr[searchStart:], ":")
if firstColon == -1 {
return "", "", nil, false
}
firstColon += searchStart
// Find the second colon after the first one (end of password)
secondColon := strings.Index(dataStr[firstColon+1:], ":")
if secondColon == -1 {
// If no second colon, the password might be followed directly by binary data
// In this case, we need to find where the magic packet starts (6 bytes of 0xFF)
// Look for the magic packet header starting after the password
passwordEnd := len(dataStr)
for i := firstColon + 1; i < len(data) && i < firstColon+100; i++ {
// Check if we found the magic packet header (6 consecutive 0xFF bytes)
if i+5 < len(data) {
allFF := true
for j := 0; j < 6; j++ {
if data[i+j] != 0xFF {
allFF = false
break
}
}
if allFF {
passwordEnd = i
break
}
}
}
username := dataStr[searchStart:firstColon]
password := dataStr[firstColon+1 : passwordEnd]
magicPacketData := data[passwordEnd:]
return username, password, magicPacketData, true
}
secondColon += firstColon + 1
// Extract username and password
username := dataStr[searchStart:firstColon]
password := dataStr[firstColon+1 : secondColon]
// Extract magic packet part (after "AUTH:username:password:")
magicPacketStart := secondColon + 1
if magicPacketStart >= len(data) {
return "", "", nil, false
}
magicPacketData := data[magicPacketStart:]
return username, password, magicPacketData, true
}