Archive for the ‘Linux’ Category

Linode Native IPv6

Thursday, May 5th, 2011

Linode (my VPS provider) has recently added IPv6 support.  It's exciting to see companies starting to support this technology.  I've just reconfigured my Linode and it seems to be working well.  I did have some issues with the default route getting set by the autoconfig.  I'm going to blame shorewall6 for that.  It's getting late and didn't feel like messing with it further.  Adding the address and gateway to the interfaces file worked like a charm though.  I'm just happy they've started enabling it where they can.

Testing Webservers via Telnet or OpenSSL

Thursday, January 20th, 2011

Today I ran into an issue testing a website where I wanted to telnet to the server to see that it was responding appropriately.  The problem that I ran into was that the site is using SSL encryption.  So using telnet wouldn't cut it.  A quick search let me to Matthew Nuzum's blog, but I wanted to post it on my own blog for future reference.

So if you want to test a regular non-SSL site, you would use the following telnet command (please use your own domain for testing).

telnet google.com 80

After you are connected, you'd paste the following GET request and press enter twice. The empty line signals when your request is complete to the web server. You should then see the page headers and the html content.

GET / HTTP/1.1
Host: google.com

If you wanted to test an SSL enabled site, you would still paste the same GET request to the web server. But instead of using telnet, you would need to use the openssl command below. After you run the command, you'll see details of the web server's certificate. Then paste in the GET request and you should get back the page headers and content. Matthew's blog mentions that it is best to type your GET request into a text document and use copy and paste rather than typing it each time. I guess there can be a relatively short timeout depending on the server configuration.

openssl s_client -connect google.com:443

Comcast IPv6 via 6rd with OpenWRT Backfire (10.03 r20728)

Sunday, January 9th, 2011

A year or two ago I had configured IPv6 on an old Linksys WRT54G running OpenWRT.  Recently I attempted to make some changes to use Comcast's 6to4 mechanism, but the on-board flash chip stopped accepting file changes.  Since the old WRT54G routers were limited to an older Linux kernel for the wireless chipset support (which prevents 6rd tunnels from working), I decided it was time to upgrade to a new router. After doing some reading of the OpenWRT forums, I decided to go with the Buffalo WZR-HPG300NH.  This device has a much faster CPU, more RAM and a much larger flash.  It also has 802.11N as well as gig ethernet.  All that for under $100!  It appears to have the best features for the dollar.

The OpenWRT wiki has the necessary information for flashing the device and getting the basics to work. Once I had all the normal stuff configured, I started checking out information online regarding Comcast's IPv6 offerings.  In the past I was using HE's Tunnel Broker, but using something from my ISP was preferable.  I first configured a Comcast 6to4 tunnel.  I roughly followed Jay R. Wren's post and had success.  The only problem I noticed with 6to4 was that my browser prefered IPv4 over IPv6 unless the hostname only had an IPv6 address (both Firefox and Safari).  So, I didn't get to see the turtle or unicorn.  So sad..

So I decided to give Comcast's 6rd a try.  Supposedly the 6rd tunnels are the better way to go.  Some of the documentation on OpenWRT's wiki has question marks, so I wasn't really sure if the necessary bits were in the kernel.  Since the OpenWRT documentation seemed to be lacking, I started out with the script in this Linux.com article.  It didn't exactly work as I got errors with the "ip tunnel 6rd" command.  I then stumbled on the IPv6 page on the DD-WRT site.  I noticed they used a slightly different ip tunnel statement.  After some tuning, I came up with the following /etc/init.d/comcast_6rd script.

#!/bin/sh /etc/rc.common

START=42
STOP=88

WANIF=eth1
LANIF=br-lan

SIXRDTUNIF=6rdtun
SIXRDTUNMTU=1280
SIXRDTUNTTL=64

WANIP=`ip -4 addr show dev $WANIF | awk '/inet / {print $2}' | cut -d/ -f 1`
WANIPSPACED=`echo $WANIP | tr . ' '`

ISP6RDPREFIX='2001:55c'
ISP6RDPREFIXLEN='32'
ISP6RDBR=`dig +short 6rd.comcast.net`

LOCAL6PREFIX=`printf "$ISP6RDPREFIX:%02x%02x:%02x%02x" $WANIPSPACED`
LOCAL6PREFIXLEN=64

