blob: fe0320c5022e04c1fd75cd05d3c543b06a218d75 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package power
import (
"context"
"errors"
"fmt"
"net"
"regexp"
"time"
)
var (
macAddrRegex = regexp.MustCompile(`(?i)^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$`)
// Magic Packet header is 0xFF repeated 6 times.
magicPacketHeader = []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
)
const (
magicPacketLength = 102
// Controller machines use 192.168.42.1/24 for swarming bots
// This will broadcast to that entire subnet.
botBroadcastAddr = "192.168.42.255:9"
// Controller machines have multiple interfaces, currently
// 'eno2' is used for swarming bots.
botInterface = "eno2"
)
// Reboot sends a WakeOnLAN magic packet {magicPacketHeader + macAddr x 16}
// using the specified network interface to the broadcast address
func WOLReboot(broadcastAddr, interfaceName, macAddr string) error {
if !macAddrRegex.Match([]byte(macAddr)) {
return fmt.Errorf("Invalid MAC: %s", macAddr)
}
remoteHwAddr, err := net.ParseMAC(macAddr)
if err != nil {
return err
}
localAddr, err := getUDPAddrFromIFace(interfaceName)
if err != nil {
return err
}
remoteAddr, err := net.ResolveUDPAddr("udp", broadcastAddr)
if err != nil {
return err
}
return sendMagicPacket(localAddr, remoteAddr, remoteHwAddr)
}
func getUDPAddrFromIFace(ifaceName string) (*net.UDPAddr, error) {
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
return nil, err
}
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}
for _, addr := range addrs {
if ipAddr, ok := addr.(*net.IPNet); ok {
// Need an IPv4, non-loopback address to send on
if !ipAddr.IP.IsLoopback() && ipAddr.IP.To4() != nil {
return &net.UDPAddr{
IP: ipAddr.IP,
}, nil
}
}
}
return nil, errors.New("No UDPAddr found on interface")
}
func sendMagicPacket(localAddr, remoteAddr *net.UDPAddr, remoteHwAddr net.HardwareAddr) error {
packet := magicPacketHeader
for i := 0; i < 16; i++ {
packet = append(packet, remoteHwAddr...)
}
if len(packet) != magicPacketLength {
return fmt.Errorf("Wake-On-LAN packet incorrect length: %d", len(packet))
}
conn, err := net.DialUDP("udp", localAddr, remoteAddr)
if err != nil {
return err
}
defer conn.Close()
// Attempt to send the Magic Packet TEN times in a row. The UDP packet sometimes
// does not make it to the DUT and this is the simplest way to increase the chance
// the device reboots.
for i := 0; i < 10; i++ {
n, err := conn.Write(packet)
if n != magicPacketLength {
return errors.New("Failed to send correct Wake-On-LAN packet length")
}
if err != nil {
return err
}
time.Sleep(1 * time.Second)
}
return nil
}
type WOLPowerManager struct {
botBroadcastAddr string
botInterface string
mac string
}
func NewWOLPowerManager(mac string) *WOLPowerManager {
return &WOLPowerManager{
botBroadcastAddr: botBroadcastAddr,
botInterface: botInterface,
mac: mac,
}
}
func (w *WOLPowerManager) Powercycle(ctx context.Context) error {
return WOLReboot(w.botBroadcastAddr, w.botInterface, w.mac)
}