Need help writing a shell script [openvpn dns resolver switchout]

I am using OpenVPN to connect to a VPN provider.

When OpenWRT boots, it gets the ISP's DNS servers through DHCP from my modem. Dnsmasq then uses those to setup /tmp/resolv.conf.auto.Next, openvpn uses the VPN provider's DDNS name to discover the best performing server. It connects and pushes the server's DNS through the tunnel. I set the following script in my openvpn config (which I found on the net):

#!/bin/sh
mv /tmp/resolv.conf.auto /tmp/resolv.conf.auto.hold
echo $foreign_option_1 | sed -e 's/dhcp-option DOMAIN/domain/g' -e 's/dhcp-option DNS/nameserver/g' > /tmp/resolv.conf.auto
echo $foreign_option_2 | sed -e 's/dhcp-option DOMAIN/domain/g' -e 's/dhcp-option DNS/nameserver/g' >> /tmp/resolv.conf.auto
echo $foreign_option_3 | sed -e 's/dhcp-option DOMAIN/domain/g' -e 's/dhcp-option DNS/nameserver/g' >> /tmp/resolv.conf.auto

This works like a charm and my /tmp/resolv.conf.auto changes.

However, dnsmasq will reset the config back to my ISP's servers periodically. At least when it does a DHCP refresh on the WAN-connection, but also on other moments I haven't quite pinpointed yet.

I am looking for some way to write a shell script that catches that and changes it back to the VPN's server, since my network loses all name resolution otherwise.

So far, my idea is to write a 'dumb'-script that runs every minute as a cron-job, checks if the tunnel is up, then checks if the resolv.conf.auto contains a 10.x.x.x nameserver. If not, it will overwrite the file with the VPN's resolver (I would add a cp to the above script at the end to backup the VPN's resolv.conf).

It would of course be nicer if the script can somehow be triggered by dnsmasq's periodical updates.

Problem is, I can do magic in PowerShell, but there's no PowerShell opkg (can that even be done?), so I need some help writing a shell-script that works for this. I would really appreciate it.

why re-invent the wheel? see script in the post below...( as a template to build on ) note: test on bootup condition...

#!/bin/sh
SEE LOWER POST FOR SCRIPT

Note: recent releases may use alternate file locations for some resolv.conf files!

1 Like

Did not find that one, thanks!

Let me see if I can interpret it...

I guess script_type is passed by openvpn as being up or down

If the up-parameter was passed by openvpn:

The while-loop evaluates all options until it is null, then it breaks out of the loop. Although the sed option is only looking for DOMAIN and not DNS here? Curious how that poster got nameserver entries in his file then.

It looks like it handles multiple valid results by echoing itself followed by a newline.

If ns has a value, use it to create the /etc/resolv.conf.vpn. If not, use static addresses to create the file.

Call uci to edit /etc/config/dhcp and change the value of resolvfile to the new file. Then commit and restart dnsmasq. Can I also use a service dnsmasq reload here instead of restart?

If the down-parameter was passed by openvpn:

Test if the resolv.conf.vpn file exists. If so, remove it, set dnsmasq back to using resolv.conf.auto, commit and restart.

Did I get that correctly?

It makes me wonder which process updates the resolv.conf.auto file then. odhcpd?

the key is;

  • its called on up | down
  • it changes the dnsmasq resolv file... ( so periodic reloads wont bother you when its switched to resolv.conf.vpn )
  • it reloads dnsmasq

the rest is not that big a deal, you already posted your own sed code... so try that instead... ( into resolv.conf.vpn same difference )

good point

udhcpc/netifd on wan etc. ( use isp dns servers )

don't bother... that's a shorthand "convenience" that requires supporting code to be sourced from another file. ( edit: yes... /etc/init.d/dnsmasq reload - is probably better )

but yes... you pretty much get the hang of it :nerd_face:

