Network

My Broadband Setup

TL:DR Setting up secure and resilient Internet access in a country with sub-par infrastructure

I moved to the UK, a country that was a leader in Europe for PC adoption and early telecoms deregulation, but has since become one of the worst for the quality of its broadband through misguided laisser-faire policies. The only fixed broadband option available in my apartment is BT OpenReach’s pathetic VDSL service1 (resold by Vodafone), which advertises 72 Mbps but I am lucky to get 40 Mbps down and 10 Mbps up.

There are several problems with this state of affairs:

  • The network is very unreliable. I’ve had outages lasting 8 hours. It is so bad I wrote my own tool to track ping times and downtime.
  • The consumer ISPs in the UK are anything but network-neutral, due to government regulations mandating Orwellian nanny-filters on the connection2. At one point, I was unable to reach the Stack Overflow for over 2 days. It turns out for some unfathomable reason Vodafone decided to use Stack Overflow as the test site when they developed the government-mandated nanny-filter, and somehow that was deployed to production as per this highly instructive email thread.
  • The IP address is dynamic. While Vodafone does not change it too often and it can be worked around using Dynamic DNS, on cellular carriers the use of Carrier-Grade NAT (CGNAT) is rife, and it makes those connections highly unsuitable for:
    • self-hosting mail servers, calendar or other services
    • working from home where I need to have long-lived SSH connections doing critical work.

Recently I found out my mobile operator, Three, offers 5G fixed broadband service. I was skeptical, their 4G service in my NIMBY-infested area3 is abysmal, I hardly ever have any signal at all on Hampstead High Street, but it turns out their 5G service is excellent, offering 500 Mbps down and 30 Mbps up, with decent ping times, probably because they managed to buy a 100Mhz contiguous allocation of 5G spectrum. Unfortunately, the service is not officially offered in my post code, so I decided to roll my own using an unlimited SIM card and an unlocked Huawei CPE Pro 2 5G router.

I have been experimenting with VPNs of late, leading to my edgewalker self-hosted VPN server, and building a VLAN on my network that thinks it is in the US using a VPN provider that shall remain nameless because it still has not been blocked by Netflix. This allows my daughter to watch her favorite US shows that are not available in the UK because of the despicable geofencing of the content cartels, who want to gouge you depending on where you live (except in this case they are not even offering the gouging, just no content).

The natural next step is to make the entire network be connected to the Internet via a WireGuard VPN. Because WireGuard, was designed for mobile connections like IPsec/IKEv2/MOBIKE, it easily adapts to shifting IP addresses (as long as one end stays put). This means it can deal with CGNAT and also fail over from 5G to DSL and back without breaking a sweat or even dropping a session.

Unfortunately there are side-effects to using a self-service VPN hosted by a cloud provider:

  • Netflix, Amazon and the BBC will refuse to serve video to you. I had to work around it by creating a special VLAN for VPN-averse devices (the LG Smart TV, AppleTV 4K and any other streamers in my household). This VLAN is bridged to the Huawei in a way that stops the offensive STP packets, so it is as if they were plugged in directly into the Huawei. This is not a solution for when we want to watch video from out iDevices, however.
  • The VPN encapsulation reduces the maximum data size (MTU) from the standard 1500 bytes of Ethernet to 1380 or so. Some sites have broken Path MTU Discovery (I found out the hard way DuckDuckGo is one of them), which means by blocking ICMP packets the server does not realize their large packets are not getting through, keeps retrying in vain until the browser times out in disgust. Setting the OpenBSD PF scrub (no-df) option took care of that.
  • Then there is the bizarre phenomenon by which Google thinks my IP is in the United Arab Emirates. I do not know how, IP2Location thinks it is in the Netherlands, and MaxMind that it is in the UK (as it is). I tried again with some other Vultr servers and kept being located to the UAE or Saudi Arabia. My best guess is that Google builds its own IP geolocation database using GPS data from Android phones, and that some brave souls in the UAE or Saudi Arabia used a VPN service running on Vultr servers, and that caused the Vultr IPs to be associated with the those countries. The only way I found to resolve that was to keep creating virtual servers and additional IPs until I found a pair that did not locate to the UAE or Saudi Arabia. Now Google thinks I am in the US rather than the UK, I can live with that.
  • Some services like Wikipedia will also block the device from edits, as it triggers a false positive for an open proxy. I sent an email to them on Saturday night and they had fixed that by next morning, whereas Google makes strenuous efforts to ensure you cannot reach a human within their organization, ever, and there is seemingly no way to prevent the defective IP geolocalisation from screwing things up (they disabled the /ncr workaround they used to have a few years ago). Tells you everything you know about the importance of customer service for a monopoly.

