Openvpn is a really nice, relatively simple VPN service.
There are lots of tutorials on how to get things rolling. This post deals with the specific issue of DNS services made available through the VPN.
Most of my users connect to our VPN using Windows clients, which is great because OpenVPN’s Windows client has the ability to override the existing DNS services that the client is using and replace them with DNS services pushed by the VPN server. This means that the users have access to all of our internal network using the same names as if they were connected locally. Just what you would expect.
Under Linux this is a bit more complicated because Linux (and most of the *nix world) uses the /etc/resolv.conf file to resolve name information and the Linux Openvpn client doesn’t change the information stored thereon it’s own. This makes things painful if Linux users don’t have access to the internal DNS.
The Openvpn solution is to use the openvpn scripting feature to pass the information to a script which will process it. After a few false starts with other people’s scripts I came up with the following configuration. OpenVPN will provide warnings in syslog that scripting is enabled because almost anything can be done with scripts, even very bad things.
I hope that you find these scripts helpful.
Linux Client Configuration: /etc/openvpn/client.conf
client dev tun proto udp script-security 2 up ./up.sh down ./down.sh remote <remote_server_ip_address> 1194 remote <remote_server_ip_address #2> 1194 resolv-retry infinite nobind persist-key persist-tun ca /etc/pki/tls/certs/my_cert_authority.pem cert /etc/pki/tls/certs/my_client_certificate.pem key /etc/pki/tls/private/my_client_key.key ns-cert-type server verb 1 mute 5
The up script: /etc/openvpn/up.sh:
#!/bin/bash log="openvpn.log" modified="0" echo "Initialization: $0 $1 $2 $3 $4 $5 $6" >> $log echo ";; created by openvpn" > ./resolv.conf.new echo ";; Initialization: $0 $1 $2 $3 $4 $5 $6" > ./resolv.conf.new for opt in ${!foreign_option_*}; do optval=${!opt} optiontype="`echo $optval | awk -F ' ' '{ print $1; }';`" if [ "$optiontype" == "dhcp-option" ]; then dhcptype=`echo $optval | awk -F ' ' '{ print $2;}';` dhcpval=`echo $optval | awk -F ' ' '{ print $3;}';` if [ "$dhcptype" == "DNS" ]; then modified="1" echo "Adding DNS Server $dhcpval" >> $log echo "nameserver $dhcpval" >>./resolv.conf.new fi if [ "$dhcptype" == "DOMAIN" ]; then modified="1" echo "Adding domain $dhcpval" >> $log echo "domain $dhcpval" >> ./resolv.conf.new echo "search $dhcpval" >> ./resolv.conf.new fi #Note that the DOMAIN must come first for the sed command do match properly. if [ "$dhcptype" == "DOMAIN-SEARCH" ]; then modified="1" echo "Adding domain search $dhcpval" >> $log sed -i "s/^search \(.*\)/search \\1 $dhcpval/" ./resolv.conf.new fi dhcptype= dhcpval= else echo "Ignore non-dhcp option: $optval" >> $log fi done if [ "$modified" != "0" ]; then if [ -f /etc/resolv.conf ] && [ -f ./resolv.conf.new ]; then cp -f /etc/resolv.conf ./resolv.conf.saved if [ $? != 0 ]; then echo "Failed to copy existing /etc/resolv.conf." >> $log fi fi if [ -f ./resolv.conf.new ]; then echo "Replacing existing name services with VPN settings" >> $log cp -f ./resolv.conf.new /etc/resolv.conf if [ $? != 0 ]; then echo "Replacement resolv.conf failed." >> $log exit 1; else rm ./resolv.conf.new fi fi else echo "/etc/resolv.conf not replaced" >> $log fi
VPN Down script: /etc/openvpn/down.sh
#!/bin/bash
log="openvpn.log"
if [ -f ./resolv.conf.saved ]; then echo "Reverting to original name services." >> $log cp -f ./resolv.conf.saved /etc/resolv.conf if [ $? == 0 ]; then rm -f resolv.conf.saved else echo "Failed to recover /etc/resolv.conf." >> $log fi else echo "No stored name service information available." >> $log fi
Server configuration file: /etc/openvpn/server.conf
port 1194 dev tun # TLS parms tls-server #Notice that the server uses a Diffie-Hellman file. This is to make session key generation less resource intensive. ca /etc/pki/tls/certs/<my_certificate_authority>.pem cert /etc/pki/tls/certs/<my_server_certificate>.pem key /etc/pki/tls/private/<my_server_key>.key dh /etc/pki/tls/private/<my_server_diffe_hellman_file>.pem # Tell OpenVPN to be a multi-client udp server mode server # The server's virtual endpoints ifconfig 192.168.200.1 192.168.200.2 # Require all clients to have a ccd file. This provides clients with a static IP address, and no client that isn't assigned a static IP address will be permitted to join the network. # It is a little more overhead to keep track of clients, and probably isn't necessary in most cases. ccd-exclusive # Use individual client configs based on SSL CN client-config-dir /etc/openvpn/ccd # Pool of /30 subnets to be allocated to clients. # When a client connects, an --ifconfig command # will be automatically generated and pushed back to # the client. # ifconfig-pool 10.2.0.4 10.2.0.255 # Push route to client to bind it to our local virtual endpoint. # These are the only routes that will be delivered to the client over the VPN. All other internet traffic will route based on their current routing tables. push "route 192.168.200.0 255.255.255.0" push "route 10.1.0.0 255.255.0.0" push "route 10.2.0.0 255.255.0.0" push "route 10.128.0.0 255.255.0.0" # Push DHCP options to clients. Windows clients use these automatically, Linux clients need our up/down scripts to help them. push "dhcp-option DNS 192.168.200.12" push "dhcp-option DOMAIN work" push "dhcp-option DOMAIN-SEARCH intranet.work" # Client should attempt reconnection on link # failure. keepalive 10 60 # Delete client instances after some period # of inactivity. inactive 600 # Route the --ifconfig pool range into the # OpenVPN server. route 192.168.201.0 255.255.255.0 # The server doesn't need privileges user openvpn group openvpn # Keep TUN devices and keys open across restarts. persist-tun persist-key verb 2