[TUTORIAL] Wake On LAN (WOL) for VMs and Containers

you have to configure it to watch the vmbr that is connected to your pfSense system sending the packets.

did you modify the script based on the pfsense WoL port number?
 
pfSense is on vmbr2. i have modified the script with both the interfaces (vmbr0 and vmbr2) but no success. also use port 40000 instead of 9. if you look at the script it basically does a lookup for the mac address you are sending the wol packet to, and then attempts to start the vm which has that "virtual" nic with that mac address. so not sure if the interface (vmbr0 or vmbr2) should really matter, as long you have one of them to route your packet to the mac address. i could be wrong though.
 
pfSense is on vmbr2. i have modified the script with both the interfaces (vmbr0 and vmbr2) but no success. also use port 40000 instead of 9. if you look at the script it basically does a lookup for the mac address you are sending the wol packet to, and then attempts to start the vm which has that "virtual" nic with that mac address. so not sure if the interface (vmbr0 or vmbr2) should really matter, as long you have one of them to route your packet to the mac address. i could be wrong though.
What does the debug output show if you watch it from the console when you send a WoL packet?
 
Great script, it's working like a charm.
Is there any possibility to have that script listening on port 9 AND 40000 so normal clients and OPNsense can wake up vms?

Or shall I simply create another script with the 2nd port set?!

Best, goeste
 
Thanks. You are free to modify it.
It's about 30 years old and has been modified numerous times.
 
Hello all,

I have updated the script to support both starting and resuming vm's using WOL:

To the other people that tried:
(There seems to be a bug or a feature in : qm that when used with the -verbose flag.
suspended and paused systems show as running.)

Bash:
#!/bin/bash

#Define bridge device in use on your system!
# E.G: vmbr0 or vmbr1

vmbr="vmbr0"
#We will use UDP port 9
portnr="9"

# fix: 10-12-24 Can now besides starting also "unpause/resume vm's" (also via Moonlight I tested)
# I will give "NO support" nor "emply" that is this code deemed fit for use!
# Use at your own risk! (people should use API instead)

