Wake: a target may list several broadcast addresses

Config keeps broadcast (one) and adds broadcasts (a list); exactly one must
be present. Wake.Addresses() returns Broadcast then Broadcasts; checkWake
errors on both-set, neither-or-empty, and non-host:port entries. Waker sends
to every address in order, logging/past a failure so one dead address does
not stop the others, and returns false only when none could be sent.

Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
2026-09-25 13:45:39 -07:00
parent 4e1dd03d07
commit 8018f67031
6 changed files with 146 additions and 14 deletions
+29 -5
View File
@@ -7,11 +7,24 @@ import (
)
// Wake is the magic-wake pattern sent to a host to rouse it: its MAC, the
// broadcast address to aim at, and how long to wait for the answer.
// broadcast address(s) to aim at, and how long to wait for the answer. A host
// that roams between networks names several, so Broadcast (one) and Broadcasts
// (a list) are alternatives: exactly one must be set.
type Wake struct {
MAC string `toml:"mac"`
Broadcast string `toml:"broadcast"`
Wait Duration `toml:"wait"`
MAC string `toml:"mac"`
Broadcast string `toml:"broadcast"`
Broadcasts []string `toml:"broadcasts"`
Wait Duration `toml:"wait"`
}
// Addresses is Broadcast (when set) followed by Broadcasts: the ordered list to
// send wake packets to, never empty for a parsed config.
func (w *Wake) Addresses() []string {
addrs := make([]string, 0, 1+len(w.Broadcasts))
if w.Broadcast != "" {
addrs = append(addrs, w.Broadcast)
}
return append(addrs, w.Broadcasts...)
}
const (
@@ -40,8 +53,19 @@ func (c *Config) checkWake() *Error {
return &Error{Field: wakeField + ".mac", Msg: "must be a MAC address"}
}
if _, _, err := net.SplitHostPort(w.Broadcast); err != nil || w.Broadcast == "" {
broadcastSet := w.Broadcast != ""
broadcastsSet := len(w.Broadcasts) > 0
switch {
case broadcastSet && broadcastsSet:
return &Error{Field: wakeField + ".broadcasts", Msg: "choose broadcast or broadcasts, not both"}
case !broadcastSet && !broadcastsSet:
return &Error{Field: wakeField + ".broadcast", Msg: "must be a non-empty host:port"}
default:
for _, a := range w.Broadcasts {
if _, _, err := net.SplitHostPort(a); err != nil || a == "" {
return &Error{Field: wakeField + ".broadcasts", Msg: "must be a non-empty host:port"}
}
}
}
if w.Wait.Duration == 0 {