start() {
        # Setup the tunnel interface
        ip tunnel add $SIXRDTUNIF mode sit ttl $SIXRDTUNTTL remote any local $WANIP

        # Set the MTU
        ip link set $SIXRDTUNIF mtu $SIXRDTUNMTU                                  

        # Bring up the tunnel interface
        ip link set $SIXRDTUNIF up              

        # Set the tunnel interface IPv6 address
        ip -6 addr add $LOCAL6PREFIX:0::1/$ISP6RDPREFIXLEN dev $SIXRDTUNIF

        # Set the LAN interface IPv6 address
        ip -6 addr add $LOCAL6PREFIX:1::1/$LOCAL6PREFIXLEN dev $LANIF     

        # Set the default IPv6 route to the ISP's IPv4/IPv6 boarder router
        ip -6 route add 2000::/3 via ::$ISP6RDBR dev $SIXRDTUNIF          

        # Enable IPv6 Forwarding
        sysctl -w net.ipv6.conf.all.forwarding=1 > /dev/null
}                                                                         

stop() {
        ip tunnel del $SIXRDTUNIF
        ip -6 addr del $LOCAL6PREFIX:1::1/$LOCAL6PREFIXLEN dev $LANIF
}

This took care of the tunnel and I was able to ping ipv6.google.com.  The next major piece was to configure the IPv6 firewall.  Since the built-in scripts just handle IPv4, the best thing to do is add the necessary commands to /etc/firewall.user. I used an example from one of the tutorials except I added a variable for the tunnel interface.

# This file is interpreted as shell script.
# Put your custom iptables rules here, they will
# be executed with each firewall (re-)start.

SIXRDTUNIF=6rdtun

# start with a clean slate
ip6tables -F

# allow icmpv6
ip6tables -A INPUT -p ipv6-icmp -j ACCEPT
ip6tables -I OUTPUT -p ipv6-icmp -j ACCEPT
ip6tables -I FORWARD -p ipv6-icmp -j ACCEPT

# allow loopback
ip6tables -A INPUT -i lo -j ACCEPT
ip6tables -A OUTPUT -o lo -j ACCEPT

# allow anything out of the tunnel
ip6tables -A OUTPUT -o $SIXRDTUNIF -j ACCEPT

# allow LAN
ip6tables -A INPUT -i br-lan -j ACCEPT
ip6tables -A OUTPUT -o br-lan -j ACCEPT

# drop packets with a type 0 routing header
ip6tables -A INPUT -m rt --rt-type 0 -j DROP
ip6tables -A OUTPUT -m rt --rt-type 0 -j DROP
ip6tables -A FORWARD -m rt --rt-type 0 -j DROP

# allow link-local
ip6tables -A INPUT -s fe80::/10 -j ACCEPT
ip6tables -A INPUT -s fe80::/10 -j ACCEPT

# allow multicast
ip6tables -A INPUT -s ff00::/8 -j ACCEPT
ip6tables -A OUTPUT -s ff00::/8 -j ACCEPT

# allow forwarding
ip6tables -A FORWARD -i br-lan -j ACCEPT
ip6tables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

# forward ident requests
ip6tables -A FORWARD -p tcp --dport 113 -j ACCEPT

# default policy...
ip6tables -P INPUT DROP
ip6tables -P FORWARD DROP
ip6tables -P OUTPUT DROP

Now that I had this new IP space somewhat secured, the next thing to do is configure radvd. The radvd daemon advertises your IPv6 configuration to the client machines on your network. I saw a couple IPv6 tunnel scripts that dynamically generate the radvd.conf file when bringing up the tunnel and I wanted to avoid that. I had noticed a Base6to4Interface option in the manpage and was able to get that working with the 6to4 tunnel. The OpenWRT radvd init script didn't exactly generate the configuration I expected. Instead it grabbed the IPv6 prefix from my wan interface and used that even though I didn't specify it in the /etc/config/radvd file. I suspect that was a result of the init script parsing the Base6to4Interface option. I took a look at the init script, but it wasn't apparent what it was doing. Either way, the following /etc/config/radvd configuration file is working for me and it seems to be cleaner than other methods I've seen. I'm not sure if it's the proper way, so I fear it might break someday.

