VLAN DMZ on Debian Virtual Server with Qemu and Libvirt

I have a set of virtual servers running on a single home physical server. I like to be able to remote into my home servers when I’m away from home so that I can get to the home automation if I need to. So one of my virtual servers is internet facing – I have a port on my router mapped through to ssh on that server.

I had thought I had configured that server with public key logon only to the ssh, but apparently I hadn’t, and I got a breach on that server. Which is embarrassing. So I decided to do a solid pass through the security of my environment.

My desire is to run a DMZ network, with only a jump-box machine in the DMZ visible to the internet. This machine will be locked down tightly, and only permit ssh off that machine to a single machine in the inner network zone. No root login will be permitted on any ssh. That means that a penetration of the environment would require cracking the public key logon on the jump-box, then cracking the username/password of the single machine that is connectable from that jump-box.

I’ll walk through the configuration I did there, but a key in all this is that it means my host server (the physical machine that hosts the virtuals) is actually connected to both the inner network and the DMZ network. But I don’t want that machine answering any connections on the DMZ network – that would effectively put the physical host into the DMZ. The hardest thing in this setup was having virtual servers that are on the DMZ network without having the host itself on that network.

So, configuration that I did. I am using a single physical network interface on the host, and I want from that:

  • The physical network interface itself. I’d like this to not get an IP address, as I don’t want it to be routable
  • A bridge interface build from the physical network, with a DHCP address on the inner network, this is on VLAN 1 / default. This bridge is made available to all virtual machines that are on the inner network
  • A virtual network interface that is build from the underlying physical network interface, but that is tagged to be on VLAN 3 (my DMZ). All traffic that transits this interface will be VLAN 3 without any configuration needed to be done on the individual machines. I don’t want this interface to be routable – I don’t want the host iself
  • A bridge interface build from this virtual network interface, allowing virtuals to be on this DMZ (VLAN 3). I don’t want this routable directly from the host, as I don’t want the host on the DMZ network

To achieve this, I have configuration in both /etc/network/interfaces, and in /etc/dhcpcd.conf. Both of these are involved in network routing and DHCP addresses, and I found that if I just create bridge configuration in the interfaces file then dhcpcd will still grab that bridge and allocate an IP address to, making it a routable interface.

Note that I did play around with using the nogateway directive in dhcpcd.conf, and with using post-up hooks to delete the routes after they were created. Neither of those gave the result I wanted.

Here is my configuration in /etc/network/interfaces:

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

auto enp3s0
iface enp3s0 inet manual

# iface enp3s0 inet manual

# vlan network interface - the .3 automatically tells it what to tag (apparently) - 3 is DMZ
auto enp3s0.3
iface enp3s0.3 inet manual

# The primary network interface
auto br0
iface br0 inet dhcp
  bridge_ports enp3s0
  bridge_stp off
  bridge_maxwait 0
  bridge_waitport 0
  bridge_fd 0
  metric 10
  pre-up /sbin/ifconfig enp3s0 mtu 9000

# the vlan bridge - DMZ
auto br0-3
iface br0-3 inet manual
  bridge_ports enp3s0.3
  bridge_stp off
  bridge_maxwait 0
  bridge_waitport 0
  bridge_fd 0
  metric 1000
  pre-up /sbin/ifconfig enp3s0.3 mtu 9000

This defines my primary interface enp3s0, but tells it that I want a manual IP address – in other words, please don’t allocate an IP address from this service. Note that if you only do this, dhcpcd will still allocate an address, so we need more configuration in dhcpcd.conf.

Then it defines a bridge br0 from that interface. This is the primary network connection that this server uses, and also the bridge used for any virtual that is on the inner network. This gets it’s IP address from DHCP, my DHCP server allocates a static address based on MAC.

Next, I define a virtual network interface on my DMZ network VLAN 3, enp3s0.3. It appears that the .3 on the end of the interface is how you tell it that you would like it tagged to VLAN 3, there isn’t some sort of tag directive. Again, I don’t want an IP address for this interface, I don’t want the host answering on the DMZ network.

Finally, I define a virtual bridge br0-3 on the virtual network interface, giving me a bridge that I can make available to my virtual servers that I want on the DMZ network.

