Commit 2690be45 authored by Ryan Jackson's avatar Ryan Jackson

Initial client code and rules for Linux firewalls

Made the following changes to the clientside code to support Linux
firewalls:

- Made os_fwconfig_line() actually do something.
- getfwconfig() adds an 'IPS' hash to the fwinfo hash.  This contains
  the IP address for each host, much like how the 'MACS' hash contains
  the MAC address for each host.  This is needed because ebtables (which
  is needed for ARP proxying) doesn't resolve hostnames.

Rules are stored in firewall/iptables-fw-rules.  Syntax is similar to
fw-rules, but without the rule number (since iptables doesn't use rule
numbers).  These should be equivalent to our ipfw-based rules, but I
haven't tested every case yet to confirm this.  I'm sure some changes
will be necessary.
parent 4a65de87
......@@ -2304,6 +2304,7 @@ sub getfwconfig($$;$)
my @fwrules = ();
my @fwhosts = ();
my %fwhostmacs = ();
my %fwhostips = ();
my %fwsrvmacs = ();
$$infoptr = undef;
......@@ -2374,8 +2375,9 @@ sub getfwconfig($$;$)
push(@fwhosts,
"NAME=$host IP=$ip ALIASES=''");
# and save off the MACs
# and save off the MACs and IPs
$fwhostmacs{$host} = $mac;
$fwhostips{$host} = $ip;
} elsif ($line =~ /$spat/) {
my $srv = $1;
my $ip = $2;
......@@ -2461,6 +2463,10 @@ sub getfwconfig($$;$)
if (%fwhostmacs) {
$fwinfo->{"MACS"} = \%fwhostmacs;
}
if (%fwhostips) {
$fwinfo->{"IPS"} = \%fwhostips;
}
# make a pass over the rules, expanding variables
my $bad = 0;
......
......@@ -1220,18 +1220,257 @@ sub os_getnfsmounts($)
return 0;
}
sub os_fwconfig_line($@)
{
my ($fwinfo, @fwrules) = @_;
my ($upline, $downline);
my $errstr = "*** WARNING: Linux firewall not implemented\n";
sub os_fwconfig_line($@) {
my ($fwinfo, @fwrules) = @_;
my ($upline, $downline);
my $pdev;
my $vlandev;
my $myip;
my $mymask;
$myip = `cat $BOOTDIR/myip`;
chomp($myip);
$mymask = `cat $BOOTDIR/mynetmask`;
chomp($mymask);
warn $errstr;
$upline = "echo $errstr; exit 1";
$downline = "echo $errstr; exit 1";
if ($fwinfo->{TYPE} ne "iptables" && $fwinfo->{TYPE} ne "iptables-vlan") {
warn "*** WARNING: unsupported firewall type '", $fwinfo->{TYPE}, "'\n";
return ("false", "false");
}
return ($upline, $downline);
# XXX debugging
my $logaccept = defined($fwinfo->{LOGACCEPT}) ? $fwinfo->{LOGACCEPT} : 0;
my $logreject = defined($fwinfo->{LOGREJECT}) ? $fwinfo->{LOGREJECT} : 0;
my $dotcpdump = defined($fwinfo->{LOGTCPDUMP}) ? $fwinfo->{LOGTCPDUMP} : 0;
#
# Convert MAC info to a useable form and filter out the firewall itself
#
my $href = $fwinfo->{MACS};
while (my ($node,$mac) = each(%$href)) {
if ($mac eq $fwinfo->{OUT_IF}) {
delete($$href{$node});
} elsif ($mac =~ /^(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})$/) {
$$href{$node} = "$1:$2:$3:$4:$5:$6";
} else {
warn "*** WARNING: Bad MAC returned for $node in fwinfo: $mac\n";
return ("false", "false");
}
}
$href = $fwinfo->{SRVMACS};
while (my ($node,$mac) = each(%$href)) {
if ($mac eq $fwinfo->{OUT_IF}) {
delete($$href{$node});
} elsif ($mac =~ /^(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})$/) {
$$href{$node} = "$1:$2:$3:$4:$5:$6";
} else {
warn "*** WARNING: Bad MAC returned for $node in fwinfo: $mac\n";
return ("false", "false");
}
}
#
# VLAN enforced layer2 firewall with Linux iptables
#
if ($fwinfo->{TYPE} eq "iptables-vlan") {
if (!defined($fwinfo->{IN_VLAN})) {
warn "*** WARNING: no VLAN for iptables-vlan firewall, NOT SETUP!\n";
return ("false", "false");
}
$pdev = `$BINDIR/findif $fwinfo->{IN_IF}`;
chomp($pdev);
my $vlanno = $fwinfo->{IN_VLAN};
$vlandev = $pdev . '.' . $vlanno;
$upline = "modprobe 8021q\n";
$upline .= "vconfig add $pdev $vlanno > /dev/null\n";
$upline .= "ifconfig $vlandev up\n";
$upline .= "brctl addbr br0\n";
$upline .= "ifconfig br0 up\n";
$upline .= "brctl addif br0 $pdev\n";
$upline .= "brctl addif br0 $vlandev\n";
$upline .= "ifconfig br0 $myip netmask $mymask\n";
$upline .= "ip route flush dev br0\n";
$upline .= "ip route show | while read line; do\n";
$upline .= " echo \$line | grep 'dev $pdev' > /dev/null || continue\n";
$upline .= " new_route=`echo \$line | sed s/$pdev/br0/`\n";
$upline .= " ip route del \$line\n";
$upline .= " ip route add \$new_route\n";
$upline .= "done\n";
$upline .= "ifconfig $pdev 0.0.0.0\n";
$downline .= " ifconfig $pdev $myip netmask $mymask\n";
$downline .= " ip route flush dev $pdev\n";
$downline .= " ip route show | while read line; do\n";
$downline .= " echo \$line | grep 'dev br0' > /dev/null || continue\n";
$downline .= " new_route=`echo \$line | sed s/br0/$pdev/`\n";
$downline .= " ip route del \$line\n";
$downline .= " ip route add \$new_route\n";
$downline .= " done\n";
$downline .= " ip route flush dev br0\n";
$downline .= " ifconfig br0 down\n";
$downline .= " ifconfig $vlandev down\n";
$downline .= " brctl delif br0 $vlandev\n";
$downline .= " brctl delif br0 $pdev\n";
$downline .= " brctl delbr br0\n";
$downline .= " vconfig rem $vlandev > /dev/null\n";
#
# Setup proxy ARP entries.
#
if (defined($fwinfo->{MACS})) {
$upline .= "ebtables -t nat -F PREROUTING\n";
# publish servers (including GW) on inside and for us on outside
if (defined($fwinfo->{SRVMACS})) {
my $href = $fwinfo->{SRVMACS};
while (my ($ip,$mac) = each %$href) {
$upline .= "ebtables -t nat -A PREROUTING -i $vlandev " .
"-p ARP --arp-opcode Request " .
"--arp-ip-dst $ip -j arpreply " .
"--arpreply-mac $mac\n";
}
}
# provide node MACs to outside
my $href = $fwinfo->{MACS};
while (my ($node,$mac) = each %$href) {
my $ip = $fwinfo->{IPS}{$node};
$upline .= "ebtables -t nat -A PREROUTING -i $pdev " .
"-p ARP --arp-opcode Request " .
"--arp-ip-dst $ip -j arpreply " .
"--arpreply-mac $mac\n";
}
$upline .= "ebtables -t nat -A PREROUTING -p ARP " .
"--arp-ip-dst $myip -j ACCEPT\n";
$upline .= "ebtables -t nat -A PREROUTING -p ARP -j DROP\n";
if ($dotcpdump) {
$upline .= " tcpdump -i $vlandev ".
"-w $LOGDIR/in.tcpdump >/dev/null 2>&1 &\n";
$upline .= " tcpdump -i $pdev ".
"-w $LOGDIR/out.tcpdump not vlan >/dev/null 2>&1 &\n";
$downline .= " killall tcpdump >/dev/null 2>&1\n";
}
}
# XXX HACK ALERT
# The rules may contain hostnames which iptables will try to resolve.
# Normally this isn't a problem, but if we've set up a bridge device
# it may take a bit before the bridge starts letting packets through
# again.
$upline .= "sleep 30\n";
} else {
$upline .= "sysctl -w net.ipv4.ip_forward=1\n";
$downline .= "sysctl -w net.ipv4.ip_forward=0\n";
}
# XXX This is ugly. Older version of iptables can't handle source or
# destination hosts or nets in the format a,b,c,d. Newer versions of
# iptables automatically expand this to separate rules for each host/net,
# so we need to do the same thing here. Since a rule could contain
# multiple sources and multiple dests, we expand each separately.
my @new_rules;
foreach my $rule (@fwrules) {
$rulestr = $rule->{RULE};
if ($rulestr =~ /^(.+)\s+(-s|--source)\s+([\S]+)(.+)/) {
push @new_rules, "$1 $2 $_ $4" for split(/,/, $3);
} else {
push @new_rules, $rulestr;
}
}
@fwrules = ();
foreach my $rulestr (@new_rules) {
if ($rulestr =~ /^(.+)\s+(-d|--destination)\s+([\S]+)(.+)/) {
push @fwrules, "$1 $2 $_ $4" for split(/,/, $3);
} else {
push @fwrules, $rulestr;
}
}
@new_rules = ();
foreach my $rulestr (@fwrules) {
$rulestr =~ s/pdev/$pdev/g;
$rulestr =~ s/vlandev/$vlandev/g;
$rulestr =~ s/\s+me\s+/ $myip /g;
# Ugh. iptables wants port ranges in the form a:b, but
# our firewall variable expansion can contain a-b. Try
# to fix it.
$rulestr =~ s/\s+--dport\s+(\S+)-(\S+)/ --dport $1:$2/;
$rulestr =~ s/\s+--sport\s+(\S+)-(\S+)/ --sport $1:$2/;
if ($logaccept && $rulestr =~ /-j ACCEPT$/) {
if ($rulestr =~ /^iptables\s+/) {
push @new_rules, $rulestr;
$rulestr =~ s/ACCEPT$/LOG/;
} elsif ($rulestr =~ /^ebtables\s+/) {
$rulestr =~ s/ACCEPT$/--log -j ACCEPT/;
}
push @new_rules, $rulestr;
} elsif ($logreject && $rulestr =~ /-j (DENY|DROP)$/) {
my $action = $1;
if ($rulestr =~ /^iptables\s+/) {
push @new_rules, $rulestr;
$rulestr =~ s/$action$/LOG/;
} elsif ($rulestr =~ /^ebtables\s+/) {
$rulestr =~ s/$action$/--log -j $action/;
}
push @new_rules, $rulestr;
} else {
push @new_rules, $rulestr;
}
}
@fwrules = @new_rules;
foreach my $rulestr (@fwrules) {
if ($rulestr =~ /^iptables\s+/) {
$upline .= " $rulestr || {\n";
$upline .= " echo 'WARNING: could not load iptables rule:'\n";
$upline .= " echo ' $rulestr'\n";
$upline .= " iptables -F\n";
$upline .= " exit 1\n";
$upline .= " }\n";
} elsif ($rulestr =~ /^ebtables\s+/) {
$upline .= " $rulestr || {\n";
$upline .= " echo 'WARNING: could not load ebtables rule:'\n";
$upline .= " echo ' $rulestr'\n";
$upline .= " ebtables -F\n";
$upline .= " exit 1\n";
$upline .= " }\n";
}
}
# This is a brute-force way to flush all ebtables and iptables
# rules, delete all custom chains, and restore all built-in
# chains to their default policies. This will produce errors
# since not all tables exist for both tools, and not every
# chain exists for all tables, so all output is sent to /dev/null.
for my $table (qw/filter nat mangle raw broute/) {
$downline .=
" iptables -t $table -F > /dev/null 2>&1 || true\n";
$downline .=
" iptables -t $table -X > /dev/null 2>&1 || true\n";
$downline .=
" ebtables -t $table -F > /dev/null 2>&1 || true\n";
$downline .=
" ebtables -t $table -X > /dev/null 2>&1 || true\n";
for my $chain (qw/INPUT OUTPUT FORWARD PREROUTING POSTROUTING BROUTING/) {
$downline .=
" iptables -t $table -P $chain ACCEPT > /dev/null 2>&1 || true\n";
$downline .=
" ebtables -t $table -P $chain ACCEPT > /dev/null 2>&1 || true\n";
}
}
return ($upline, $downline);
}
sub os_fwrouteconfig_line($$$)
......
......@@ -12,7 +12,8 @@ TBDB = @TBDBNAME@
MDOPTS = --compact --skip-extended-insert --no-create-info --skip-set-charset
FW_SCRIPTS = initfwvars.pl
FW_FILES = open.sql closed.sql basic.sql elabinelab.sql
FW_FILES = open.sql closed.sql basic.sql elabinelab.sql iptables-open.sql iptables-closed.sql \
iptables-basic.sql iptables-elabinelab.sql
include $(OBJDIR)/Makeconf
......@@ -24,6 +25,9 @@ all: $(FW_SCRIPTS) $(FW_FILES)
include $(TESTBED_SRCDIR)/GNUmakerules
iptables-%.sql: genconfig-iptables.pl
$(SRCDIR)/genconfig-iptables.pl -f $(SRCDIR)/iptables-fw-rules -M $* > $@
%.sql: genconfig.pl
$(SRCDIR)/genconfig.pl -f $(SRCDIR)/fw-rules -M $* > $@
......
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2005-2011 University of Utah and the Flux Group.
# All rights reserved.
#
use Getopt::Std;
use English;
my $datafile = "fw-rules";
my $optlist = "eMIf:";
my $domysql = 0;
my $doiptables = 1;
my $expand = 0;
my $qualifiers = 0;
my @lines;
sub usage()
{
print "Usage: genconfig-iptables [-MI] config ...\n".
" -e expand EMULAB_ variables\n".
" -f file specify the input rules file\n".
" -q include qualifiers\n".
" -M generate mysql commands\n".
" -I generate iptables commands\n".
"\n".
" Valid configs are: open, closed, basic, elabinelab\n";
exit(1);
}
my %fwvars;
sub getfwvars()
{
# XXX for Utah Emulab as of 11/11
$fwvars{EMULAB_GWIP} = "155.98.36.1";
$fwvars{EMULAB_GWMAC} = "00:d0:bc:f4:14:f8";
$fwvars{EMULAB_NS} = "155.98.32.70";
$fwvars{EMULAB_CNET} = "155.98.36.0/22";
$fwvars{EMULAB_BOSSES} = "boss,subboss";
$fwvars{EMULAB_SERVERS} = "boss,subboss,ops";
$fwvars{EMULAB_MCADDR} = "234.0.0.0/8";
$fwvars{EMULAB_MCPORT} = "1025-65535";
}
sub expandfwvars($)
{
my ($rule) = @_;
getfwvars() if (!%fwvars);
if ($rule =~ /EMULAB_\w+/) {
foreach my $key (keys %fwvars) {
$rule =~ s/$key/$fwvars{$key}/g
if (defined($fwvars{$key}));
}
if ($rule =~ /EMULAB_\w+/) {
warn("*** WARNING: Unexpanded firewall variable in: \n".
" $rule\n");
}
}
return $rule;
}
sub doconfig($)
{
my ($config) = @_;
my $ruleno = 1;
my ($type, $style, $enabled);
if ($doiptables) {
print "# $config\n";
print "iptables -F\n";
print "iptables -X\n";
}
if ($domysql) {
$type = "iptables-vlan";
$style = lc($config);
# XXX
$style = "emulab" if ($style eq "elabinelab");
$enabled = 1;
print "DELETE FROM `default_firewall_rules` WHERE ".
"type='$type' AND style='$style';\n";
}
foreach my $line (@lines) {
next if ($line !~ /#.*$config/);
next if ($line =~ /^#/);
if ($line =~ /#\s*(\d+):.*/) {
$ruleno = $1;
} else {
$ruleno++;
}
my $qual;
if ($line =~ /#.*\+(\w+)/) {
$qual = $1;
}
($rule = $line) =~ s/\s*#.*//;
chomp($rule);
$rule = expandfwvars($rule) if ($expand);
if ($doiptables) {
print "$rule # config=$config";
print ", $qual only)"
if ($qualifiers && $qual);
print "\n";
}
if ($domysql) {
if ($qualifiers) {
print "INSERT INTO `default_firewall_rules` VALUES (".
"'$type','$style',$enabled,$qual,$ruleno,'$rule');\n";
} else {
print "INSERT INTO `default_firewall_rules` VALUES (".
"'$type','$style',$enabled,$ruleno,'$rule');\n";
}
}
}
print "\n";
}
%options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"M"})) {
$domysql = 1;
$doiptables = 0;
}
if (defined($options{"I"})) {
$doiptables = 1;
$domysql = 0;
}
if (defined($options{"e"})) {
$expand = 1;
}
if (defined($options{"f"})) {
$datafile = $options{"f"};
}
if (defined($options{"q"})) {
$qualifiers = 1;
}
if (@ARGV == 0) {
usage();
}
@lines = `cat $datafile`;
foreach my $config (@ARGV) {
$config = uc($config);
doconfig($config);
}
exit(0);
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment