Generate VTun Configurations Automatically

Generate VTun Configurations Automatically

Generate a vtund.conf file on the fly to match changing network conditions.

If you've just come from "Tunnel with VTun and SSH" [Hack #102], the following script will automatically generate a working vtund.conf file for the client side.

If you haven't read the previous hack (or if you've never used VTun), go back and read it before attempting to grok this bit of Perl. Essentially, it attempts to take the guesswork out of changing around the routing table on the client side by auto-detecting the default gateway and building vtund.conf accordingly.

The Code

Save this file as vtundconf, and run the script each time you use a new wireless network to generate an appropriate vtund.conf on the fly:

#!/usr/bin/perl -w
# vtund wrapper in need of a better name.
# (c)2002 Schuyler Erle & Rob Flickenger
################ CONFIGURATION

# If TunnelName is blank, the wrapper will look at @ARGV or $0.
# Config is TunnelName, LocalIP, RemoteIP, TunnelHost, TunnelPort, Secret
my $TunnelName = ""; 
my $Config   = q{
  home  5000  sHHH
  tunnel2       6001  foobar


use POSIX 'tmpnam';
use IO::File;
use File::Basename;
use strict;

# Where to find things...
$ENV{PATH}  = "/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/[RETURN]sbin";
my $IP_Match = '((?:\d{1,3}\.){3}\d{1,3})';      # match
my $Ifconfig = "ifconfig -a";
my $Netstat = "netstat -rn";
my $Vtund  = "/bin/echo";
my $Debug  = 1;

# Load the template from the data section.
my $template = join( "", );

# Open a temp file -- adapted from Perl Cookbook, 1st Ed., sec. 7.5.
my ( $file, $name ) = ("", "");
$name = tmpnam(  )
  until $file = IO::File->new( $name, O_RDWR|O_CREAT|O_EXCL );
END { unlink( $name ) or warn "Can't remove temporary file $name!\n"; }

# If no TunnelName is specified, use the first thing on the command line,
# or if there isn't one, the basename of the script.
# This allows users to symlink different tunnel names to the same script.
$TunnelName ||= shift(@ARGV) || basename($0);
die "Can't determine tunnel config to use!\n" unless $TunnelName;

# Parse config.
my ($LocalIP, $RemoteIP, $TunnelHost, $TunnelPort, $Secret);
for (split(/\r*\n+/, $Config)) {
  my ($conf, @vars) = grep( $_ ne "", split( /\s+/ ));
  next if not $conf or $conf =~ /^\s*#/o; # skip blank lines, comments
  if ($conf eq $TunnelName) {
    ($LocalIP, $RemoteIP, $TunnelHost, $TunnelPort, $Secret) = @vars;

die "Can't determine configuration for TunnelName '$TunnelName'!\n"
  unless $RemoteIP and $TunnelHost and $TunnelPort;

# Find the default gateway.
my ( $GatewayIP, $ExternalDevice );

for (qx{ $Netstat }) {
  # In both Linux and BSD, the gateway is the next thing on the line,
  # and the interface is the last.
  if ( /^(?:|default)\s+(\S+)\s+.*?(\S+)\s*$/o ) {
    $GatewayIP = $1;
    $ExternalDevice = $2;

die "Can't determine default gateway!\n" unless $GatewayIP and $ExternalDevice;

# Figure out the LocalIP and LocalNetwork.
my ( $LocalNetwork );
my ( $iface, $addr, $up, $network, $mask ) = "";

sub compute_netmask {
  ($addr, $mask) = @_;
  # We have to mask $addr with $mask because linux /sbin/route
  # complains if the network address doesn't match the netmask.
  my @ip = split( /\./, $addr );
  my @mask = split( /\./, $mask );
  $ip[$_] = ($ip[$_] + 0) & ($mask[$_] + 0) for (0..$#ip);
  $addr = join(".", @ip);
  return $addr;

for (qx{ $Ifconfig }) {
  last unless defined $_;

  # If we got a new device, stash the previous one (if any).
  if ( /^([^\s:]+)/o ) {
    if ( $iface eq $ExternalDevice and $network and $up ) {
      $LocalNetwork = $network;
    $iface = $1;
    $up = 0;

  # Get the network mask for the current interface.
  if ( /addr:$IP_Match.*?mask:$IP_Match/io ) {
    # Linux style ifconfig.
    compute_netmask($1, $2);
    $network = "$addr netmask $mask";
  } elsif ( /inet $IP_Match.*?mask 0x([a-f0-9]{8})/io ) {
    # BSD style ifconfig.
    ($addr, $mask) = ($1, $2);
    $mask = join(".", map( hex $_, $mask =~ /(..)/gs )); 
    compute_netmask($addr, $mask);
    $network = "$addr/$mask";

  # Ignore interfaces that are loopback devices or aren't up.
  $iface = "" if /\bLOOPBACK\b/o;
  $up++    if /\bUP\b/o;

die "Can't determine local IP address!\n" unless $LocalIP and $LocalNetwork;

# Set OS dependent variables.
my ( $GW, $NET, $PTP );
if ( $^O eq "linux" ) {
  $GW = "gw"; $PTP = "pointopoint"; $NET = "-net";
} else {
  $GW = $PTP = $NET = "";

# Parse the config template.
$template =~ s/(\$\w+)/$1/gee;

# Write the temp file and execute vtund.
if ($Debug) {
  print $template;
} else {
  print $file $template;
  close $file;
  system("$Vtund $name");

_  _DATA_  _

options {
  port $TunnelPort;
  ifconfig /sbin/ifconfig;
  route /sbin/route;

default {
  compress no;
  speed 0;

# 'mytunnel' should really be \Qbasename $0\Q or some such
# for automagic config selection
$TunnelName {   
  type tun;
  proto tcp;
  keepalive yes;

  pass $Secret;

  up {
   ifconfig "%% $LocalIP $PTP $RemoteIP arp";
   route "add $TunnelHost $GW $GatewayIP";
   route "delete default";
   route "add default $GW $RemoteIP";
   route "add $NET $LocalNetwork $GW $GatewayIP";

  down {
   ifconfig "%% down";
   route "delete default";
   route "delete $TunnelHost $GW $GatewayIP";
   route "delete $NET $LocalNetwork";
   route "add default $GW $GatewayIP";

Running the Hack

To configure the script, take a look at its Configuration section. The first line of $Config contains the addresses, port, and secret used in "Tunnel with VTun and SSH" [Hack #102]. The second line simply serves as an example of how to add more.

To run the script, either call it as vtundconf home or set $TunnelName to the name of the tunnel you would like to be the default. Better yet, make symlinks to the script, like this:

# ln -s vtundconf home 
# ln -s vtundconf tunnel2

Then, you can generate the appropriate vtund.conf by calling the symlink directly:

# vtundconf home > /usr/local/etc/vtund.conf

You might be wondering why anyone would go to all of the trouble of making a vtund.conf-generating script in the first place. Once you get the settings right, you'll never have to change them, right?

Well, usually, that's true. But consider the case of a Linux laptop that uses many different networks in the course of the day (say, a DSL line at home, Ethernet at work, and maybe a wireless connection at the local coffee shop). Running vtundconf once at each location will give you a working configuration instantly, even if your IP address and gateway are assigned by DHCP. This makes it easy to get up and running quickly with a live, routable IP address, regardless of the local network topology.

Rob Flickenger

 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows