5d5b6c99a7
Fix Wake-on-LAN: set SO_BROADCAST on UDP socket, send 3 magic packets, expose broadcast_addr and wake timeout fields in UI, add Test Wake endpoint, surface send errors in job status, bump default wake timeout to 180s.
172 lines
4.1 KiB
Go
172 lines
4.1 KiB
Go
package wol
|
|
|
|
import (
|
|
"net"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestParseMAC(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
wantOK bool
|
|
wantBytes [6]byte
|
|
}{
|
|
{"AA:BB:CC:DD:EE:FF", true, [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}},
|
|
{"aa:bb:cc:dd:ee:ff", true, [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}},
|
|
{"AA-BB-CC-DD-EE-FF", true, [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}},
|
|
{"11:22:33:44:55:66", true, [6]byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66}},
|
|
{"not:a:mac:addre", false, [6]byte{}},
|
|
{"GG:HH:II:JJ:KK:LL", false, [6]byte{}},
|
|
{"aa:bb:cc:dd", false, [6]byte{}},
|
|
{"", false, [6]byte{}},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
mac, err := ParseMAC(tt.input)
|
|
if tt.wantOK {
|
|
if err != nil {
|
|
t.Errorf("ParseMAC(%q) unexpected error: %v", tt.input, err)
|
|
continue
|
|
}
|
|
if mac != tt.wantBytes {
|
|
t.Errorf("ParseMAC(%q) = %v, want %v", tt.input, mac, tt.wantBytes)
|
|
}
|
|
} else {
|
|
if err == nil {
|
|
t.Errorf("ParseMAC(%q) expected error, got nil", tt.input)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatMAC(t *testing.T) {
|
|
mac := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}
|
|
got := FormatMAC(mac)
|
|
want := "aa:bb:cc:dd:ee:ff"
|
|
if got != want {
|
|
t.Errorf("FormatMAC() = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestBuildMagicPacket(t *testing.T) {
|
|
mac := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}
|
|
packet := BuildMagicPacket(mac)
|
|
|
|
if len(packet) != 102 {
|
|
t.Errorf("BuildMagicPacket length = %d, want 102", len(packet))
|
|
}
|
|
|
|
for i := 0; i < 6; i++ {
|
|
if packet[i] != 0xFF {
|
|
t.Errorf("packet[%d] = %02x, want FF", i, packet[i])
|
|
}
|
|
}
|
|
|
|
for i := 0; i < 16; i++ {
|
|
offset := 6 + i*6
|
|
for j := 0; j < 6; j++ {
|
|
if packet[offset+j] != mac[j] {
|
|
t.Errorf("packet[%d] = %02x, want %02x (rep %d)", offset+j, packet[offset+j], mac[j], i)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSendInvalidBroadcast(t *testing.T) {
|
|
mac, _ := ParseMAC("aa:bb:cc:dd:ee:ff")
|
|
err := Send(mac, "not-an-ip")
|
|
if err == nil {
|
|
t.Error("Send with invalid broadcast address expected error, got nil")
|
|
}
|
|
}
|
|
|
|
func TestSendThreePacketsToListener(t *testing.T) {
|
|
mac, _ := ParseMAC("aa:bb:cc:dd:ee:ff")
|
|
ln, err := net.ListenPacket("udp", "127.0.0.1:9")
|
|
if err != nil {
|
|
t.Skipf("skipping test (need port 9, likely requires root): %v", err)
|
|
}
|
|
defer ln.Close()
|
|
|
|
err = Send(mac, "127.0.0.1")
|
|
if err != nil {
|
|
t.Errorf("Send to 127.0.0.1: expected nil, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSendPacketReceived(t *testing.T) {
|
|
mac, _ := ParseMAC("aa:bb:cc:dd:ee:ff")
|
|
ln, err := net.ListenPacket("udp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Skipf("skipping receive test: %v", err)
|
|
}
|
|
defer ln.Close()
|
|
addr := ln.LocalAddr().(*net.UDPAddr)
|
|
|
|
deadline := time.Now().Add(5 * time.Second)
|
|
errCh := make(chan error, 1)
|
|
go func() {
|
|
time.Sleep(500 * time.Millisecond)
|
|
conn, err := net.DialUDP("udp4", nil, &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: addr.Port})
|
|
if err != nil {
|
|
errCh <- err
|
|
return
|
|
}
|
|
sc, err := conn.SyscallConn()
|
|
if err != nil {
|
|
conn.Close()
|
|
errCh <- err
|
|
return
|
|
}
|
|
if err := sc.Control(func(fd uintptr) {
|
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1)
|
|
}); err != nil {
|
|
conn.Close()
|
|
errCh <- err
|
|
return
|
|
}
|
|
conn.SetWriteDeadline(time.Now().Add(3 * time.Second))
|
|
packet := BuildMagicPacket(mac)
|
|
for i := 0; i < 3; i++ {
|
|
conn.Write(packet)
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
conn.Close()
|
|
errCh <- nil
|
|
}()
|
|
|
|
expected := BuildMagicPacket(mac)
|
|
buf := make([]byte, 102)
|
|
rcvd := 0
|
|
for rcvd < 3 {
|
|
ln.SetReadDeadline(deadline)
|
|
n, _, err := ln.ReadFrom(buf)
|
|
if err != nil {
|
|
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
|
break
|
|
}
|
|
t.Fatalf("unexpected error reading packet %d: %v", rcvd+1, err)
|
|
}
|
|
if n != len(expected) {
|
|
t.Errorf("packet %d: got length %d, want %d", rcvd+1, n, len(expected))
|
|
continue
|
|
}
|
|
if string(buf[:n]) != string(expected[:n]) {
|
|
t.Errorf("packet %d content mismatch", rcvd+1)
|
|
}
|
|
rcvd++
|
|
}
|
|
if rcvd != 3 {
|
|
t.Errorf("expected 3 packets, received %d", rcvd)
|
|
}
|
|
select {
|
|
case sendErr := <-errCh:
|
|
if sendErr != nil {
|
|
t.Logf("sender goroutine error (ignored): %v", sendErr)
|
|
}
|
|
default:
|
|
}
|
|
}
|