This is what I implemented, by replacing the too-limiting Ubiquiti Security Gateway in my UniFi switched and wireless network with an OpenBSD router that establishes a WireGuard VPN to a modified edgewalker running in the cloud with Vultr.

The configuration is quite complex because I have the following VLANs:

  • The default VLAN (which is actually not even a VLAN, as Ubiquiti gear is not really Enterprise-class and does not default to VLANs). Because of the VPN compatibility issues, I am now using source IP based policy routing on the default VLAN and only servers go through the VPN.
  • VLAN 2 for my office work-from-home Mac, I just do not trust the various antivirus (and other software that are required for compliance) anywhere near my personal networks
  • VLAN 4 for the VPN-averse devices as mentioned above
  • VLAN 7 is directly on the ISP router
  • VLAN 666 for Internet of Things devices (at least those that can be operated without connecting to the rest of the LAN unlike printers)
  • VLAN 1776 for my geofencing-busting freedom VPN that thinks it is in the USA
  • Not a VLAN, but the Ethernet connection between my OpenBSD box and the Huawei router runs on a dedicated interface because in a bizarre effort to be “helpful” it sends a stream of Spanning Tree Protocol (STP) packets that basically cause my Ubiquiti switched network to melt down. OpenBSD can block them, but seemingly UniFi does not give you that control (so much for security, then). VLAN 4 is bridged to this.

OpenBSD has a concept of routing domains that allows you to virtualize your network stack into multiple routing tables, the way you can with VRF on a Cisco. This has proved invaluable, as has managing the configuration files in git to ensure I can always back out failed changes, and using Emacs’s TRAMP mode to edit files remotely.

It is mostly running, I have yet to move the Vodafone VDSL PPPoE circuit over from the decommissioned USG to the OpenBSD router and set up an IGP or some other routing protocol to fail over the default route to the Internet underlying WireGuard if one of the two connections fails. I am sure I will discover oddities as I go.

5G is extremely sensitive to positioning. Moving the Huawei just 20cm along the window makes the difference between 300Mbps down/10Mbps up/20ms ping and 500/30/12ms.

Not everything is perfect, of course. Ping times have risen slightly, and are more variable, as can be expected of a wireless network with layers of VPN processing latency added.

All DNS has to go through my own DNS servers, which are routed through the VPN so my ISPs cannot sniff those requests, or tamper with them. I thus get the benefits of encrypted DNS without having to trust the likes of Cloudflare or worse, Google. I do not (yet) block DoH or DoT like this gentleman does but I am planning to. My endgame is to add filtering similar to what the PiHole does, and interface the DNS server with the firewall to only allow IoT devices to connect to IP addresses that are the result of legitimate DNS lookups. I am also planning on recording DNS lookups using the dnstap interface for audit and parental control purposes.

Each VLAN has its own DHCP server instance. Known MAC addresses get a static IP in their DHCP lease, others get an address in 10.0.6.0/24 and all of their network traffic is recorded for forensics purposes using pflog. The same also applies to IoT devices.

Ubiquiti devices are prohibited by the OpenBSD firewall from accessing the Internet by the firewall, because of their history of security breaches, and to prevent any interference from the cloud.

Update (2022-06-10):

Real fiber finally reached my apartment complex in October 2021. It’s operated by a small Manchester ISP called 4th Utility. They top at 400Mbps but it is symmetrical. Annoyingly, their routers are locked down and you need to ask customer support to make any changes. I was the first one connected, but sadly for them the median age here is probably 70 and they are having a hard time signing up new subscribers.


  1. It is a travesty that the Advertising Standards Authority has allowed ISPs to deceptively advertise their lousy copper DSL networks as “full fibre” on the basis they have fiber somewhere, and that this was not laughed out of court. ↩︎

  2. The UK is not quite as bad an enemy of the Internet as Australia, but only just. After all, this is a country without a Constitution, without a Bill of Rights or separation of Church and State, with a monarchy that is far from merely ceremonial, and where the ruling party campaigned on a manifesto of “we need to cut back on human rights”. ↩︎

  3. NIMBYs do not like cellular towers and even Uber drivers remark on how bad reception is in Hampstead. ↩︎