config interface
        option interface        'lan'
        option AdvSendAdvert    1
        option AdvManagedFlag   0
        option AdvOtherConfigFlag 0
        option ignore           0

config prefix
        option interface        'lan'
        option AdvOnLink        1
        option AdvAutonomous    1
        option AdvRouterAddr    0
        option Base6to4Interface        'eth1'
        option ignore           0

config rdnss
        option interface        'lan'
        # If not specified, the link-local address of the interface is used
        option addr             ''
        option ignore           1

I did try setting the rdnss option, but it didn't seem to have much effect on my Mac OS X clients. I actually wonder if it would be better to do DHCP so that I can do authoritative DDNS updates to ISC Bind. Maybe specifying the DNS servers in the DHCP configuration would work better with different client operating systems. I did find a big list of Comcast DNS servers. That might be helpful if I come back to the DNS issue.

So now I see the unicorn galloping and the turtle dancing.  I guess I could re-use my Hurricane Electric tunnel on my Linode.  I was really hoping they would have deployed native IPv6 by now though.

Simple IP Failover

Thursday, August 13th, 2009

At work, we have a few virtual machines which are part of some sort of cluster.  Some are active/active and some are active/passive.  Some are load balancers and some are webservers.  Using clustering or IP failover for high availability is a great.  It's much easier to update nodes one at a time without having to schedule downtime or cause noticeable impact to the end user.  In the past I've been using the Linux-HA software.  It's full featured but very complicated.

Recently, I've been working on moving one of our services from Redhat Enterprise Linux based virtual machines to Ubuntu Linux.  Redhat has always served us pretty well, but some of the project requirements included newer versions of software than what was available in the lastest distribution.  These requirements were met with the latest long term release of Ubuntu, which is now over a year old.  I appreciate the timely release schedule that Ubuntu uses as well as the inclusion of the latest versions of various packages.  But, I'm getting off topic here.  I was utilizing the Linux-HA software with Redhat to run an active/active cluster.  Each cluster node handled http requests directly (one hostname had a couple IP addresses associated).  This worked well, but the Linux-HA software wasn't fun to manage.  I didn't use any front end tools, just edited the XML files and loaded them.  My other complaint was that the requests were not properly balanced over all the nodes using the DNS round robin approach.

So the new implementation now has redundant backend workers (running Tomcat), with a single Apache load balancer on the front end.  The Apache load balancer works as a reverse proxy and gracefully handles conditions where workers stop responding.  The load is appropriately dispersed between the workers and I am extremely pleased with the results.

But, there is one problem.  The Apache load balancer isn't highly available.  I didn't want to set up the Linux-HA software again, so I started looking around for a more simple solution (think KISS).  I soon found this blog post and it was exactly what I was looking for.

After reading the article, I decided that I would like to write a perl script that would use lock files and daemonize instead of a shell script.  I had just done another script that did that and was very happy with how it worked.  After putting together the script and doing some testing, I decided I needed two scripts.  One for the active node and one for the standby node.  The basic idea is that the standby node checks the active node to see that it is up and running.  If it detects a failure, it will bring up the service IP address, send the arp packet, and restart the Apache daemon (so it sees all IP addresses).  When the standby node detects the primary is back on the network, it shuts down the service IP address.  On the primary node, it will check the default gateway to determine if it is up on the network.  If it detects a failure, it will shutdown the service IP address.  When it resumes network connectivity, it will add the service IP, send the arp packet, and restart the apache service.

So the two scripts are below.  First the one that runs on the primary and second is the one that runs on the standby node.  I hope to use these with little modifications for all our applications.  Some have multiple IP addresses for IP based virtualhosting (SSL sites).  I also installed the fake package to utilize the send_arp program. I will probably need to make some more revisions, but I thought this might be helpful to other people out there trying to accomplish the same thing. These scripts come "as-is" with no warranty what so ever. Feel free to do what you'd like with them. If anyone has better solutions, feel free to post a comment!

EDIT (19-August-2009): I had some issues with a race condition in one of the scripts. The script that brought the IP address up on the primary node had an issue with the ping counter. I decided to just run one script on the standby node to simplify things. On the primary, I scheduled a cronjob to run the send_arp command every minute. This will send an arp packet to update the tables on the router when it's on-line. I've also made some slight modifications to the script that runs on the backup host.

