package wol import ( "encoding/hex" "fmt" "log" "net" "os" "strconv" "strings" ) // Service kapselt WOL-Einstellungen und -Verhalten type Service struct { broadcastAddr string port int debug bool } // NewService erstellt einen neuen Wake-on-LAN Service und liest Konfiguration // Konfiguration per Env: // - WOL_BROADCAST: explizite Broadcast-Adresse (z. B. 192.168.0.255). Wenn leer, werden Interface-Broadcasts verwendet // - WOL_PORT: Ziel-UDP-Port (Standard 9) // - WOL_DEBUG: bei "1", "true" (case-insensitive) werden Debug-Logs ausgegeben func NewService() *Service { bcast := strings.TrimSpace(os.Getenv("WOL_BROADCAST")) port := 9 if v := strings.TrimSpace(os.Getenv("WOL_PORT")); v != "" { if p, err := strconv.Atoi(v); err == nil && p > 0 && p <= 65535 { port = p } } debug := false if v := strings.ToLower(strings.TrimSpace(os.Getenv("WOL_DEBUG"))); v == "1" || v == "true" || v == "yes" { debug = true } return &Service{broadcastAddr: bcast, port: port, debug: debug} } // WakePC sendet ein Wake-on-LAN Paket an die angegebene MAC-Adresse func (s *Service) WakePC(mac string) error { cleanMAC := strings.ReplaceAll(strings.ReplaceAll(mac, ":", ""), "-", "") if s.debug { log.Printf("WOL: preparing magic packet for MAC %s (clean %s)", mac, cleanMAC) } magicPacket, err := s.createMagicPacket(cleanMAC) if err != nil { log.Printf("Fehler beim Erstellen des Magic Packets: %v", err) return err } if s.debug { preview := 12 if len(magicPacket) < preview { preview = len(magicPacket) } log.Printf("WOL: packet size=%d preview=%s...", len(magicPacket), strings.ToUpper(hex.EncodeToString(magicPacket[:preview]))) } if s.broadcastAddr != "" { // Explizite Broadcast-Adresse verwenden addr := fmt.Sprintf("%s:%d", s.broadcastAddr, s.port) if s.debug { log.Printf("WOL: sending to explicit broadcast %s", addr) } return s.sendViaUDP(magicPacket, nil, s.broadcastAddr) } // Über alle aktiven Interfaces senden (IPv4 Broadcast) broadcasts := s.collectInterfaceBroadcasts() if len(broadcasts) == 0 { // Fallback: Limited Broadcast if s.debug { log.Printf("WOL: no interface broadcasts found, falling back to 255.255.255.255") } return s.sendViaUDP(magicPacket, nil, "255.255.255.255") } var firstErr error var sent int for _, it := range broadcasts { laddr := &net.UDPAddr{IP: it.localIP, Port: 0} if s.debug { log.Printf("WOL: sending from %s to %s:%d", laddr.IP.String(), it.broadcast.String(), s.port) } err := s.sendViaUDP(magicPacket, laddr, it.broadcast.String()) if err != nil { if firstErr == nil { firstErr = err } if s.debug { log.Printf("WOL: send failed on iface %s → %s: %v", laddr.IP.String(), it.broadcast.String(), err) } continue } sent++ } if sent == 0 && firstErr != nil { return firstErr } if s.debug { log.Printf("WOL: successfully sent on %d interface(s)", sent) } return nil } // helper zum Senden über UDP4 (optional gebunden an LocalAddr) func (s *Service) sendViaUDP(packet []byte, laddr *net.UDPAddr, bcast string) error { raddr, err := net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%d", bcast, s.port)) if err != nil { return err } conn, err := net.DialUDP("udp4", laddr, raddr) if err != nil { return err } defer conn.Close() // Broadcast explizit aktivieren (OS-spezifisch) if err := enableBroadcast(conn, s.debug); err != nil { if s.debug { log.Printf("WOL: enable broadcast failed: %v", err) } } // Schreiben n, err := conn.Write(packet) if s.debug { log.Printf("WOL: wrote %d bytes to %s", n, raddr.String()) } return err } // createMagicPacket erstellt ein Wake-on-LAN Magic Packet func (s *Service) createMagicPacket(mac string) ([]byte, error) { macBytes, err := hex.DecodeString(mac) if err != nil { return nil, err } if len(macBytes) != 6 { return nil, fmt.Errorf("ungültige MAC-Länge: %d", len(macBytes)) } packet := make([]byte, 102) for i := 0; i < 6; i++ { packet[i] = 0xFF } for i := 6; i < 102; i += 6 { copy(packet[i:i+6], macBytes) } return packet, nil } // ifaceBroadcast repräsentiert eine Broadcast-Adresse plus lokale IP type ifaceBroadcast struct { localIP net.IP broadcast net.IP } // collectInterfaceBroadcasts ermittelt Broadcast-Adressen für alle aktiven IPv4-Interfaces func (s *Service) collectInterfaceBroadcasts() []ifaceBroadcast { var result []ifaceBroadcast ifs, err := net.Interfaces() if err != nil { if s.debug { log.Printf("WOL: failed to list interfaces: %v", err) } return result } for _, iface := range ifs { if (iface.Flags&net.FlagUp) == 0 || (iface.Flags&net.FlagLoopback) != 0 { continue } addrs, err := iface.Addrs() if err != nil { continue } for _, a := range addrs { ipNet, ok := a.(*net.IPNet) if !ok || ipNet.IP == nil { continue } ip4 := ipNet.IP.To4() if ip4 == nil { continue } mask := ipNet.Mask if len(mask) != 4 { continue } bcast := net.IPv4( ip4[0]|^mask[0], ip4[1]|^mask[1], ip4[2]|^mask[2], ip4[3]|^mask[3], ) result = append(result, ifaceBroadcast{localIP: ip4, broadcast: bcast}) } } return result } // ValidateMAC prüft, ob die MAC-Adresse gültig ist func ValidateMAC(mac string) bool { cleanMAC := strings.ReplaceAll(strings.ReplaceAll(mac, ":", ""), "-", "") if len(cleanMAC) != 12 { return false } _, err := hex.DecodeString(cleanMAC) return err == nil }