Automating Epson SSL/TLS certificate renewal

Network-capable Epson printers like my new ET-16600 have a web-based user interface that supports HTTPS. You can even upload publicly recognized certificates from Let’s Encrypt et al, unfortunately the only options they offer is a Windows management app (blech) or a manual form.

When you have to upload this every month (that’s when I automatically renew my Let’s Encrypt certificates), this gets old really fast, and strange errors happen if you forget to do so and end up with an expired certificate.

I wrote a quick Python script to automate this (and yes, I am aware of the XKCDs on the subject of runaway automation):

#!/usr/bin/env python3
import requests, html5lib, io

URL = 'https://myepson.example.com/'
USERNAME = 'majid'
PASSWORD = 'your-admin-UI-password-here'
KEYFILE = '/home/majid/web/acme-tiny/epson.key'
CERTFILE = '/home/majid/web/acme-tiny/epson.crt'

########################################################################
# step 1, authenticate
jar = requests.cookies.RequestsCookieJar()
set_url = URL + 'PRESENTATION/ADVANCED/PASSWORD/SET'
r = requests.post(set_url, cookies=jar,
                  data={
                    'INPUTT_USERNAME': USERNAME,
                    'access': 'https',
                    'INPUTT_PASSWORD': PASSWORD,
                    'INPUTT_ACCSESSMETHOD': 0,
                    'INPUTT_DUMMY': ''
                  })
assert r.status_code == 200
jar = r.cookies

########################################################################
# step 2, get the cert update form iframe and its token
form_url = URL + 'PRESENTATION/ADVANCED/NWS_CERT_SSLTLS/CA_IMPORT'
r = requests.get(form_url, cookies=jar)
tree = html5lib.parse(r.text, namespaceHTMLElements=False)
data = dict([(f.attrib['name'], f.attrib['value']) for f in
             tree.findall('.//input')])
assert 'INPUTT_SETUPTOKEN' in data

# step 3, upload key and certs
data['format'] = 'pem_der'
del data['cert0']
del data['cert1']
del data['cert2']
del data['key']

upload_url = URL + 'PRESENTATIONEX/CERT/IMPORT_CHAIN'

########################################################################
# Epson doesn't seem to like bundled certificates,
# so split it into its componens
f = open(CERTFILE, 'r')
full = f.readlines()
f.close()
certno = 0
certs = dict()
for line in full:
  if not line.strip(): continue
  certs[certno] = certs.get(certno, '') + line
  if 'END CERTIFICATE' in line:
    certno = certno + 1
files = {
  'key': open(KEYFILE, 'rb'),
}
for certno in certs:
  assert certno < 3
  files[f'cert{certno}'] = io.BytesIO(certs[certno].encode('utf-8'))

########################################################################
# step 3, submit the new cert
r = requests.post(upload_url, cookies=jar,
                  files=files,
                  data=data)

########################################################################
# step 4, verify the printer accepted the cert and is shutting down
if not 'Shutting down' in r.text:
  print(r.text)
assert 'Shutting down' in r.text
print('Epson certificate successfully uploaded to printer.')

Update (2020-12-29):

If you are having problems with the Scan to Email feature, with the singularly unhelpful message “Check your network or WiFi connection”, it may be the Epson does not recognize the new Let’s Encrypt R3 CA certificate. You can address this by importing it in the Web UI, under the “Network Security” tab, then “CA Certificate” menu item on the left. The errors I was seeing in my postfix logs were:

Dec 29 13:30:20 zulfiqar mail.info postfix/smtpd[13361]: connect from epson.majid.org[10.0.4.33]
Dec 29 13:30:20 zulfiqar mail.info postfix/smtpd[13361]: SSL_accept error from epson.majid.org[10.0.4.33]: -1
Dec 29 13:30:20 zulfiqar mail.warn postfix/smtpd[13361]: warning: TLS library problem: error:14094418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca:ssl/record/rec_layer_s3.c:1543:SSL alert number 48:
Dec 29 13:30:20 zulfiqar mail.info postfix/smtpd[13361]: lost connection after STARTTLS from epson.majid.org[10.0.4.33]
Dec 29 13:30:20 zulfiqar mail.info postfix/smtpd[13361]: disconnect from epson.majid.org[10.0.4.33] ehlo=1 starttls=0/1 commands=1/2

Update (2021-08-01):