putting in /etc/rc.local;

        rm -fr "/tmp/resolv.conf.vpn" 2> /dev/null
        uci set dhcp.@dnsmasq[0].resolvfile='/tmp/resolv.conf.auto'
        uci commit dhcp
       # /etc/init.d/dnsmasq restart &

should cover you in case of unexpected power off...

Turns out the original poster cut off the right side of the script when posting here. The original is:

#!/bin/sh

case $script_type in
up)
	i=1
	ns=""
	while true; do
		# As we know, for non-Windows openvpn clients can accept push DHCP
		# options by using a client-side up script which parses the 
		# foreign_option_n environmental variable list
		eval opt=\$foreign_option_${i}
		[ -z "${opt}" ] && break

		ns="$ns\n$(echo ${opt} | sed -e 's/dhcp-option DOMAIN/domain/g' -e 's/dhcp-option DNS/nameserver/g')"

		i=$((i + 1))
	done

	if [ -n "$ns" ]; then
		echo -e "$ns" > /tmp/resolv.conf.vpn
	else
		echo -e "nameserver 209.244.0.3\nnameserver 64.6.64.6" > /tmp/resolv.conf.vpn
	fi

	uci set dhcp.@dnsmasq[0].resolvfile='/tmp/resolv.conf.vpn'
	uci commit dhcp
	# Let it runs on background, in order to avoid any delay to add route table,
	# which will effort to mwan3
	/etc/init.d/dnsmasq restart &
	;;
down)
	# Restore dns
	[ -f "/tmp/resolv.conf.vpn" ] || return 0
	rm -fr "/tmp/resolv.conf.vpn"
	uci set dhcp.@dnsmasq[0].resolvfile='/tmp/resolv.conf.auto'
	uci commit dhcp
	/etc/init.d/dnsmasq restart &
	;;
esac

exit 0

Which also explains why openvpn started pniacking when parsing the sed line. It was missing )" at the end.

I edited it for my config and it works like a charm, thank you!

Last question: is there any way to automate the rc.local addition (and is that hash character intentional)?

Now if only I can fix the MTU issue that causes every packet to be fragmented in two on my VPN tunnel. Apparently, OpenWRT's kernel does not support MTU autodiscovery. Nor does its OpenVPN work with fragment or mssfix.

1 Like

Well, little snag. My VPN lost its connection during a periodical key-refresh and then endlessly tried to resolve its FQDN using the VPN-DNS host. Oh well, maybe I should throw in the towel and pick a single VPN server by IP instead of using a DNS name that auto-selects the best one.

1 Like

Excellent news! Trial by fire :man_farmer:

I believe you can add a forwarder for this domain in dnsmasq... for an easy non "watch based switcher script" workaround...

(/etc/config/dhcp)

list server /vpnserver.ddns.name/WANDNSIP

( you may need a static route for that too to force out WANGW in the event the vpn is actually up and your router attempt to query WANDNSIP via the tunnel and the DNS server does not like traffic from that network, ... depends on how your routes are populated )

( but yes... IP is better and also checkout client keepalive config options )

I used to just set the static IP of a VPN server and VPN DNS in OpenWRT, but that of course meant without VPN I had no name resolution. Plus, the VPN provider advised me to use auto-select where possible. The idea of letting them select the server with best performance appealed to me. So I figured if I am going to re-install my OpenWRT anyway I might as well do it the right way.

I suppose I could still write a cron-job that checks the system-log for the error and does the switchover, but your forwarder-solution may be more elegant. I didn't even consider that possibility. Thanks!

...and just as I typed that you had to make it more complicated. :wink:

1 Like

it's likely this is more of a medium/longer term advice, so the ip you get regularly a couple of times should be fine... just recheck every month or couple or if speed is effected...

is it a common provider? can you share the dns name? others might have more info and I can check it out too...

AirVPN, they told me it was the most complicated one to configure, so obviously I had to get it. :slightly_smiling_face:

And no, if I connect 3 times in a row, the dynamic DNS name will always select the server that performs best at that very moment. In the past few days I have been doing a ton of troubleshooting because of fragmented UDP packages and must have visited every server in my country. The staff may be annoyed by support requests from "lesser" humans, but they do seem to have their infrastructure running quite well.

