Intro

In a previous post I showed you how to configure a port-mirror in Proxmox. In that post, I used a bit of a dirty hack (bash scripts and crontab) to ensure the port-mirror is activated if the host or the VM reboots.

Luckily for me, I have some really smart colleagues who mentioned that in libvirt, there is a hook mechanism that can be used to trigger scripts based on VM state. A quick duck hunt revealed that Proxmox has implemented this feature as well 🥳

In this post I will show you how to use Hook Scripts with Proxmox to enable/disable a port-mirror depending on the VMs execution phase .

As an added bonus, I will also show you how to duplicate a port-mirror source, to multiple destinations within Proxmox.

The following software was used in this post.

  • Proxmox - 7.0-11

Lab Network

The following diagram show the lab setup outlined in this post. The goal, is to have traffic port-mirrored from the Juniper switch to both the eda01 and eta01 VMs. Both the eda01 and eta01 VMs need a dedicated feed therefore, port-mirror traffic will be duplicated on vmbr1

blog/proxmox-hook-script-port-mirror/port-mirror-lab.png

The following points summarise the diagram.

  • fw01 is a PaloAlto firewall and is the default gateway for all networks.
  • sw01 is a Juniper switch and is used to connect LAN devices.
  • pmx01 is a Proxmox host and has two physical interfaces:
    • eno1 is configured as a trunk port and carries multiple VLANs.
    • enp1s0 is configured as an access port.
  • pmx01 has the following virtual network components:
    • vmbr0 and vmbr1 are virtual switches.
    • A tap* interface is created for each vNIC interface on a VM automatically.
    • Tap interfaces have the naming format; tap<vm-id>i<interface-number>.
  • eda01, exa01 and eta01 are VM guests hosted on the Proxmox host.
  • Port-mirror sources are denoted in yellow.
  • Port-mirror destinations are denoted in green.

Hook Scripts

As previously mentioned, hook scripts allow you to run a script based on a VMs execution phase .

This feature is not extensively documented (that I could find). From what I can gather the supported phases are:

  • pre-start
  • post-start
  • pre-stop
  • post-stop

An example hook script can be found in the onboard docs at the following location:

/usr/share/pve-docs/examples/guest-example-hookscript.pl

For all you Perl loving heathons, that should be right up your alley. For everyone else, it looks like you can run a script in any language that has a runtime installed.

For my purposes, I will be using the bash script from the previous post with a couple of alterations to make it more portable and useable for multiple guests.

Hook Script Location

Hook scripts need to be stored in a snippets/ directory on a storage pool that Proxmox knows about. I will place my scripts in the /var/lib/vz/ directory that maps to the local storage pool.

Hook Script

Create a file named extrahop-port-mirror.sh in the /var/lib/vz/snippets/ directory with the following contents.

file
#! /usr/bin/env bash
# /var/lib/vz/snippets/extrahop-port-mirror.sh
VM_ID=$1;
EXECUTION_PHASE=$2
SOURCE_PORT="enp1s0";
VM_BRIDGE="vmbr1";
LOGGING=/root/scripts/extrahop-port-mirror.log;

function create_mirror {

  /usr/bin/date >> $LOGGING;

  /usr/bin/echo "Creating mirror on $VM_BRIDGE for $VM_ID"... >> $LOGGING;

  /usr/bin/ovs-vsctl \
    -- --id=@"$SOURCE_PORT" get Port "$SOURCE_PORT" \
    -- --id=@tap"$VM_ID"i1 get Port tap"$VM_ID"i1 \
    -- --id=@"$VM_ID"m create \
           Mirror name="$VM_ID"-mirror \
           select-dst-port=@"$SOURCE_PORT" \
           select-src-port=@"$SOURCE_PORT" \
           output-port=@tap"$VM_ID"i1 \
    -- add Bridge "$VM_BRIDGE" mirrors @"$VM_ID"m; >> $LOGGING;

  /usr/bin/echo "####################" >> $LOGGING;

}

function clear_mirror {

   /usr/bin/date >> $LOGGING;

  /usr/bin/echo "Clearing mirror on $VM_BRIDGE for $VM_ID..." >> $LOGGING;

  /usr/bin/ovs-vsctl \
    -- --id=@"$VM_ID"m get Mirror "$VM_ID"-mirror \
    -- remove Bridge "$VM_BRIDGE" mirrors @"$VM_ID"m; >> $LOGGGING;

  /usr/bin/echo "####################" >> $LOGGING;

}