The script was broken due to changes in Let’s Encrypt’s trust path. Seemingly Epson’s software doesn’t like certificates incorporating 3 PEM files and shows the singularly unhelpful error “Invalid File”. I modified the script to split the certificate into its component parts. You may also need to upload the root certificates via the “CA Certificate” link above. I added these and also updated the built-in root certificates to version 02.03 and it seems to work:

  • lets-encrypt-r3-cross-signed.pem 40:01:75:04:83:14:a4:c8:21:8c:84:a9:0c:16:cd:df
  • isrgrootx1.pem 82:10:cf:b0:d2:40:e3:59:44:63:e0:bb:63:82:8b:00
  • lets-encrypt-r3.pem 91:2b:08:4a:cf:0c:18:a7:53:f6:d6:2e:25:a7:5f:5a

They are available from the Let’s Encrypt certificates page.

Apple iCalendar's buggy SNI

TL:DR If you use Apple’s calendar client software, do not run the server on an IP and port shared with any other SSL/TLS services.

I run my own CalDAV calendar server for my family and for myself. For a very long time I used DAViCal, but it’s always been a slight annoyance to set up on Apple devices because they don’t like DAViCal’s https://example.com/davical/caldav.php/majid URLs. What’s more, recent versions of iCalendar would pop up password prompts at random, and after re-entering the password a couple of times (once is not enough), would finally go on and work. The various devices would also all too often get out of sync, sometimes with the inscrutable error:

Server responded with “500” to operation CalDAVAccountRefreshQueueableOperation

requiring deleting the calendar account and recreating it by hand.

I tried replacing DAViCal with Radicale today, with the same flaky user experience, and I finally figured out why: Apple uses at least a couple of daemons to manage calendar and sync, including dataaccessd, accountsd and remindd (also CalendarAgent depending on your OS version). It seems some or all of them do not implement Server Name Indication (SNI) consistently. SNI is the mechanism by which a TLS client indicates what server it is trying to connect to during the TLS handshake, so multiple servers can share the same IP address and port, and is an absolutely vital part of the modern web. For example many servers use Amazon Web Services’ Elastic Load Balancer or CloudFront services, which are used by multiple clients, if Amazon had to dedicate a separate IP address for each, it would break their business model1.

Sometimes, those daemons will not use SNI, which means they will get your default server. In my case, it’s password-protected with a different password than the CalDAV one, which is what triggers the “enter password” dialog. At other times, they will call your CalDAV server with dubious URLs like /.well-known/caldav, /principals/, /dav/principals/, /caldav/v2 and if your server has a different HTTP password for that and sends back a HTTP 401 status code instead of a 404 Not Found, well, that will also trigger a reauthentication prompt.

Big Sur running on my M1 MacBook Air seems to be more consistent about using SNI, but will still poke around on those URLs, triggering the reauthentication prompts.

In other words, the only way to get and Apple-compatible calendar server running reliably is to dedicate an IP and port to it that is not shared with anything else. I only have one IP address at home where the server runs, and I run other vital services behind HTTPS, so I can’t dedicate 443 to a CalDAV server. Fortunately, the configuration will accept the syntax example.org:8443 to use a non-standard port (make sure you use the Advanced option, not Automatic), but this is incredibly sloppy of Apple.


  1. Amazon does in fact have a Legacy Clients Support option, but they charge a $600/month fee for that, and if you need more than two, they will demand written justification before approving your request. ↩︎

Edgewalker, a DIY VPN server

TL:DR Don’t trust VPN services, roll your own with this easy script.

Rationale

There are many reasons to use a Virtual Private Network. Perhaps you are on an unsecured WiFi network. Perhaps you don’t want your Internet Service Provider to snoop on your browsing history using Deep Packet Inspection and compile a marketing dossier on your. Perhaps like my daughter you want to access video content on Netflix that is not available in your country. Perhaps you want to bypass the nanny state content filters the British government mandates.

Most VPN services are untrustworthy. You depend on the VPN provider’s assurances to protect your privacy, which completely defeats the purpose of a VPN. The only way you can be sure is to run your own, but baroque network protocols engendering complex software makes it difficult to do so even for the technically savvy.

Streisand was one of the first efforts to automate the process, using cloud virtual servers as the hosts operating the VPN. Trail of Bits implemented Algo to simplify it and remove some questionable choices Streisand made (although, to be fair, the Streisand project seems to have jettisoned many of them and converged on WireGuard).

