Dynamically switch DNS server when VPN tunnel comes up (Wireguard)

I am configuring OpenWRT as a VPN client to be run in an unfriendly environment. Think of "behind some great firewall".

I'm running my own VPN server based on Debian. It offers OpenVPN and Wireguard as well as OSPF and a DNS server.

The OpenWRT client uses its WAN port to connect to a local LAN and learn its IP, default gateway and DNS server from there.

It then connects to the OpenVPN server (using the server name) as well as the Wireguard server (using the servers IP address). This makes me independent from DNS resolution to connect to the VPN server.

On top of the OpenVPN and Wireguard tunnels runs bird 2.0 using OSPF. It connects to bird on the VPN server, chooses from all running VPN tunnels the one with the lowest cost and establishes routes to 0.0.0.0/1 and 128.0.0.0/1 through this tunnel. As soon as one of tunnels comes up, this in effect switches the default route from the local network to the VPN tunnel.

On the LAN interface I have a DHCP server running. Also the wireless network is running and connected to LAN. In effect: if someone connects to one of the LAN ports or WiFi, he gets an IP, default gateway (OpenWRT) and DNS server (dnsmasq on OpenWRT) from me. When he uses this connection, traffic flows through one of the VPN tunnels to my VPN server.

What you can do now (after some subtleties regarding forwarding/masquerading, double NAT): connect the OpenWRT router to your unfriendly ISP router, then connect your devices to the OpenWRT router through LAN or WiFi and the internet will see your devices with the source IP of my VPN server. Welcome to the free world.

There remains one problem: dnsmasq still uses the DNS server we learned from the local network, effectively leaking DNS requests to the unfriendly environment.

I know how to run a script from the OpenVPN client when the tunnel comes up or goes down. With this scrip I can manipulate dnsmasq.resolvfile to use the DNS server behind the OpenVPN tunnel.

What I am missing is a way to run a script when the Wireguard tunnel comes up or goes down. From what I have seen it does not trigger anything in hotplug when the tunnel comes up. Beware: I am not interested in doing something when the interface comes up: the wg0 interface exists long before the tunnel comes up and it does not change to UP or DOWN following the tunnel state.

Has someone got an idea how to trigger something when an Wireguard tunnel comes up / goes down?

I asked a similar thing a while back. That thread is here:

You could implement this with a hotplug script.

I ended up not spending time to implement any automatic solutions and instead just used public dns servers (like 8.8.8.8).

I would be happy to do so. But there seems to be nothing in hotplug that gets triggered when a VPN tunnel goes up or down.

I’d have to check for wireguard and hotplug, but in the case of OpenVPN, you can specify up and down scripts within the OpenVPN conf file.

That's what I wrote.

Yes, but I chimed in to give you a link to my thread that was similar, and that I found it was going to be more effort than was with it to get a solution working for my context.



Following up on psherman thread and following some logic along the way I found that weighted DNS is working for my scenario using dnsmasq-full and the LuCi WireGuard interface DNS settings tab. My weighted DNS resides on the WAN, making it the lesser choice until I stop / down the wg0 tunnel. My situation is wg0 is default route for all clients connected via double NAT to the internet. Sometimes I run on Windows the WireGuard software to bind my PC to the tunnel and effectively have a kill switch.

Pic is hidden ~ kinda a bright transition to eyes

2022-09-28_124627

opkg install dnsmasq-full --download-only && opkg remove dnsmasq && opkg install dnsmasq-full --cache . && rm *.ipk

System Software Specs
   "hostname": "Dachshund",
   "system": "Qualcomm Atheros QCA9533 ver 2 rev 0",
   "model": "MikroTik RouterBOARD 951Ui-2nD (hAP)",
   "board_name": "mikrotik,routerboard-951ui-2nd",
   "rootfs_type": "squashfs",
   "release": {
   	"distribution": "OpenWrt",
   	"version": "22.03.0",
   	"revision": "r19685-512e76967f",
   	"target": "ath79/mikrotik",
   	"description": "OpenWrt 22.03.0 r19685-512e76967f"

There seems to be nothing that gets triggered by Wireguard tunnels going up and down.

Hotplug documentation states that route changes should trigger hotplug with ACTION=ifupdate and IFUPDATE_ROUTES=1 but I could not verify that. Maybe a bug? Or "not implemented yet"?

Anyway: when you don't have a trigger you need to poll. In my case the existence or absence of a route for 0.0.0.0/1 tells me which DNS server to use.

Here is my /root/scripts/vpn_dns:

#!/bin/sh

P=vpn_dns
ME=/root/scripts/$P
INIT_ME=/etc/init.d/$P
INIT_CRON=/etc/init.d/cron
CRONTAB=/etc/crontabs/root

LOG="logger -t $P"

STD_RESOLVFILE=/tmp/resolv.conf.d/resolv.conf.auto
VPN_RESOLVFILE=/tmp/resolv.conf.d/resolv.conf.vpn
CUR_RESOLVFILE=`uci show dhcp.@dnsmasq[0].resolvfile | cut -d= -f2 | sed "s/'//g"`
NEW_RESOLVFILE=""

CUR_VPN_DNS=`cut -d ' ' -f2 $VPN_RESOLVFILE 2>/dev/null`
NEW_VPN_DNS=`ip route show 0.0.0.0/1 | cut -d' ' -f3`

[ "$1" != "" ] && $LOG "$1"

if [ "$1" = "install" ] ; then
  if [ "$0" != "$ME" ] ; then
    echo "$0: copy me to $ME and run me from there." >&2
    exit 1
  fi
  cat <<-EOF >$INIT_ME
#!/bin/sh /etc/rc.common
START=13
boot() { $ME clean ; }
EOF
  chmod 700 $INIT_ME
  $INIT_ME enable
  
  touch $CRONTAB
  sed -i "/$P/d" $CRONTAB
  echo "* * * * * $ME" >>$CRONTAB
  $INIT_CRON running || $INIT_CRON enable
  $INIT_CRON restart
elif [ "$1" = "uninstall" ] ; then
  touch $CRONTAB
  sed -i "/$P/d" $CRONTAB
  $INIT_CRON restart
  
  $ME clean
  
  $INIT_ME disable
  rm $INIT_ME
elif [ "$1" = "clean" ] ; then
  NEW_RESOLVFILE=$STD_RESOLVFILE
elif [ "$NEW_VPN_DNS" != "" ] ; then
  if [ "$CUR_RESOLVFILE" != "$VPN_RESOLVFILE" -o "$CUR_VPN_DNS" != "$NEW_VPN_DNS" ] ; then
    $LOG nameserver $NEW_VPN_DNS
    echo "nameserver $NEW_VPN_DNS" >$VPN_RESOLVFILE
    NEW_RESOLVFILE=$VPN_RESOLVFILE
  fi
elif [ "$CUR_RESOLVFILE" != "$STD_RESOLVFILE" ] ; then
  NEW_RESOLVFILE=$STD_RESOLVFILE
fi

if [ "$NEW_RESOLVFILE" != "" ] ; then
  $LOG resolvfile $NEW_RESOLVFILE
  uci set dhcp.@dnsmasq[0].resolvfile=$NEW_RESOLVFILE
  uci commit
  [ "$1" != "clean" ] &&  /etc/init.d/dnsmasq restart
fi

Run it once manually with /root/scripts/vpn_dns install. It will periodically check for the route 0.0.0.0/1 and chose the gateway as DNS server. Works well enough for me.