WOL-Broadcast überarbeiten

Antworten
volfo
Beiträge: 24
Registriert: 26 Mär 2021, 09:35

WOL-Broadcast überarbeiten

Beitrag von volfo »

Hallo uib,

wir in Ulm haben das WOL - Problem, dass wir maximal 10 Clients gleichzeitig über configed auswählen und WakeOnLan ausführen können.
Jeder weitere Aufruf wird von den Routern gedropped. Der Packageburst überschreitet den Maximalwert.

Beim Debuggen sind wir final im Opsi-WOL Code an dieser for-Schleife hängen geblieben:

Code: Alles auswählen

Line 294 			for broadcastAddress, targetPorts in self._broadcastAddresses.items():
Auch wenn die Clients in unterschiedlichen Subnetzen sind (bei uns 21 Stück) wird an alle der WOL-Befehl gesendet.

Könntet ihr hier eine Vorauswahl einbauen?
zB. könnte in der hostcontrol.conf die broadcastAddresses noch durch die jeweilige Subnetmask erweitert werden und dann könnte
die for-Schleife durch eine switch-Anweisung ersetzt und die passende Broadcast-Addresse gewählt werden. (irgendwie so zumindest)

Den Burst hier zu reduzieren wäre sicher eine feine Sache.

Viele Grüße aus Ulm
Volker
volfo
Beiträge: 24
Registriert: 26 Mär 2021, 09:35

Re: WOL-Broadcast überarbeiten

Beitrag von volfo »

Hier eine Präzisierung der WOL-Überarbeitung:

für unsere Zwecke habe ich ein WOL - pythonskript geschrieben, dass directed broadcasts für WOL verwendet.
Es läuft auf unserem opsi-server mit opsi-python, wäre also wahrscheinlich recht einfach in opsi zu übernehmen.
Entgegen meinem ersten Post benötigt es keine weiteren Daten, nur eine passende Sortierung im Ipaddress-Format.
Das macht den WakeOnLan-Aufruf viel performanter,
bei uns reduziert es den WOL-Traffic etwa um den Faktor 20 (abhängig von der Anzahl an Subnetzen und Clients).

unter Verwendung von

Code: Alles auswählen

import ipaddress
nehme ich alle broadcast-Addressen aus der hostcontrol.conf und sortiere sie:

Code: Alles auswählen

    for adr in broadcasts:
        bcas.append(ipaddress.ip_address(adr))    
    bcas.sort()
dann kann man für die ClientIP aus den sortierten broadcast-Addressen leicht die passende herausfinden:

Code: Alles auswählen

ipadr = ipaddress.ip_address(ip)
#getting broadcast-address for ip
a = iter(bcas)
for bca in bcas:
	if ((ipadr<bca) and (ipadr>next(a))):
		ownbca=bca
		break
und somit kann man aus einem Broadcast über alle Broadcast-Addressen einen directed broadcast in das passende Subnetz machen.
Benutzeravatar
j.schneider
uib-Team
Beiträge: 1789
Registriert: 29 Mai 2008, 15:14

Re: WOL-Broadcast überarbeiten

Beitrag von j.schneider »

Hallo,

sieht gut aus, wir bauen das gerne ein, vielen Dank!
Bitte einmal das komplette Skript posten oder per E-Mail an uns.

Danke!

Grüße
Jan Schneider
volfo
Beiträge: 24
Registriert: 26 Mär 2021, 09:35

Re: WOL-Broadcast überarbeiten

Beitrag von volfo »

Bitte sehr:

Code: Alles auswählen

#!/usr/bin/opsi-python
# -*- coding: utf-8 -*-

# This file is part of the kiz-opsi-infrastructure
# WakeUpScript - WakeOnLan
# working on Opsi4.2-Server without using Opsi-Server-Api
# Copyright (C) 2020 kiz - Ulm University <kiz@uni-ulm.de>

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import subprocess
import time
import json
import ipaddress
import logging
from logging.handlers import RotatingFileHandler

### setting up logging ###
logFormatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-8.8s]  %(message)s", datefmt="%Y/%m/%d %H:%M:%S")
rootLogger = logging.getLogger(__name__)
rootLogger.setLevel(logging.INFO)

fileHandler = RotatingFileHandler("wakeup.log", maxBytes=2000000, backupCount=3)
fileHandler.setFormatter(logFormatter)
rootLogger.addHandler(fileHandler)

### basic configs ###

#DataBase-Dumb with host_getHashes(attributes=[])
allClients= "setup/allClients.json"
#ClientList to WakeUp
clientlisttxt= "clientsList.txt"
#Broadcast-Addresses (extracted from etc/opsi/hostcontrol.conf)
broadcasts= ["134.60.1.255",
            "134.60.2.255",
            "134.60.15.255",
            "134.60.32.255",
            "134.60.39.255",
            "134.60.40.255",
            "134.60.46.255",
            "134.60.55.255",
            "134.60.101.255",
            "134.60.112.63",
            "134.60.127.255",
            "134.60.128.255",
            "134.60.130.255",
            "134.60.132.255",
            "134.60.133.255",
            "134.60.193.255",
            "134.60.194.255",
            "134.60.202.255",
            "134.60.205.255",
            "134.60.206.255",
            "134.60.215.255"]

### MAIN() ###