Edgewalker is similar, but awesomer:

  • It is based on OpenBSD, widely considered the most secure general-purpose OS, rather than Linux.
  • Like Algo, it implements IPsec/IKEv2/MOBIKE rather than OpenVPN (read the Algo announcement for the reasons why).
    • IPsec/IKEv2 works out of the box on iOS, iPadOS and macOS.
    • In theory on Windows as well, although I have no idea how to make it work or simplify setup, any help is welcome.
  • It also implements WireGuard (recommended for Linux and Android, along with travel VPN-capable routers like the GL.iNet Mango)
  • It uses QR codes to simplify installation as much as possible on the client devices.
  • It uses Let’s Encrypt so your IPsec certificates just work (WireGuard does not rely on PKI)
  • It uses its own Unbound DNS server with DNSSEC validation support, for better privacy
  • It has no dependencies on Ansible, Python or anything else exotic you need to add on your own machine, other than a SSH client.
  • It is just a shell script with little bits of Python thrown in like Acme-Tiny, and easily auditable.

While you can run the script again as your Let’s Encrypt certificates expire (although it generates new credentials each time), I recommend simply destroying the VM and creating a new one. Of course, if you are running on physical hardware, you will want to rerun the script. If using WireGuard only, you don’t need to rerun the script as WireGuard keys do not expire and there are no certificates.

Prerequisites

You need:

  • A Let’s Encrypt account and key (I’m working on setting this up automatically for you, in the meantime you can use Step 1 on this page to do that for you).
  • An OpenBSD machine reachable from the Internet (it can be a physical machine you own, or a cloud VM like Vultr).
  • The ability to add a DNS record for the machine’s IP address (IPv4 only for now).
  • The 80x25 OpenBSD console does not support UTF-8 and cannot display the QR code in a single screen. Use a different terminal, or enter the profile URL by hand.

If you have a firewall in front of the OpenBSD machine, it needs to allow the following inbound traffic (possibly using static port mappings if you use NAT):

  • SSH (TCP port 22) so you can actually log in to your machine.
  • HTTP (TCP port 80) and HTTPS (TCP port 443) to allow Let’s Encrypt certificate issual and allow you to get the Apple-format Profiles that will ease setup on your iDevice.
  • UDP ports 500 (IKE), 1701 (IPsec) and 4500 (IPsec NAT traversal).
  • Optionally IPsec protocols ESP (IP protocol number 50, hex 0x32)) and AH (decimal 51 hex 0x33) and ESP for maximum efficiency, although many firewalls won’t support this.
  • UDP port 51820 (WireGuard).

Instructions

  • Clone the Github repository into one of your own, or copy the file edgewalker.sh somewhere you can download it without it being tampered with in transit, in practice that means HTTPS.
  • Edit the first lines in the script edggewalker.sh (X509 and USERNAME). Not strictly necessary, but make it your own.
  • Log in as root on your OpenBSD machine, then:
    pkg_add wget
    wget -c https://raw.githubusercontent.com/YOUR_GITHUB_ACCOUNT_HERE/edgewalker/main/edgewalker.sh
    sh -e edgewalker.sh
    
  • The script will ask you for:
    • The DNS name of your OpenBSD machine.
    • To copy-paste your Let’s Encrypt account key in PEM format.
  • It will then obtain Let’s Encrypt certificates, generate a QR code that you can use to download the profile on your iDevice to set up the VPN.

Credits

  • The OpenBSD team, for making their wonderful security-focused OS.
  • Reyk Flöter for making OpenIKEd, a breath of fresh air in the unnecessarily convoluted world of VPN software.
  • Jason A. Donenfeld for inventing WireGuard.
  • Let’s Encrypt, for making certificates cheap and easy.
  • Daniel Roesler for the fantastic Acme-Tiny.

Demo

I created a fresh OpenBSD 6.8 VM vpn42.majid.org on Vultr, and here is what the experience looks like:

Here is how to install the VPN on an iPhone:

Here is how to create a suitable VM on Vultr:

Only the best will do for my readers

Qualys SSL Labs: A+, 100% rating

Securityheaders.io: A+ rating

Update (2020-11-22):

I just switched this blog to use Amazon’s Cloudfront CDN to speed up things (the old server was in Mt Prospect, Illinois near Chicago O’Hare airport, and particularly painful to access from London due to the latency). Unfortunately that also means the perfect A+ is gone, as AWS has more lenient compatibility settings for its SSL gateways.