while true; do

  sleep 5
  wake_mac=$(tcpdump -c 1 -UlnXi ${vmbr} ether proto 0x0842 or udp port ${portnr} 2>/dev/null |\

  sed -nE 's/^.*20:  (ffff|.... ....) (..)(..) (..)(..) (..)(..).*$/\2:\3:\4:\5:\6:\7/p')

  echo "Captured magic packet for address: \"${wake_mac}\""

  echo -n "Looking for existing VM: "

  matches=($(grep -il ${wake_mac} /etc/pve/qemu-server/*))

  if [[ ${#matches[*]} -eq 0 ]]; then

    echo "${#matches[*]} found"

  echo -n "Looking for existing LXC: "

  matches=($(grep -il ${wake_mac} /etc/pve/lxc/*))

  if [[ ${#matches[*]} -eq 0 ]]; then

    echo "${#matches[*]} found"

    continue

  elif [[ ${#matches[*]} -gt 1 ]]; then

    echo "${#matches[*]} found, using first found"

  else

    echo "${#matches[*]} found"

  fi

  vm_file=$(basename ${matches[0]})

  vm_id=${vm_file%.*}

  details=$(pct status ${vm_id} -verbose | egrep "^name|^status")

  name=$(echo ${details} | awk '{print $2}')

  status=$(echo ${details} | awk '{print $4}')

  if [[ "${status}" != "stopped" ]]; then

    echo "SKIPPED CONTAINER ${vm_id} : ${name} is ${status}"

  else

    echo "STARTING CONTAINER ${vm_id} : ${name} is ${status}"

    pct start ${vm_id}

  fi

    continue

  elif [[ ${#matches[*]} -gt 1 ]]; then

    echo "${#matches[*]} found, using first found"

  else

    echo "${#matches[*]} found"

  fi

  vm_file=$(basename ${matches[0]})

  vm_id=${vm_file%.*}

  # (Due to 'bug/odd feature' in qm): qm status ${vm_id} -verbose
  # will show paused and suspended vms as 'status: running')
  # solution querying only name using 'qm status -verbose'

  name=$(qm status ${vm_id} -verbose | egrep "^name" | awk '{print $2}')

  # And querying the status using just 'qm status'
  status=$(qm status ${vm_id} | awk '{print $2}')


  #So now it is possible to start/resume stopped/paused/suspended vms!
  if [[ "${status}" != "stopped" ]]; then

         if [[ "${status}" == "suspended" ]] || [[ "${status}" == "paused" ]]; then
         echo "Resuming VM ${vm_id}  Status of ${name} was ${status}"
         qm resume ${vm_id}
         elif [[ "${status}" != "suspended" ]] || [[ "${status}" != "paused" ]]; then
         echo "SKIPPED VM ${vm_id} because ${name} is ${status}"
         fi
  else
    echo "STARTING VM ${vm_id} : ${name} is ${status}"
    qm start ${vm_id}
  fi
done

Good luck! It works fine with moonlight to wake up a dozed off GameVM
 
Last edited:
  • Like
Reactions: Onoitsu2
My proxmox node has a dedicated interface on vmbr0 and all my vms and containers use vmbr2. ive tried wol_hack.sh with both interfaces and it wont capture incoming magic packets from any client (moonlight in my case). no error messages either. having said that my pfsense instance (on the same node) can send wol packets successfully to vms and wakes them up in 2 secs.

any ideas?
I've found some software that sends WOL sends it to subnet 255.255.255.255, but you need use 255.255.255.0 for this, so your proxmox host should see it. In my own testing and implementation of this, with UpSnap and some custom Ping and Shutdown commands, I have had to mess with all kinds of things. I have VM's and LXC's on their own isolated SDN's and can spin them up, so long as you are sending from an IP within the same range as the proxmox host IP, so like 192.168.1.0/24, versus 192.168.2.0/24 even while they are on a different numbering scheme entirely.
 
Not a network genius but:

Correct me if I am wrong but from what I understand is:

The host that is sending the WOL packet must be on the vmbr2,
as each bridge would have their own Broadcasting range.

As either the networks for vmbr1 vmbr2 are split in to different netmasks,
with different gateways or there is a different range all together.

Broadcasting stays with in its netmask

WOL is sent as a broacast on Mac address.

Did you cover the obvious? Firewall rules?
-Where are you triggering the WOL request from?

Maybe you need to add a route?
 
Last edited:
Hi,

A version of the script that also allows you to stop a VM or LXC sending the reverse MAC address:

Bash:
#!/bin/bash

# Define bridge device in use on your system!
# E.G: vmbr0 or vmbr1
vmbr="vmbr0"
# We will use UDP port 9
portnr="9"

# Function to invert MAC address (reverse byte order)
invert_mac_address() {
  local mac=$1
 
  # Split the MAC address into parts
  IFS=':' read -ra PARTS <<< "$mac"
 
  # Reverse the order of the parts
  local inverted="${PARTS[5]}:${PARTS[4]}:${PARTS[3]}:${PARTS[2]}:${PARTS[1]}:${PARTS[0]}"
 
  echo "$inverted"
}

# Function to search for MAC address in VM or LXC files and return matches
search_mac_address() {
  local mac_address=$1
  local container_type=$2
  local search_path=""
 
  if [[ "${container_type}" == "vm" ]]; then
    search_path="/etc/pve/qemu-server/*"
  elif [[ "${container_type}" == "lxc" ]]; then
    search_path="/etc/pve/lxc/*"
  else
    return 1
  fi
 
  local matches=($(grep -il ${mac_address} ${search_path}))
 
  echo -n "Looking for existing ${container_type^^}: "
 
  if [[ ${#matches[*]} -eq 0 ]]; then
    echo "${#matches[*]} found"
    return 1
  elif [[ ${#matches[*]} -gt 1 ]]; then
    echo "${#matches[*]} found, using first found"
  else
    echo "${#matches[*]} found"
  fi
 
  # Return the first match path
  echo "__MATCH_PATH__:${matches[0]}"
  return 0
}

# Function to manage VM operations
manage_vm() {
  local vm_path=$1
  local operation=$2 # "start" or "shutdown"
 
  local vm_file=$(basename "${vm_path}")
  local vm_id=${vm_file%.*}
 
  # Get VM name
  local name=$(qm status ${vm_id} -verbose | egrep "^name" | awk '{print $2}')
  # Get VM status
  local status=$(qm status ${vm_id} | awk '{print $2}')
 
  if [[ "$operation" == "start" ]]; then
    if [[ "${status}" != "stopped" ]]; then
      if [[ "${status}" == "suspended" ]] || [[ "${status}" == "paused" ]]; then
        echo "Resuming VM ${vm_id}  Status of ${name} was ${status}"
        qm resume ${vm_id}
      else
        echo "SKIPPED VM ${vm_id} because ${name} is ${status}"
      fi
    else
      echo "STARTING VM ${vm_id} : ${name} is ${status}"
      qm start ${vm_id}
    fi
  elif [[ "$operation" == "shutdown" ]]; then
    if [[ "${status}" == "stopped" ]]; then
      echo "SKIPPED VM ${vm_id} because ${name} is already ${status}"
    else
      echo "SHUTTING DOWN VM ${vm_id} : ${name} is ${status}"
      qm shutdown ${vm_id} --forceStop
    fi
  fi
}

# Function to manage LXC operations
manage_lxc() {
  local container_path=$1
  local operation=$2 # "start" or "shutdown"
 
  local container_file=$(basename "${container_path}")
  local container_id=${container_file%.*}
 
  local details=$(pct status ${container_id} -verbose | egrep "^name|^status")
  local name=$(echo ${details} | awk '{print $2}')
  local status=$(echo ${details} | awk '{print $4}')
 
  if [[ "$operation" == "start" ]]; then
    if [[ "${status}" != "stopped" ]]; then
      echo "SKIPPED CONTAINER ${container_id} : ${name} is ${status}"
    else
      echo "STARTING CONTAINER ${container_id} : ${name} is ${status}"
      pct start ${container_id}
    fi
  elif [[ "$operation" == "shutdown" ]]; then
    if [[ "${status}" == "stopped" ]]; then
      echo "SKIPPED CONTAINER ${container_id} : ${name} is already ${status}"
    else
      echo "SHUTTING DOWN CONTAINER ${container_id} : ${name} is ${status}"
      pct shutdown ${container_id} --forceStop
    fi
  fi
}

# Main loop
# I will give "NO support" nor "emply" that is this code deemed fit for use!
# Use at your own risk! (people should use API instead)
while true; do
  sleep 5
  wake_mac=$(tcpdump -c 1 -UlnXi ${vmbr} ether proto 0x0842 or udp port ${portnr} 2>/dev/null |\
  sed -nE 's/^.*20:  (ffff|.... ....) (..)(..) (..)(..) (..)(..).*$/\2:\3:\4:\5:\6:\7/p')
 
  echo "Captured magic packet for address: \"${wake_mac}\""
 
  # Check if the MAC is inverted (for shutdown operation)
  inverted_mac=$(invert_mac_address "${wake_mac}")
 
  # First try to find the original MAC for start operation
  # Capture the output in a variable
  vm_output=$(search_mac_address ${wake_mac} "vm")
 
  # Extract only the line with the path marker
  vm_match=$(echo "$vm_output" | grep "__MATCH_PATH__:" | sed 's/__MATCH_PATH__://')
 
  if [[ -n "${vm_match}" ]]; then
    echo "Found VM with matching MAC, path: ${vm_match}"
    manage_vm "${vm_match}" "start"
    continue
  fi

  # Check for LXC next
  lxc_output=$(search_mac_address ${wake_mac} "lxc")
 
  # Extract only the line with the path marker
  lxc_match=$(echo "$lxc_output" | grep "__MATCH_PATH__:" | sed 's/__MATCH_PATH__://')
 
  if [[ -n "${lxc_match}" ]]; then
    echo "Found LXC with matching MAC, path: ${lxc_match}"
    manage_lxc "${lxc_match}" "start"
    continue
  fi
 
  # If no match found for original MAC, check for inverted MAC (shutdown operation)
  echo "Checking for inverted MAC address: ${inverted_mac} (for shutdown)"
 
  # Check for VM with inverted MAC
  vm_output=$(search_mac_address ${inverted_mac} "vm")
 
  # Extract only the line with the path marker
  vm_match=$(echo "$vm_output" | grep "__MATCH_PATH__:" | sed 's/__MATCH_PATH__://')
 
  if [[ -n "${vm_match}" ]]; then
    echo "Found VM with matching inverted MAC, path: ${vm_match}"
    manage_vm "${vm_match}" "shutdown"
    continue
  fi
 
  # Check for LXC with inverted MAC
  lxc_output=$(search_mac_address ${inverted_mac} "lxc")
 
  # Extract only the line with the path marker
  lxc_match=$(echo "$lxc_output" | grep "__MATCH_PATH__:" | sed 's/__MATCH_PATH__://')
 
  if [[ -n "${lxc_match}" ]]; then
    echo "Found LXC with matching inverted MAC, path: ${lxc_match}"
    manage_lxc "${lxc_match}" "shutdown"
    continue
  fi
 
  echo "No matching VM or container found for MAC: ${wake_mac} or inverted MAC: ${inverted_mac}"
done

Captured magic packet for address: "bc:24:11:6b:40:7d"
Found VM with matching MAC, path: /etc/pve/qemu-server/103.conf
STARTING VM 103 : Win11-1-Osiris is stopped
swtpm_setup: Not overwriting existing state file.
Captured magic packet for address: "7d:40:6b:11:24:bc"
Checking for inverted MAC address: bc:24:11:6b:40:7d (for shutdown)
Found VM with matching inverted MAC, path: /etc/pve/qemu-server/103.conf
SHUTTING DOWN VM 103 : Win11-1-Osiris is running
Captured magic packet for address: "bc:24:11:a7:1c:62"
Found LXC with matching MAC, path: /etc/pve/lxc/101.conf
SKIPPED CONTAINER 101 : coolify is running
Captured magic packet for address: "62:1c:a7:11:24:bc"
Checking for inverted MAC address: bc:24:11:a7:1c:62 (for shutdown)
Found LXC with matching inverted MAC, path: /etc/pve/lxc/101.conf
SHUTTING DOWN CONTAINER 101 : coolify is running
Captured magic packet for address: "bc:24:11:a7:1c:62"
Found LXC with matching MAC, path: /etc/pve/lxc/101.conf
STARTING CONTAINER 101 : coolify is stopped

Build with Anthropic: Claude 3.7 Sonnet :cool:
 
  • Like
Reactions: Tsever and Onoitsu2