EDIT (28-March-2010): I've taken down my git repository and I'm including the script below.

#!/usr/bin/perl -w
#
# ipfaild.pl
#
# Daemon to handle IP failover and restart necessary services.
#
# 11-Aug-2009 - Patrick Hennessy
#
use strict;
use Sys::Syslog;
use POSIX qw(setsid);
use Fcntl ':flock';
use Net::Ping::External qw(ping);

# Vars
#
my $pid;
my $progname = "ipfaild";
my $daemon_pidfile = "/var/run/$progname/$progname.pid";
my $daemon_lockfile = "/var/run/$progname/$progname.lock";
my $log_facility = "LOG_DAEMON";
my $ifconfig = '/sbin/ifconfig';
my $send_arp = '/usr/sbin/send_arp';
my $apache2ctl = '/usr/sbin/apache2ctl';
my $ping_timeout = 1;
my $sleep_time = 2;
my $missed = 0;
my $ipRec;

my $otherHost = 'otherhost.domain.com';
my $thisMAC = '00:11:22:33:44:55';

my @ipRecords = (
        { name=> 'fooservice', pubip => '192.168.1.200', dev => 'eth0:10', mask => '255.255.255.0' },
);

# Subroutines
#
sub daemonize;

# Daemonize process
#
daemonize;

# Acquire exclusive lock
#
open LOCKFILE, ">$daemon_lockfile" or die "$progname: can't write to $daemon_lockfile: $!\n";
flock(LOCKFILE, LOCK_EX | LOCK_NB) or die "$progname: can't acquire lock: $daemon_lockfile: $!\n";
print LOCKFILE "$pid\n";

# Open syslog.
#
openlog($progname, "pid", $log_facility);

# Signal handlers
#
my $keep_processing = 1;
$SIG{HUP}  = sub { syslog("info", "Caught SIGHUP:  exiting gracefully"); $keep_processing = 0; };
$SIG{INT}  = sub { syslog("info", "Caught SIGINT:  exiting gracefully"); $keep_processing = 0; };
$SIG{QUIT}  = sub { syslog("info", "Caught SIGQUIT:  exiting gracefully"); $keep_processing = 0; };
$SIG{TERM}  = sub { syslog("info", "Caught SIGTERM:  exiting gracefully"); $keep_processing = 0; };

# Bring down interfaces.
#
for $ipRec (@ipRecords) {
        syslog("info", "Running: $ifconfig $ipRec->{'dev'} down");
        system($ifconfig, $ipRec->{'dev'}, 'down') == 0
                or syslog("info", "Error: $? Could not run: $ifconfig $ipRec->{'dev'} down");
}

# Main loop
#
while ($keep_processing) {
        # Ping the other host and count dropped packets
        #
        if (! ping(host => $otherHost, timeout => $ping_timeout)) {
                $missed++;
        } else {
                if ($missed > 2) {
                        for $ipRec (@ipRecords) {
                                syslog("info", "Running: $ifconfig $ipRec->{'dev'} down");
                                system($ifconfig, $ipRec->{'dev'}, 'down') == 0
                                        or syslog("info", "Error: $? Could not run: $ifconfig $ipRec->{'dev'} down");
                        }
                        $missed = 0;
                }
        }

        # Bring up IP addresses if packets dropped
        #
        if ($missed == 2) {
                for $ipRec (@ipRecords) {
                        syslog("info", "Running: $ifconfig $ipRec->{'dev'} $ipRec->{'pubip'} netmask $ipRec->{'mask'}");
                        system($ifconfig, $ipRec->{'dev'}, $ipRec->{'pubip'}, 'netmask', $ipRec->{'mask'}) == 0
                                or syslog("info", "Error: $? Could not run: $ifconfig $ipRec->{'dev'} $ipRec->{'pubip'} netmask $ipRec->{'mask'}");
                        syslog("info", "Running: $send_arp $ipRec->{'pubip'} $thisMAC $ipRec->{'pubip'} ff:ff:ff:ff:ff:ff");
                        system($send_arp, $ipRec->{'pubip'}, $thisMAC, $ipRec->{'pubip'}, 'ff:ff:ff:ff:ff:ff') == 0
                                or syslog("info", "Error: $? Could not run: $send_arp $ipRec->{'pubip'} $thisMAC $ipRec->{'pubip'} ff:ff:ff:ff:ff:ff");
                }
                syslog("info", "Running: $apache2ctl restart");
                system($apache2ctl, 'restart') == 0
                        or syslog("info", "Error: $? Could not run: $apache2ctl restart");
        }

        # Sleep
        #
        sleep($sleep_time);
}