Now, I need to do configuration in /etc/dhcpcd.conf to tell it which of these devices I want to have IP addresses. I am also blocking ipv6, because I haven’t worked out yet what firewalling I’d need on this.

interface br0

interface enp3s0

interface enp3s0.3

interface br0-3

So we have interface br0, which we are happy to have an ipv4 address (the only addressable interface) but we don’t want an ipv6 address on it. Then our other three interfaces all have no ipv4 or ipv6 on them, meaning they’re not routable at all.

We can check the routing that we get by using ip route:

root@server:/home/paul# ip route
default via dev br0 proto dhcp src metric 10 
default via dev br0 metric 10 dev br0 proto dhcp scope link src metric 10 

This is telling us that the only routes available are via the gateway on the inner network (, and via br0 with its IP address. Prior to this configuration you would have seen here something more like:

paul@server:~/ansible$ ip route
default via dev br0 proto dhcp src metric 10 
default via dev enp3s0 proto dhcp src metric 202 
default via dev enp3s0.3 proto dhcp src metric 204 
default via dev br0-3 proto dhcp src metric 206 dev br0 proto dhcp scope link src metric 10 dev enp3s0 proto dhcp scope link src metric 202 dev enp3s0.3 proto dhcp scope link src metric 204 dev br0-3 proto dhcp scope link src metric 206 

This is what I had before I started the dhcpcd.conf changes, and it meant the host server was answering on all 4 interfaces, and it was very hard to work out what was going on / where network traffic was going.

Pet Tutor controlled from Raspberry Pi and OpenHab

My partner has a Pet Tutor version 3, which is a device that feeds treats triggered by a bluetooth phone app. She uses this to treat the dog when she’s not near as part of training her not to be concerned when people come near, or to stay on her bed when my partner goes down the other end of the house.

The problem is that it’s bluetooth only, so she has limited range. She has a wifi camera that means we can check on the dog when we’re outside, or out and about. She’d like to be able to also treat when outside bluetooth range, or even outside the house entirely – via public internet.

My intention was to use existing raspberry pi devices I have to control the Pet Tutor. You need either a pi3 or newer, or a bluetooth dongle on an older pi. This breaks into three steps:

  1. Get your pi to see and connect to the pet tutor, to manually trigger a treat
  2. Write a script so your pi will send the command based on an MQTT event
  3. Update your home automation (in my case OpenHab) or some other service to send the MQTT event

Firstly, getting your pi to see the device. You should start by making sure your pi is all up to date:

sudo apt update
sudo apt upgrade

Next, see if you can see the device directly using bluetooth tools. Here we’re turning on our bluetooth device on the pi, and then scanning for bluetooth devices. We’re looking for the device with a name of PTFeeder, and getting the MAC address from it:

pi@raspberrypi3:~ $ sudo bluetoothctl
Agent registered
[bluetooth]# scan on
Discovery started
[CHG] Controller B8:27:EB:1D:38:31 Discovering: yes
[NEW] Device EB:EF:A9:56:B4:4A EB-EF-A9-56-B4-4A
[NEW] Device 0B:53:55:D1:E9:10 0B-53-55-D1-E9-10
[NEW] Device 46:2A:03:3B:A0:4C 46-2A-03-3B-A0-4C
[CHG] Device EB:EF:A9:56:B4:4A RSSI: -84
[NEW] Device 88:C6:26:43:3B:16 88-C6-26-43-3B-16
[NEW] Device 4C:96:E8:06:3E:1F 4C-96-E8-06-3E-1F
[NEW] Device 4B:A3:07:0C:47:38 4B-A3-07-0C-47-38
[NEW] Device 76:8A:A5:13:54:9F 76-8A-A5-13-54-9F
[NEW] Device 3C:22:FB:AA:07:0B 3C-22-FB-AA-07-0B
[CHG] Device 88:C6:26:43:3B:16 ManufacturerData Key: 0x0003
[CHG] Device 88:C6:26:43:3B:16 ManufacturerData Value:
00 17 1a 00 00 0e 10 32 7e fe 0b 1e 88 c6 26 43 …….2~…..&C
3b 16 1a ;..
[NEW] Device C8:69:CD:60:21:9E C8-69-CD-60-21-9E
[NEW] Device 00:05:C6:1F:56:5A PTFeeder
[bluetooth]# quit

Next, we’ll connect to that PetTutor device directly using gatt tool, and check the services and characteristics it offers. We’re looking for the specific handle that we send messages to on the device.

pi@raspberrypi:~ $ sudo gatttool -b 00:05:C6:1F:56:5A -I
[00:05:C6:1F:56:5A][LE]> connect
Attempting to connect to 00:05:C6:1F:56:5A
Connection successful
[00:05:C6:1F:56:5A][LE]> primary
attr handle: 0x0001, end grp handle: 0x000b uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle: 0x000c, end grp handle: 0x000f uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle: 0x0010, end grp handle: 0x0033 uuid: b0e6a4bf-cccc-ffff-330c-0000000000f0
attr handle: 0x0034, end grp handle: 0x0038 uuid: 0000180f-0000-1000-8000-00805f9b34fb
attr handle: 0x0039, end grp handle: 0x004b uuid: 0000180a-0000-1000-8000-00805f9b34fb

Looking at the characteristics list, we’re looking for the one with the uuid b0e6a4bf-cccc-ffff-330c-0000000000f0. In my case the attr handle is 0x0010. We then scan for characteristics from the attr handle to the next handle:

[00:05:C6:1F:56:5A][LE]> characteristics 0x0010 0x0034
handle: 0x0011, char properties: 0x0b, char value handle: 0x0012, uuid: b0e6a4bf-cccc-ffff-330c-0000000000f2
handle: 0x0015, char properties: 0x12, char value handle: 0x0016, uuid: b0e6a4bf-cccc-ffff-330c-0000000000f3
handle: 0x0019, char properties: 0x0b, char value handle: 0x001a, uuid: b0e6a4bf-cccc-ffff-330c-0000000000f1
handle: 0x001d, char properties: 0x0b, char value handle: 0x001e, uuid: b0e6a4bf-cccc-ffff-330c-0000000000f4
handle: 0x0022, char properties: 0x0b, char value handle: 0x0023, uuid: b0e6a4bf-cccc-ffff-330c-0000000000f5
handle: 0x0027, char properties: 0x0b, char value handle: 0x0028, uuid: b0e6a4bf-cccc-ffff-330c-0000000000f6
handle: 0x002c, char properties: 0x0b, char value handle: 0x002d, uuid: b0e6a4bf-cccc-ffff-330c-0000000000f7
handle: 0x0031, char properties: 0x0a, char value handle: 0x0032, uuid: b0e6a4bf-cccc-ffff-330c-0000000000f8

We’re looking for the characteristic with uuid: b0e6a4bf-cccc-ffff-330c-0000000000f1. We’re looking to use the char value handle – in my case 0x001a. Try the following and see if it delivers a treat.
char-write-req 0x001a 00

We now want to turn that into a single line command that we can use in a script:

gatttool -b 00:05:C6:1F:56:5A --char-write-req -a 0x001a -n 00

That should also dispense a treat.

Next, in my house we have a few raspberry pis, and the pet tutor can be in different places in the house. My aim is to have every pi in the house broadcast the command, so wherever the pet tutor is it’ll still get the command. Note that my pi devices are spread around enough that I don’t think the pet tutor will receive duplicate commands, but that may be something to watch out for. Also, some places in the house the pi doesn’t reach – doing this properly there’d be a pi right next to the pet tutor so that it always had a good connection.

I’m choosing to use perl, so I needed to install an MQTT module for perl:

sudo cpan
install Net::MQTT::Simple

Then create the perl script as /usr/bin/feeddog.pl:

#! /usr/bin/perl
use Net::MQTT::Simple;
my $mqtt = Net::MQTT::Simple->new('raspberrypi2:1883');
$mqtt->subscribe("feed/feeddog", \&feed);

sub feed {
my ($topic, $message) = @_;
print "$message";
if( $message = "feed") {
print "feeding\n";
`gatttool -b 00:05:C6:1F:56:5A --char-write-req -a 0x001a -n 00`;

My MQTT broker is running on “raspberrypi2:1883” – you should put in here the device you installed MQTT on.

I have this script added to /etc/rc.local as follows so that it auto starts on reboot:

/usr/bin/feeddog.pl &

You can test this by rebooting the pi to get the script running, and then use the MQTT client to send messages:

mosquitto_pub -h raspberrypi2 -p 1883 -t feed/feeddog -m feed

Next, I chose to join this to my openhab installation, by publishing an MQTT message when a switch is flicked. If you don’t have openhab you could make a web page or use another tool – I used my openhab because it already has a service that proxies it outside our home network, and because we have a 4G internet connection at the moment I cannot get a static IP address.

Openhab configuration looks like the following:

.items file:
Switch swFeedDog “Feed Dog [%s]” { mqtt=”>[pi2:feed/feeddog:command:ON:feed]” }

.rules file:
rule “Feed dog when switch set”
Item swFeedDog changed to ON
logDebug( ‘dog’, ‘Request to feed the dog’ )

.sitemap file:
sitemap default label=”Home Automation” {
Frame label=”Feed”{
Switch item=swFeedDog

Whilst I doubt anyone has the exact same setup we have, hopefully this is enough to move you towards your own configuration working.

Fisher and Paykel RC519 Chest Freezer very loud

We recently upgraded our chest freezer from a small Mitre 10 model to a nice F&P RC519, a large chest freezer with an internal light and electronic controls.

The freezer itself is really nice. The problem is that it’s loud when running. A brief web search tells me this model recently changed and got a lot noisier. It looks like they moved to a smaller compressor and cooling radiator, but added a fan to make the smaller setup move more air and therefore still deliver as much cooling. And the fan is loud.

The fan is a bi-sonic 4C-230MB tube axial fan.

Bi Sonic Fan

My reading is that the 4C is the model, 230 is the voltage (it’s available in 115 or 230), M is the speed (available in low, medium or high), and B is ball bearing as opposed to sleeve bearing. It’s a 120mm AC fan. At 50Hz it runs at 2,350 RPM and moves 71CFM at 35DBA of noise. I’m not great at decibels but I have to say ours sounds louder than that.

Looking at the mounting, it’s configured to suck air in across the fins, and then exhaust that warmer air onto the motor. That probably makes sense rather than pushing warm air from the motor onto the cooling fins, but it does mean it’s more reliant on the shroud to make sure that the air actually comes across the fins. And the shroud isn’t great – basically there’s a gap that lets air circulate from the exhaust side back to the inlet side, and there’s a vent to the side of the fan that lets fresh air in without coming through the entirety of the cooling coil.

So, my plan is that if I adjust that ducting a bit I’d get better air flow over the cooling fins, and that might let me get away with a bit less CFM. And then I can replace the fan with a quieter one. Perhaps this Orion fan, which runs at 1600 RPM, delivers 51 CFM (so 72% of the flow of the original fan), but at 25DBA instead of 35DBA. I think every 10 decibels is 10x as loud, so that fan will be 10 times quieter in theory. Which is a lot.

Arguably we could replace with the bi-sonic 4C-230LB instead, which is just the low speed version of the fan currently in it. That pushes slightly more air at 57CFM (80% of the original fan) but at 30 DBA. That’s a lot noisier than the Orion fan, although it’s hard to tell how accurate any of these measurements are, and it’s also possible that the fan I have is just a noisy one. It’s also true that the fan in free air will be quite different sounding than when it’s sucking/blowing across a grill, as it is when installed.

Anyway, ordering the fan and I’ll see if it works. The main issue currently is I can’t see how to open the wiring, so I can’t see how to wire the new fan in. I can of course just cut the wires and join it, but given it’s 230 volts it seems like it’d be better to open up the wiring box and direct connect it into the loom. It looks like I might have to remove the entire compressor to achieve that, which is weird. Problem for another day though, getting the fan is the first step.

Arriving visitors alerted to your phone (or watch)

We have a bush track that arrives on the bottom of our property, on the public land. When people suddenly appear there it makes our dogs very excited, and my partner would prefer we had notice that they are arriving ahead of time, so we can control the dogs.

This same solution could also be used for a gateway / driveway to tell you people are arriving on your farm, or in fact any other Dakota Alert monitoring solution.

The aim is to connect our Dakota Alert monitor to a raspberry pi, and then in turn connect that to a push alerting application that puts alerts onto your mobile phone when someone is coming. From there, if you have a smart watch your phone will alert on your smart watch.

The Dakota Alert equipment can be found here: https://dakotaalert.com/. You need a module that has the relay outputs – in our case the 4000 plus kit. This kit can trigger a relay when someone arrives, as well as playing a tone. That trigger is then read by the raspberry pi. This unit has a 1.6km range between the beams and the receiver unit (1 mile I guess), which is plenty for what we needed.

Start off by configuring the Dakota Alert system, and set the dip switches so that relay 1 will trigger. Get someone to walk through the beam and check that you’re getting a signal on the relay output (you can do this with a continuity tester, or using your multimeter to check the resistance – it should go to zero on the normally open connections when someone goes through the beam).

Next, you need a Raspberry Pi. Install it with Raspberry Pi OS (raspbian), and get it connected to your network and operating. There are plenty of guides on the internet to do that. We’re going to connect the Dakota Alert system to the GPIO pins on the Raspberry Pi. You need to know which Pi you have – I have an old Raspberry Pi model A, but you may have a newer one, and the pins can be slightly different. Refer to the pinout here.

What we’ll do is to take a line from the 3V3 (3.3V) power output on the pi, through the switch on the Dakota Alert, and back to the GPIO pin you’ve chosen on the pi (in my case, I’m going to GPIO11, or pin 23). That looks something like this:

Pi connected to Dakota Alert receiver

What you can see here is the red wire coming from pin 1 on the Raspberry Pi (marked as 3V3 in the pinout). It then goes to the NO (normally open) connection for relay 1 on the Dakota Alert Receiver (you press the orange tab in with a screwdriver to put the wire in). Then we come back out of the common on the NO relay 1 switch, and back to pin 23 on the Pi (GPIO pin 11).

The effect of this is that we’re taking 3.3V from the Pi, running it through the relay switch on the Dakota Alert receiver, and back to Pin 11. When the Dakota Alert relay switch is open (remembering it’s “normally open”) then we have no voltage. When the Dakota Alert relay switch closes (when someone comes) then it will put 3.3V on GPIO pin 11.

You can test this directly on the Pi with the following commands:

pi@raspberrypi4:~ $ cd /sys/class/gpio
pi@raspberrypi4:/sys/class/gpio $ ls
export gpiochip0  unexport
pi@raspberrypi4:/sys/class/gpio $ echo 11 > export
pi@raspberrypi4:/sys/class/gpio $ ls
export  gpio11  gpiochip0  unexport

This makes the pin gpio11 available for direct manipulation.

pi@raspberrypi4:/sys/class/gpio $ cd gpio11
pi@raspberrypi4:/sys/class/gpio/gpio11 $ ls
active_low  device  direction  edge  power  subsystem  uevent  value
pi@raspberrypi4:/sys/class/gpio/gpio11 $ echo in > direction
pi@raspberrypi4:/sys/class/gpio/gpio11 $ cat value

The value is currently 0, meaning no voltage. If you trigger your Dakota Alert by having someone else walk through the beam, and cat value whilst they do it, you should see the value change to 1. If it doesn’t then you need to do some trouble shooting.

You now have your Dakota Alert system connected to your pi, and telling your pi when someone crosses the beam.

Next, you need to install a push alert app on your phone, so that you can receive alerts. There are a number of different apps available, but I chose pushover, as it seemed the most simple and did exactly what I wanted and nothing more. Follow the instructions on their site to create a userid, download the app, and install it on your phone. Get to a point where you can send alerts to yourself on your phone through their website or by using the e-mail.

Next, create an application on the pushover site, and get the API token from that new application.

Next, we’re going to write a python script that listens for the Dakota Alert relay on the GPIO pins, and then calls pushover to tell it that an alert has been recieved.

I initially wrote this code using information from this alarm howto, and modified it using information from this robotics tutorial, which had more elegant handling of ctrl-c through using interrupts instead of waiting on an alert.

The final code looks like this:

import http.client, urllib
import signal, sys, syslog, time
import RPi.GPIO as GPIO

# constant for the GPIO pin wwe've chosen
OUR_PIN = 11 

# signal handler that traps ctrl-c, cleans up the GPIO, and exits
def signal_handler(sig, frame):

# callback when the pin is triggered, which calls pushover
def pin_triggered_callback(channel):
  syslog.syslog("Detected a rising edge - waiting 100ms to see if we stay on")

  if GPIO.input(OUR_PIN) == 1:
    syslog.syslog("Still high after 100ms, someone is approaching")

    conn = http.client.HTTPSConnection("api.pushover.net:443")

    conn.request("POST", "/1/messages.json",
        "token": "<<your pushover API/application token>>",
        "user": "<<comma separated pushover users to notify>>",
        "message": "Someone approaches around the track",
        "sound": "alien",
      }), { "Content-type": "application/x-www-form-urlencoded" })

    response = conn.getresponse()

    # if response isn't 200 then we failed - no actual error handling here, we just print it on the console
    if response.status != 200 :
      syslog.syslog("Failed to send notification for trail monitoring: " + response.status + response.reason + response.read(1000) )

    syslog.syslog("Wasn't sustained for 100ms, transient spike of some sort")

# main routine - sets everything up, and installs the interrupt handlers
if __name__ == '__main__':
  #setup GPIO using Broadcom SOC channel numbering

  # set to pull-up (normally closed position)
  GPIO.setup(OUR_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

  GPIO.add_event_detect(OUR_PIN, GPIO.RISING, 
    callback=pin_triggered_callback, bouncetime=2000)
  signal.signal(signal.SIGINT, signal_handler)

You then need to add this script to the autostart on your pi, so that when it reboots it’s automatically executed. There are a bunch of ways to do that here, I’m lazy so I’m just doing rc.local. One impact of this is that if the script crashes it won’t restart, and more complete methods could monitor it’s execution and auto-restart it.

#!/bin/sh -e
# rc.local
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
# In order to enable or disable this script just change the execution
# bits.
# By default this script does nothing.

# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"

/home/pi/TrailMonitoring/trailMonitoring.py &

exit 0

We add in our script name with an & on the end so that it runs in the background (rather than blocking the startup script). We can see what it’s doing by looking in /var/log/syslog.

Openhab Heating Configuration

This tutorial is a section of my wider series on creating a home automation solution for heating and watering in my house.  Refer to the index here.

In this post I cover a detailed configuration of Openhab for heating purposes.  We create the rule sets to allow us to have an arbitrary number of zones, with each zone having 4 setpoints at configurable times during the day.  The zones push those setpoints down to the devices in the zone.  We allow people to change the temperature in a zone using the wall thermostat, but override again at the next timed change.

We also control the running of the boiler based on how many valves are open – this is to avoid the boiler short cycling when there are very few valves or valves only partly open.  We’ll cover this in more detail when we get to the relevant rules.

We create a sitemap that gives us access to most of this information.

Continue reading

Configuration of MAXCUL and CUL Dongle

This tutorial is a section of my wider series on creating a home automation solution for heating and watering in my house.  Refer to the index here.

In this post I cover the installation of the firmware for the CUL dongle (culfw), the programming of the dongle to work with your raspberry pi, and configuration of the maxcul binding for Openhab.

At the end of this tutorial you should have one wall thermostat and one radiator valve paired to your raspberry pi via the CUL dongle.

Continue reading

Installing Openhab on Pi

This tutorial is a section of my wider series on creating a home automation solution for heating and watering in my house.  Refer to the index here.

In this post we install a new Raspberry Pi from scratch, and get OpenHab running on it.  We choose to use Wifi, and we install it headless (no GUI and no monitor).  We install by connecting the card reader to a linux machine, which gives us access to the file system as we build it.

Continue reading

Using Pi and OpenHab to control radiators and watering

Our house has hydronic radiator heating, we also have a watering system and other equipment.  I wanted to use a Raspberry Pi to control the heating, as our radiators previously had manual valves.  I also wanted to control the garden watering system whilst I was at it.

This tutorial series will cover the process of installing and configuring the software to achieve this, with topics being: