Files
Adam Shiervani 72d27ac85e feat: add custom broadcast IP option to Wake-on-LAN (#1238) (#1345)
* fix: add custom broadcast IP option to Wake-on-LAN (#1238)

Add support for specifying a custom subnet broadcast IP when sending
WOL magic packets, enabling wake across different subnets.

Backend:
- Add broadcastIP optional parameter to rpcSendWOLMagicPacket
- Add OptionalParams support to RPCHandler for params with zero defaults
- Pass broadcastIP query param through HTTP handler

UI:
- Add broadcast address dropdown (Auto/Custom) to WOL dialog
- Show subnet broadcast IP input when Custom is selected
- Pass broadcastIP to RPC call when custom mode is active

* fix: move broadcast address field to add form only, default to Auto (#1238)

* fix(ui): simplify WoL broadcast dropdown and indent custom field

- Rename "Auto (global broadcast)" to "Auto" in the broadcast address
  dropdown
- Wrap the custom subnet IP input in a nested indent with left border,
  matching the settings page pattern (NestedSettingsGroup style)

* fix(i18n): use localization system for WoL broadcast address labels

Replace hardcoded English strings with m.xxx() calls in the broadcast
address UI and add the 4 new keys to all 14 locale files.
2026-03-28 12:54:57 +01:00

86 lines
2.0 KiB
Go

package kvm
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
wolPackets = promauto.NewCounter(
prometheus.CounterOpts{
Name: "jetkvm_wol_sent_packets_total",
Help: "Total number of Wake-on-LAN magic packets sent.",
},
)
wolErrors = promauto.NewCounter(
prometheus.CounterOpts{
Name: "jetkvm_wol_sent_packet_errors_total",
Help: "Total number of Wake-on-LAN magic packets errors.",
},
)
)
// SendWOLMagicPacket sends a Wake-on-LAN magic packet to the specified MAC address.
// broadcastIP optionally overrides the default 255.255.255.255 broadcast address.
func rpcSendWOLMagicPacket(macAddress string, broadcastIP string) error {
// Parse the MAC address
mac, err := net.ParseMAC(macAddress)
if err != nil {
wolErrors.Inc()
return ErrorfL(wolLogger, "invalid MAC address", err)
}
// Determine broadcast address
target := "255.255.255.255"
if broadcastIP != "" {
if ip := net.ParseIP(broadcastIP); ip == nil || ip.To4() == nil {
wolErrors.Inc()
return ErrorfL(wolLogger, "invalid broadcast IP address", fmt.Errorf("invalid IP: %s", broadcastIP))
}
target = broadcastIP
}
// Create the magic packet
packet := createMagicPacket(mac)
// Set up UDP connection
conn, err := net.Dial("udp", target+":9")
if err != nil {
wolErrors.Inc()
return ErrorfL(wolLogger, "failed to establish UDP connection", err)
}
defer conn.Close()
// Send the packet
_, err = conn.Write(packet)
if err != nil {
wolErrors.Inc()
return ErrorfL(wolLogger, "failed to send WOL packet", err)
}
wolLogger.Info().Str("mac", macAddress).Msg("WOL packet sent")
wolPackets.Inc()
return nil
}
// createMagicPacket creates a Wake-on-LAN magic packet
func createMagicPacket(mac net.HardwareAddr) []byte {
var buf bytes.Buffer
// Write 6 bytes of 0xFF
buf.Write(bytes.Repeat([]byte{0xFF}, 6))
// Write the target MAC address 16 times
for range 16 {
_ = binary.Write(&buf, binary.BigEndian, mac)
}
return buf.Bytes()
}