def main():
    #Preparing DataSet
    errorClients=[]    
    jsonclients= __readJsonClients(allClients)
    clientList= __readClientList(clientlisttxt)
    clientsToWakeUp= __setClientsToWakeUp(clientList,jsonclients,broadcasts,errorClients)
    
    #logging malfunctional Clients    
    if len(errorClients)>0:
        rootLogger.warning("Found {} malfunctional Clients in DataSet: ".format(len(errorClients)))
        for r in errorClients:
            rootLogger.warning("ID={} MAC={} IP={}".format(r["id"], r["hardwareAddress"], r["ipAddress"]))          

    #checking ClientList
    rootLogger.info("Reading Clients from file: {}".format(clientlisttxt))
    for r in clientList:
        # item does not exist
        if not any(d['id'] == r for d in clientsToWakeUp):            
            rootLogger.warning("ClientID from ClientList does not exists in DataSet: {}".format(r))
            print("Error with: {}".format(r))    
            
    #starting Mainroutine     
    rootLogger.info("Prepared {} Clients. Starting procedure".format(len(clientsToWakeUp)))   
    for client in clientsToWakeUp:
        rootLogger.info("Checking client: {}".format(client["id"]))        
        if __isPingable(client):
            rootLogger.info("Client pings, next one...") 
        else:
            rootLogger.info("Sending Wol to {}".format(client["id"]))
            __powerOn(client)
        time.sleep(2)
    rootLogger.info("Skript finished - see you next time")


def __isPingable(client):
    p = subprocess.Popen(['ping', '-c', '1', client["id"]], stdout=subprocess.PIPE)
    p.wait()
    if p.poll():
        return False
    else:
        return True


def __powerOn(client):
    with subprocess.Popen(['wakeonlan', '-i', str(client["bca"]), '-p', '12287', client["mac"]], stdout=subprocess.PIPE) as proc:
        rootLogger.debug(proc.stdout.read())
    proc.wait()
    return proc.poll()  


def __readClientList(clientlist):
    with open(os.path.dirname(os.path.realpath(__file__)) + '/' + clientlist) as f:
        clients = f.readlines()
        # you may also want to remove whitespace characters like `\n` at the end of each line
    clients = [client.strip() for client in clients]
    return clients


def __readJsonClients(allClients):
    with open(os.path.dirname(os.path.realpath(__file__)) + '/' + allClients, 'r') as json_file:
        jsonClients = json.load(json_file)
    return jsonClients


def __setClientsToWakeUp(clientList, jsonClients, broadcasts,errorClients):
    #prepare broadcast-addresses
    bcas=[]
    for adr in broadcasts:
        bcas.append(ipaddress.ip_address(adr))    
    bcas.sort()
    #get data from allClients
    clientsToWakeUp=[]
    for dic in jsonClients:
        id = dic.get("id")
        ip = dic.get("ipAddress")        
        mac = dic.get("hardwareAddress")                
        ownbca=""
        if ip==None:
            ipadr=None            
            errorClients.append(dic)
        else:
            ipadr = ipaddress.ip_address(ip)
             #getting broadcast-address for ip       
            a = iter(bcas)
            for bca in bcas:
                if ((ipadr<bca) and (ipadr>next(a))):
                    ownbca=bca
                    break
        if id in clientList:
            clientsToWakeUp.append({"id":id, "ip":ipadr,"mac":mac,"bca":ownbca})               
    return clientsToWakeUp      


if __name__ == "__main__":
    # execute only if run as a script
    main()

Benutzeravatar
j.schneider
uib-Team
Beiträge: 1789
Registriert: 29 Mai 2008, 15:14

Re: WOL-Broadcast überarbeiten

Beitrag von j.schneider »

Danke für das Skript, ich habe mir das angeschaut.
Ich würde vorschlagen, die hostcontrol.conf so zu ändern, dass man steuern kann für welche Netzwerkadresse welche Broadcast-Adressen verwendet werden sollen. Hierbei wird dann der Eintrag ausgewählt, der für die hinterlegte IP-Adresse des Clients am besten passt.
Ein Beispiel:

Code: Alles auswählen

config = {
	"broadcastAddresses": {
		"0.0.0.0/0": {
			"255.255.255.255": [7, 9, 12287]
		},
		"10.10.0.0/16": {
			"10.10.1.255": [12287],
			"10.10.2.255": [12287]
		},
		"10.10.3.0/24": {
			"10.10.3.255": [12287]
		},
		"192.168.1.0/24": {
			"192.168.1.255": [12287, 9, 12287]
		}
	}
}
Was haltet ihr davon?
volfo
Beiträge: 24
Registriert: 26 Mär 2021, 09:35

Re: WOL-Broadcast überarbeiten

Beitrag von volfo »

Den Vorschlag finde ich gut, damit wäre es das Verhalten auch in der config sichtbar und verständlich.
Benutzeravatar
j.schneider
uib-Team
Beiträge: 1789
Registriert: 29 Mai 2008, 15:14

Re: WOL-Broadcast überarbeiten

Beitrag von j.schneider »

Wir haben das jetzt so umgesetzt.
Folgende Pakete werden dafür benötigt (aktuell in experimental):

opsi-utils 4.2.0.149-1
opsiconfd 4.2.0.228-1
opsi-server 4.2.0.61-1

Die Dokumentation dazu ist in Arbeit.
Antworten