And although for convenience every VPN endpoint listens for DNS requests on the same IP, they will each pass their own internal IP as a resolver and gateway.

Normally I don't notice it since the tunnel can stay up for weeks if not months, but it is nice to have when they have a server down. My client will automatically reconnect to another one.

This is a sample watcher script you can background on vpn->up... ( watcher.sh & )

#!/bin/sh

VPNDNSIP="1.2.3.4"

n=5

while sleep 50; do
        t=\$(ping -c $n $VPNDNSIP | grep -o -E '\d+ packets r' | grep -o -E '\d+')
        if [ "$t" -eq 0 ]; then
        	#uci switch back to resolv.conf.auto
			#end myself
		fi
done

As you can see... it attempts to ping the VPNDNS server... if it can't should switch your uci resolv back...

TLS: soft reset sec=0 bytes=25429520466/-1 pkts=24712268/0
VERIFY OK
VERIFY KU OK
Validating certificate extended key usage
++ Certificate has EKU (str) TLS Web Server Authentication, expects TLS Web Server Authentication
VERIFY EKU OK
VERIFY OK
Outgoing Data Channel: Cipher 'AES-256-GCM' initialized with 256 bit key
Incoming Data Channel: Cipher 'AES-256-GCM' initialized with 256 bit key
Control Channel: TLSv1.2, cipher TLSv1.2 DHE-RSA-AES256-GCM-SHA384, 4096 bit RSA
[<VPNSERVNAME>] Inactivity timeout (--ping-restart), restarting
SIGUSR1[soft,ping-restart] received, process restarting
Restart pause, 5 second(s)
NOTE: the current --script-security setting may allow this configuration to call user-defined scripts
TCP/UDP: Preserving recently used remote address: [AF_INET]133.17.172.145:443
Socket Buffers: R=[87380->87380] S=[16384->16384]
Attempting to establish TCP connection with [AF_INET]133.17.172.145:443 [nonblock]
Maximum number of concurrent DNS queries reached (max: 150)
Maximum number of concurrent DNS queries reached (max: 150)
Maximum number of concurrent DNS queries reached (max: 150)
Maximum number of concurrent DNS queries reached (max: 150)
TCP: connect to [AF_INET]133.17.172.145:443 failed: Operation timed out
SIGUSR1[connection failed(soft),init_instance] received, process restarting
Restart pause, 5 second(s)

I changed the IP by the way. Looks it may have been a hiccup. According to the logs the server did not have any issues.

The DNS queries-issue happens because apparently something in my network spams dnsmasq as soon as it loses the ability to resolve (one of the reasons why I wanted this construction to work well). But that does not matter for this one, since it tries to reconnect using IP address.

I'm using TCP over UDP (which may have been less sensitive) since all the UDP packets get fragmented, resulting in a drastic reduction in throughput. TCP has enough downsides, but for now it delivers more than UDP does.


Thanks for the script. "end myself" is a bit morbid, everything okay? :wink:

while sleep 50 means it will loop indefinitely in 50 second intervals? Unless I add break after the uci part?

I really love the help!

1 Like

yup... break or maybe exit

I found these scripts here... so credit to their authors... :nose:

i.e. things like this

OpenWrt vpn setup is a bit of a pain even without ddns... being interested in learning makes it worthwhile.

Pointing me to an example dealing exactly with the maximum concurrent issue, did you do that on purpose? :slightly_smiling_face:

I don't mind that, it's sort of an indicator that my tunnel went down.

An addition for those wanting to do something similar. My VPN provider gives out an IPv6 DNS as well. Which led to a funny nameserver6 in the resolv.conf. So I changed the script line to:
sed -e 's/dhcp-option DOMAIN/domain/g' -e 's/dhcp-option DNS6/nameserver/g -e 's/dhcp-option DNS/nameserver/g'
It's important to put the DNS6 first.

1 Like