# Bring down interfaces.
#
for $ipRec (@ipRecords) {
        syslog("info", "Running: $ifconfig $ipRec->{'dev'} down");
        system($ifconfig, $ipRec->{'dev'}, 'down') == 0
                or syslog("info", "Error: $? Could not run: $ifconfig $ipRec->{'dev'} down");
}

# Close syslog.
#
closelog();

# Close lockfile.
close (LOCKFILE);

# Exit.
#
exit(0);

# Functions
#
sub daemonize() {
        open STDIN, '/dev/null' or die "$progname: can't read /dev/null: $!";
        open STDOUT, '>/dev/null' or die "$progname: can't write to /dev/null: $!";
        defined(my $pid = fork) or die "$progname: can't fork: $!";
        if($pid) {
                # parent
                open PIDFILE, ">$daemon_pidfile" or die "$progname: can't write to $daemon_pidfile: $!\n";
                print PIDFILE "$pid\n";
                close(PIDFILE);
                exit;
        }
        # child
        setsid or die "$progname: can't start a new session: $!";
        open STDERR, '>&STDOUT' or die "$progname: can't dup stdout: $!";
}

Linode Dynamic DNS Ash Script

Monday, May 11th, 2009

After my post last night regarding a bash script to update Linode's DNS Manager, opello from #linode on OFTC provided some sed commands to parse the JSON output. Using his commands, I made another version of the script.  I've tested this on my router running the Tomato firmware (which runs BusyBox).

I found that the wget command built into the version of BusyBox does not support HTTP POST. It also does not support https urls, only http. This means your Linode API key would be transmitted in clear text, which probably isn't a good thing.

Another solution that was suggested was to simply wget a CGI script running on your webserver, which could update the Linode DNS Manager using perl or python over secure channels. That would reduce the complexity on the home router side and allow you to use the developed Linode API libraries.

Therefor, I wouldn't recommend using this unless you are able to send the requests over ssl channels. I am glad to have a slightly better understanding of sed. I'll probably modify the original bash script to use that as well.

#!/bin/ash
#
# Script to update Linode's DNS Manager for a given name.
#

# Things you need to change.
APIKEY=$(cat /home/root/linode-apikey)
LASTIP="/tmp/lastip"
DOMAIN="domain.com"
SOAEMAIL="hostmaster@domain.com"
STATUS="1"
RRTYPE="A"
RRNAME="home"
IFACE="vlan1"

# Shouldn't need to change anything below here.

WGET="wget -qO - http://api.linode.com/api/?api_key=$APIKEY"
NEWIP=$(ifconfig $IFACE | head -n2 | tail -n1 | cut -d: -f2 | cut -d' ' -f1)
test -e $LASTIP && OLDIP=$(cat $LASTIP) || OLDIP=""

if [ x"$OLDIP" = x"$NEWIP" ]; then
  logger "No IP address change detected. Keeping $NEWIP"
else
   DOMAINID=$($WGET"&action=domainList" | \
        sed -nr "s#.*\"DOMAIN\":\"$DOMAIN\",\"DOMAINID\":([0-9]+),.*#\1#p")
   RESOURCEID=$($WGET"&action=domainResourceList&DomainID=$DOMAINID" | \
        sed -nr "s#.*\"RESOURCEID\":([0-9]+),\"DOMAINID\":$DOMAINID,\"TYPE\":\"$RRTYPE\",\"NAME\":\"$RRNAME\".*#\1#p")
   $WGET"&action=domainResourceSave&ResourceID=$RESOURCEID&DomainID=$DOMAINID&Name=$RRNAME&Type=$RRTYPE&Target=$NEWIP"; echo
   $WGET"&action=domainSave&DomainID=$DOMAINID&Domain=$DOMAIN&Type=master&Status=$STATUS&SOA_Email=$SOAEMAIL"; echo
   echo $NEWIP > $LASTIP
   logger "Updated IP address to $NEWIP"
fi