function show_mirrors {

  /usr/bin/date >> $LOGGING;

  /usr/bin/echo "Show existing mirrors..." >> $LOGGING;

  /usr/bin/ovs-vsctl list Mirror >> $LOGGING;

  /usr/bin/echo "####################" >> $LOGGING;

}

if [[ "$EXECUTION_PHASE" == "post-start" ]]; then

  clear_mirror;

  create_mirror;

  show_mirrors;

elif [[ "$EXECUTION_PHASE" == "pre-stop" ]]; then

  clear_mirror;

  show_mirrors;

fi

When Proxmox executes this script, it does so with 2 arguments.

  • VM_ID
  • EXECUTION_PHASE

These arguments can be mapped to variables in the script. In my case I am mapping them as follows.

  • VM_ID=$1
  • EXECUTION_PHASE=$2

The other variables: SOURCE_PORT , VM_BRIDGE and LOGGING should be confirgured to match your setup.

This script allows you to add and remove multiple port-mirror destinations by using the add and remove commands rather than the set and clear commands from the previous post.

The other thing that is required, is to make the script executable.

cmd
chmod +x /var/lib/vz/snippets/extrahop-port-mirror.sh

Connect Hook Script

Now that the hook script is created, we need to connect it to a VM. Connect the hookscript to both the eda01 and eta01 VMs with the following command.

cmd
qm set 201 --hookscript local:snippets/extrahop-port-mirror.sh
qm set 204 --hookscript local:snippets/extrahop-port-mirror.sh

# Output
update VM 201: -hookscript local:snippets/extrahop-port-mirror.sh
update VM 204: -hookscript local:snippets/extrahop-port-mirror.sh

That's it! Now when the VM or host shuts down or restarts, the port mirror will be automagically activated/deactivated. Additionally, vmbr1 is sending a duplicate data feed to both the eda01 and eta01.

How to Disconnect Hook a Script

If for some reason you want to disconnect a hook script from a VM, use the following command.

cmd
qm set 201 --delete hookscript

# Output
update VM 201: -delete hookscript

Example Log File

Below is an example of the log file that is generated.

cmd
####################
Sat Nov 27 06:24:50 AEST 2021
Clearing mirror on vmbr1 for 204...
####################
Sat Nov 27 06:24:50 AEST 2021
Show existing mirrors...
_uuid               : 89c4a723-130d-4871-a84d-d143e165ec7d
external_ids        : {}
name                : "201-mirror"
output_port         : a9e16d6a-2010-484e-a924-cb4f1a46195f
output_vlan         : []
select_all          : false
select_dst_port     : [dbd6b1f0-2cf0-453c-881e-a888f655e83a]
select_src_port     : [dbd6b1f0-2cf0-453c-881e-a888f655e83a]
select_vlan         : []
snaplen             : []
statistics          : {tx_bytes=82504527671, tx_packets=82870736}
####################
Sat Nov 27 06:25:03 AEST 2021
Clearing mirror on vmbr1 for 204...
####################
Sat Nov 27 06:25:03 AEST 2021
Creating mirror on vmbr1 for 204...
####################
Sat Nov 27 06:25:03 AEST 2021
Show existing mirrors...
_uuid               : 7c33f30a-2bdf-44d2-8ce2-8d7d92d5cc7f
external_ids        : {}
name                : "204-mirror"
output_port         : e36be586-eda1-44e1-b6d0-45b802b16ecf
output_vlan         : []
select_all          : false
select_dst_port     : [dbd6b1f0-2cf0-453c-881e-a888f655e83a]
select_src_port     : [dbd6b1f0-2cf0-453c-881e-a888f655e83a]
select_vlan         : []
snaplen             : []
statistics          : {}

_uuid               : 89c4a723-130d-4871-a84d-d143e165ec7d
external_ids        : {}
name                : "201-mirror"
output_port         : a9e16d6a-2010-484e-a924-cb4f1a46195f
output_vlan         : []
select_all          : false
select_dst_port     : [dbd6b1f0-2cf0-453c-881e-a888f655e83a]
select_src_port     : [dbd6b1f0-2cf0-453c-881e-a888f655e83a]
select_vlan         : []
snaplen             : []
statistics          : {tx_bytes=82511889277, tx_packets=82881722}
####################

Outro

In this post, I showed you how use a Proxmox hook script to automagically start/stop a port-mirror destined for multiple VMs connected to the same virtual bridge. I believe this method is a nice improvement over my previous attempt and I hope you find it useful.