Kludging /etc/resolv.conf In Ubuntu 10.04
By Gerry Patterson
Recently I got a phone call from someone who was encountering problems
with Ubuntu networking. The problem was easily solved by updating the
resolver configuration file, /etc/resolv.conf. But the changes weren't
"sticky". Eventually the /etc/resolv.conf file would be over-written and
the problem would reappear.
Th script presented below is a "kludge". After system startup it will
monitor the contents of /etc/resolv.conf ... If your preferred DNS disappears,
it will re-insert them.
Note: If all you want to do is install the script just go
to the end of this page).
The New User Friendly Ubuntu
As Ubuntu gains in popularity, there has been a number of "enhancements" which make it easier to use and install. Of course the downside of this is that sometimes when something unexpected happens it can be difficult to troubleshoot the problem.
One of the most complex and troublesome areas of computing is "networking". And the newer versions of Ubuntu seem to have automated a lot of this stuff. In the past it was necessary to configure "ppoe" and configure your IP addresses ... This would mean running some setup scripts and possibly editing the files /etc/network/interfaces, and /etc/resolv.conf
These days a lot of this stuff seems to happen "magically". And it's not always obvious how it has happened. A few days ago I got a phone call from a perplexed friend who could not access various things on the internet. After guiding him through a few simple commands in a terminal window, I was able to instruct him on adding lines to /etc/resolv.conf. This is pretty easy ... In Ubuntu all you need to do to alter the file is enter the command:
sudo pico /etc/resolv.conf
Then you just move the cursor down to the where the nameserver is mentioned and enter your preferred DNS. This might be your ISP's primary and secondary DNS or you might use Google's public DNS.
In this particular case, the problem may have arisen because the modem had been setup to resolve DNS queries for the local subnets. The network was being shaped and the response was very poor.
There are many areas to investigate ... All of them rather tedious over the telephone. On the other hand, the resolver configuration file was not being over-written frequently, only when the machine was re-booted and sometimes when the connection had to be re-established.
The Sledghammer Approach
It seemed to me that this an ideal case for a simple script that checks the state of /etc/resolv.conf every ten minutes or so and re-writes it if it changes.
I came up with this following script:
#!/usr/bin/perl use strict; use warnings; use subs qw (fatal_err logmsg); my %DNS_static = ( "1.2.3.4" => 1, "1.2.3.5" => 2, "1.2.3.6" => 3, ); my $logger = "/usr/bin/logger"; my $resolv = "/etc/resolv.conf"; my $sleeps = 0; my $max_sleep = 10; # ------------------------------------------------------------------------ sub logmsg { my ($msg) = @_; $msg =~ s/\s+$//; system "$logger $0: $msg\n"; } # ------------------------------------------------------------------------ sub fatal_err { logmsg "FATAL: $_[0]"; die $_[0]; } # ------------------------------------------------------------------------ logmsg "commenced"; while (1) { my @dns; my @head; my @tail; my %DNS = %DNS_static; $sleeps++; $sleeps = $max_sleep / 2 if ($sleeps > $max_sleep); sleep $sleeps * 60; open RESOLV,$resolv || fatal_err "Cannot open $resolv"; while (<RESOLV>) { if (/^nameserver (\S+)/) { my $dns = $1; push @dns,$_; delete $DNS{$dns} if ($DNS{$dns}); } else { if (@dns) { push @tail,$_; } else { push @head,$_; } } } if ( keys %DNS ) { logmsg "Over-writing $resolv"; open RESOLV,">$resolv" || fatal_err "Cannot open: $resolv"; foreach (@head) {print RESOLV} foreach my $dns (sort{$DNS{$a}<=>$DNS{$b}} keys %DNS){ print RESOLV "nameserver $dns\n"; } foreach (@dns) {print RESOLV} foreach (@tail) {print RESOLV} close RESOLV; $sleeps = 0; } } |
All this script does, is check the contents of the resolver configuration file for the preferred nameserver entries, 1.2.3.4, 1.2.3.5 and 1.2.3.6 ... Of course you wouldn't actually use those IP address (substitute the ones you really want).
Whenever it detects that there has been a change, it opens the resolver configuration file and adds the preferred nameservers at the top of the list (if they are missing). Also the sleep time varies. It starts off at 1 minute and gradually increases the sleep time to vary between five and ten minutes. However, whenever something removes the preferred nameservers from the resolver file the sleep time drops back to one minute.
Also it writes to /var/log/syslog with the logger command. When it starts and whenever it over-writes /etc/resolv.conf, it will put a message in /var/log/syslog.
Kludging The Resolver
The above script must be run as root.
Let's suppose you want to call it /usr/local/sbin/dns_kludge. Then you could start it with the command:
sudo nohup /usr/local/sbin/dns_kludge &
It would be better to put that command in your startup script (/etc/rc.local). The rc.local script is run by /etc/init.d/rc.local ... Just make sure that /etc/rc.local is executable ... And if it contains any other commands, be careful that they don't exit with status greater than zero. The rc.local script often uses the -e option. At the top of the script, you will see:
#!/bin/sh -e
Some people just remove the "-e" ... Which means that it keeps going until it gets to the end.
If you understand the above script, you probably understand enough to fix the problem anyway ... Or maybe you are just lazy and prefer to use a sledgehammer rather than a screwdriver.
Note: If you want to install the above script just go to the end of this page.
The User-Friendly Version
Now a user-friendly "sledgehammer" might seem like an oxymoron. But if you wanted to make this so that it could be installed, it seemed to me that it would be easiest to write it and install it with another perl script.
Below is the full version of the script that installs the kludge script:
#!/usr/bin/perl use strict; use warnings; use POSIX; my $logger = "/usr/bin/logger"; my $resolv = "/etc/resolv.conf"; my $rclocal = "/etc/rc.local"; my $install_dir = "/usr/local/sbin"; my $name = "dns_kludge"; my $prgname = "$install_dir/$name"; my $P1 = '#!/usr/bin/perl use strict; use warnings; use subs qw (fatal_err logmsg); my %DNS_static = ('; my $P2 = '); my $logger = "/usr/bin/logger"; my $resolv = "/etc/resolv.conf"; my $sleeps = 0; my $max_sleep = 10; # ------------------------------------------------------------------------ sub logmsg { my ($msg) = @_; $msg =~ s/\s+$//; system "$logger $0: $msg\n"; } # ------------------------------------------------------------------------ sub fatal_err { logmsg "FATAL: $_[0]"; die $_[0]; } # ------------------------------------------------------------------------ logmsg "commenced"; while (1) { my @dns; my @head; my @tail; my %DNS = %DNS_static; $sleeps++; $sleeps = $max_sleep / 2 if ($sleeps > $max_sleep); sleep $sleeps * 60; open RESOLV,$resolv || fatal_err "Cannot open $resolv"; while (<RESOLV>) { if (/^nameserver (\S+)/) { my $dns = $1; push @dns,$_; delete $DNS{$dns} if ($DNS{$dns}); } else { if (@dns) { push @tail,$_; } else { push @head,$_; } } } if ( keys %DNS ) { logmsg "Over-writing $resolv"; open RESOLV,">$resolv" || fatal_err "Cannot open: $resolv"; foreach (@head) {print RESOLV} foreach my $dns (sort{$DNS{$a}<=>$DNS{$b}} keys %DNS){ print RESOLV "nameserver $dns\n"; } foreach (@dns) {print RESOLV} foreach (@tail) {print RESOLV} close RESOLV; $sleeps = 0; } }'; my $reply; my @dns; die "\nERROR: MUST specify either 'install', 'uninstall' or 'config' as an argument!\n\n" unless (@ARGV); if ( $ARGV[0] eq "install" || $ARGV[0] eq "config" ) { # install unless ( -e $resolv && -w $resolv ) { die "ERROR: $resolv is missing or not writeable!\n"; } unless ( -e $rclocal && -w $rclocal ) { die "ERROR: $rclocal is missing or not writeable!\n"; } unless ( -d $install_dir) { mkdir $install_dir || die "Cannot create $install_dir - $!"; die "Cannot find folder: $install_dir" unless (-d $install_dir); print "INFO: Created folder $install_dir\n"; } unless ( -x $logger ) { print STDERR "ERROR: cannot find $logger\n"; die "Do you need to install util-linux?\n"; } print "\nEnter DNS servers, one at a time, as IP addr (Press Enter When Done):\n"; while (<STDIN>) { my $reply = $_; $reply =~ s/\s+$//; $reply =~ s/^\s+//; last unless ($reply); unless ($reply =~ /^\d+\.\d+\.\d+\.\d+$/) { print "ERROR: $reply does not seem to be an IP address\n"; next; } my $ping = `ping -c1 $reply 2>&1`; if ($?) { print "WARNING: Cannot ping $reply!\n"; } push @dns,$reply; } die "\nERROR: Must specify at least one DNS server\n\n" unless (@dns); open PRG,">$prgname" || die "Cannot open $prgname for output - $!"; print PRG "$P1\n"; my $i = 1; foreach my $dns (@dns) { printf PRG "\t\"%s\" => %d,\n",$dns,$i++; } print PRG "$P2\n"; close PRG; chmod 0755,"$prgname"; if ( $ARGV[0] eq "install" ) { open RCLOCAL,"$rclocal" || die "Cannot open: $rclocal - $!"; open NEW,">$rclocal.new" || die "Cannot open: $rclocal.new - $!"; my $exit = 0; while (<RCLOCAL>) { if ( /^exit/ && $exit == 0) { $exit++; print NEW "nohup $prgname &\n"; } print NEW; } print NEW "nohup $prgname &\n" unless ($exit); close NEW; close RCLOCAL; rename $rclocal,sprintf("%s.bak.%s",$rclocal,POSIX::strftime( "%y%m%d%H%M", localtime)); rename "$rclocal.new",$rclocal; chmod 0755,"$rclocal"; } print "\nInstalled -- Reboot to activate\n\n"; } elsif ( $ARGV[0] eq "uninstall" ) { # uninstall unlink $prgname || die "Cannot unlink $prgname - $!"; open RCLOCAL,"$rclocal" || die "Cannot open: $rclocal - $!"; open NEW,">$rclocal.new" || die "Cannot open: $rclocal.new - $!"; while (<RCLOCAL>) { next if (index ($_,"nohup $prgname") == 0 ); print NEW; } close NEW; close RCLOCAL; rename $rclocal,sprintf("%s.bak.%s",$rclocal,POSIX::strftime( "%y%m%d%H%M", localtime)); rename "$rclocal.new",$rclocal; chmod 0755,"$rclocal"; print "\nUninstalled -- Reboot to remove\n\n"; } else { die "\nERROR: MUST specify either 'install', 'uninstall' or 'config'\n\n"; } |
As you can see it contains the original script as a string. It prompts for the nameservers and then alters the /etc/rc.local script to run it at system startup.
The details are written to syslog. To track them you could enter:
sudo grep dns_kludge /var/log/syslog
To stop it, look for the PID with:
ps -ef | grep dns_kludge.
Kill it with "kill -TERM"
Installing/Uninstalling.
To fetch the install script, use wget. The install, config and uninstall options are listed below. Note: If you wish to change the configuration because you change your mind about the nameserver entries, then you must "uninstall" and then "install" again.
# get and install wget http://www.pgts.com.au/download/scripts/make_dk sudo perl make_dk install # remove sudo perl make_dk uninstall |
As with most of the scripts on this site, there are no restrictions on the
use of this code.