require "${General::swroot}/lang.pl";
require "/usr/lib/firewall/firewall-lib.pl";
require "${General::swroot}/location-functions.pl";
+require "${General::swroot}/ipblocklist-functions.pl";
# Set to one to enable debugging mode.
my $DEBUG = 0;
# MARK masks
my $NAT_MASK = 0x0f000000;
+# Country code, which is used to mark hostile networks.
+my $HOSTILE_CCODE = "XD";
+
my %fwdfwsettings=();
my %fwoptions = ();
my %defaultNetworks=();
my %locationsettings = (
"LOCATIONBLOCK_ENABLED" => "off"
);
+my %blocklistsettings= (
+ "ENABLE" => "off",
+);
-my @p2ps=();
+my %ipset_loaded_sets = ();
+my @ipset_used_sets = ();
my $configfwdfw = "${General::swroot}/firewall/config";
my $configinput = "${General::swroot}/firewall/input";
my $configoutgoing = "${General::swroot}/firewall/outgoing";
-my $p2pfile = "${General::swroot}/firewall/p2protocols";
my $locationfile = "${General::swroot}/firewall/locationblock";
my $configgrp = "${General::swroot}/fwhosts/customgroups";
my $netsettings = "${General::swroot}/ethernet/settings";
+my $blocklistfile = "${General::swroot}/ipblocklist/settings";
&General::readhash("${General::swroot}/firewall/settings", \%fwdfwsettings);
&General::readhash("${General::swroot}/optionsfw/settings", \%fwoptions);
&General::readhash("$locationfile", \%locationsettings);
}
+# Check if the ipblocklist settings file exits.
+if (-e "$blocklistfile") {
+ # Read-in settings file.
+ &General::readhash("$blocklistfile", \%blocklistsettings);
+}
+
# Get all available locations.
my @locations = &Location::Functions::get_locations();
+# Get all supported blocklists.
+my @blocklists = &IPblocklist::get_blocklists();
+
+# Name or the RED interface.
+my $RED_DEV = &General::get_red_interface();
+
my @log_limit_options = &make_log_limit_options();
my $POLICY_INPUT_ALLOWED = 0;
my $POLICY_FORWARD_ACTION = $fwoptions{"FWPOLICY"};
my $POLICY_OUTPUT_ACTION = $fwoptions{"FWPOLICY1"};
+#workaround to suppress a warning when a variable is used only once
+my @dummy = ( $Location::Functions::ipset_db_directory );
+undef (@dummy);
+
# MAIN
&main();
sub main {
+ # Get currently used ipset sets.
+ @ipset_used_sets = &ipset_get_sets();
+
# Flush all chains.
&flush();
- # Destroy all existing ipsets.
- run("$IPSET destroy");
-
# Prepare firewall rules.
if (! -z "${General::swroot}/firewall/input"){
&buildrules(\%configinputfw);
&buildrules(\%configfwdfw);
}
- # Load P2P block rules.
- &p2pblock();
-
# Load Location block rules.
&locationblock();
+ # Load rules to block hostile networks.
+ &drop_hostile_networks();
+
+ # Handle ipblocklist.
+ &ipblocklist();
+
# Reload firewall policy.
run("/usr/sbin/firewall-policy");
+ # Cleanup not longer needed ipset sets.
+ &ipset_cleanup();
+
#Reload firewall.local if present
if ( -f '/etc/sysconfig/firewall.local'){
run("/etc/sysconfig/firewall.local reload");
run("$IPTABLES -t nat -F $CHAIN_NAT_SOURCE");
run("$IPTABLES -t nat -F $CHAIN_NAT_DESTINATION");
run("$IPTABLES -t mangle -F $CHAIN_MANGLE_NAT_DESTINATION_FIX");
-
- # Flush LOCATIONBLOCK chain.
- run("$IPTABLES -F LOCATIONBLOCK");
}
sub buildrules {
$source = "";
}
+ # Make sure that $source is properly defined
+ next unless (defined $source);
+
my $source_intf = @$src[1];
foreach my $dst (@destinations) {
my @source_options = ();
if ($source =~ /mac/) {
push(@source_options, $source);
- } elsif ($source =~ /-m geoip/) {
+ } elsif ($source =~ /-m set/) {
+ # Split given arguments into single chunks to
+ # obtain the set name.
+ my ($a, $b, $c, $loc_src, $e) = split(/ /, $source);
+
+ # Call function to load the networks list for this country.
+ &ipset_restore($loc_src);
+
push(@source_options, $source);
} elsif($source) {
push(@source_options, ("-s", $source));
# Prepare destination options.
my @destination_options = ();
- if ($destination =~ /-m geoip/) {
+ if ($destination =~ /-m set/) {
+ # Split given arguments into single chunks to
+ # obtain the set name.
+ my ($a, $b, $c, $loc_dst, $e) = split(/ /, $destination);
+
+ # Call function to load the networks list for this country.
+ &ipset_restore($loc_dst);
+
push(@destination_options, $destination);
} elsif ($destination) {
push(@destination_options, ("-d", $destination));
return ($hrs * 60) + $min;
}
-sub p2pblock {
- open(FILE, "<$p2pfile") or die "Unable to read $p2pfile";
- my @protocols = ();
- foreach my $p2pentry (<FILE>) {
- my @p2pline = split(/\;/, $p2pentry);
- next unless ($p2pline[2] eq "off");
-
- push(@protocols, "--$p2pline[1]");
- }
- close(FILE);
-
- run("$IPTABLES -F P2PBLOCK");
- if (@protocols) {
- run("$IPTABLES -A P2PBLOCK -m ipp2p @protocols -j DROP");
- }
-}
-
sub locationblock {
- # The LOCATIONBLOCK chain now gets flushed by the flush() function.
+ # Flush LOCATIONBLOCK chain.
+ run("$IPTABLES -F LOCATIONBLOCK");
# If location blocking is not enabled, we are finished here.
if ($locationsettings{'LOCATIONBLOCK_ENABLED'} ne "on") {
# is enabled.
foreach my $location (@locations) {
if(exists $locationsettings{$location} && $locationsettings{$location} eq "on") {
- run("$IPTABLES -A LOCATIONBLOCK -m geoip --src-cc $location -j DROP");
+ # Call function to load the networks list for this country.
+ &ipset_restore($location);
+
+ # Call iptables and create rule to use the loaded ipset list.
+ run("$IPTABLES -A LOCATIONBLOCK -m set --match-set $location src -j DROP");
+ }
+ }
+}
+
+sub drop_hostile_networks () {
+ # Flush the HOSTILE firewall chain.
+ run("$IPTABLES -F HOSTILE");
+
+ # If dropping hostile networks is not enabled, we are finished here.
+ if ($fwoptions{'DROPHOSTILE'} ne "on") {
+ # Exit function.
+ return;
+ }
+
+ # Exit if there is no red interface.
+ return unless($RED_DEV);
+
+ # Call function to load the network list of hostile networks.
+ &ipset_restore($HOSTILE_CCODE);
+
+ # Check traffic in incoming/outgoing direction and drop if it matches
+ run("$IPTABLES -A HOSTILE -i $RED_DEV -m set --match-set $HOSTILE_CCODE src -j HOSTILE_DROP");
+ run("$IPTABLES -A HOSTILE -o $RED_DEV -m set --match-set $HOSTILE_CCODE dst -j HOSTILE_DROP");
+}
+
+sub ipblocklist () {
+ # Flush the ipblocklist chains.
+ run("$IPTABLES -F BLOCKLISTIN");
+ run("$IPTABLES -F BLOCKLISTOUT");
+
+ # Check if the blocklist feature is enabled.
+ if($blocklistsettings{'ENABLE'} eq "on") {
+ # Loop through the array of private networks.
+ foreach my $private_network (@PRIVATE_NETWORKS) {
+ # Create firewall rules to never block private networks.
+ run("$IPTABLES -A BLOCKLISTIN -p ALL -i $RED_DEV -s $private_network -j RETURN");
+ run("$IPTABLES -A BLOCKLISTOUT -p ALL -o $RED_DEV -d $private_network -j RETURN");
+ }
+ }
+
+ # Loop through the array of blocklists.
+ foreach my $blocklist (@blocklists) {
+ # Check if the blocklist feature and the current processed blocklist is enabled.
+ if(($blocklistsettings{'ENABLE'} eq "on") && ($blocklistsettings{$blocklist}) && ($blocklistsettings{$blocklist} eq "on")) {
+ # Call function to load the blocklist.
+ &ipset_restore($blocklist);
+
+ # Call function to check if the corresponding iptables drop chain already has been created.
+ if(&firewall_chain_exists("${blocklist}_DROP")) {
+ # Create iptables chain.
+ run("$IPTABLES -N ${blocklist}_DROP");
+ } else {
+ # Flush the chain.
+ run("$IPTABLES -F ${blocklist}_DROP");
+ }
+
+ # Check if logging is enabled.
+ if(($blocklistsettings{'LOGGING'}) && ($blocklistsettings{'LOGGING'} eq "on")) {
+ # Create logging rule.
+ run("$IPTABLES -A ${blocklist}_DROP -j LOG -m limit --limit 10/second --log-prefix \"BLKLST_$blocklist \"");
+ }
+
+ # Create Drop rule.
+ run("$IPTABLES -A ${blocklist}_DROP -j DROP");
+
+ # Add the rules to check against the set
+ run("$IPTABLES -A BLOCKLISTIN -p ALL -i $RED_DEV -m set --match-set $blocklist src -j ${blocklist}_DROP");
+ run("$IPTABLES -A BLOCKLISTOUT -p ALL -o $RED_DEV -m set --match-set $blocklist dst -j ${blocklist}_DROP");
+
+ # IP blocklist or the blocklist is disabled.
+ } else {
+ # Check if the blocklist related iptables drop chain exits.
+ unless(&firewall_chain_exists("${blocklist}_DROP")) {
+ # Flush the chain.
+ run("$IPTABLES -F ${blocklist}_DROP");
+
+ # Drop the chain.
+ run("$IPTABLES -X ${blocklist}_DROP");
+ }
}
}
}
return 0;
}
+
+sub firewall_chain_exists ($) {
+ my ($chain) = @_;
+
+ my $ret = &General::system("iptables", "--wait", "-n", "-L", "$chain");
+
+ return $ret;
+}
+
+sub ipset_get_sets () {
+ my @sets;
+
+ # Get all currently used ipset lists and store them in an array.
+ my @output = `$IPSET -n list`;
+
+ # Loop through the temporary array.
+ foreach my $set (@output) {
+ # Remove any newlines.
+ chomp($set);
+
+ # Add the set the array of used sets.
+ push(@sets, $set);
+ }
+
+ # Display used sets in debug mode.
+ if($DEBUG) {
+ print "Used ipset sets:\n";
+ print "@sets\n\n";
+ }
+
+ # Return the array of sets.
+ return @sets;
+}
+
+sub ipset_restore ($) {
+ my ($set) = @_;
+
+ # Empty variable to store the db file, which should be
+ # restored by ipset.
+ my $db_file;
+
+ # Check if the set already has been loaded.
+ if($ipset_loaded_sets{$set}) {
+ # It already has been loaded - so there is nothing to do.
+ return;
+ }
+
+ # Check if the given set name is a country code.
+ if($set ~~ @locations) {
+ # Libloc adds the IP type (v4 or v6) as part of the set and file name.
+ my $loc_set = "$set" . "v4";
+
+ # The bare filename equals the set name.
+ my $filename = $loc_set;
+
+ # Libloc uses "ipset" as file extension.
+ my $file_extension = "ipset";
+
+ # Generate full path and filename for the ipset db file.
+ my $db_file = "$Location::Functions::ipset_db_directory/$filename.$file_extension";
+
+ # Call function to restore/load the set.
+ &ipset_call_restore($db_file);
+
+ # Check if the set is already loaded (has been used before).
+ if ($set ~~ @ipset_used_sets) {
+ # The sets contains the IP type (v4 or v6) as part of the name.
+ # The firewall rules matches against sets without that extension. So we safely
+ # can swap or rename the sets to use the new ones.
+ run("$IPSET swap $loc_set $set");
+ } else {
+ # If the set is not loaded, we have to rename it to proper use it.
+ run("$IPSET rename $loc_set $set");
+ }
+
+ # Check if the given set name is a blocklist.
+ } elsif ($set ~~ @blocklists) {
+ # IPblocklist sets contains v4 as setname extension.
+ my $set_name = "$set" . "v4";
+
+ # Get the database file for the given blocklist.
+ my $db_file = &IPblocklist::get_ipset_db_file($set);
+
+ # Call function to restore/load the set.
+ &ipset_call_restore($db_file);
+
+ # Check if the set is already loaded (has been used before).
+ if ($set ~~ @ipset_used_sets) {
+ # Swap the sets.
+ run("$IPSET swap $set_name $set");
+ } else {
+ # Rename the set to proper use it.
+ run("$IPSET rename $set_name $set");
+ }
+ }
+
+ # Store the restored set to the hash to prevent from loading it again.
+ $ipset_loaded_sets{$set} = "1";
+}
+
+sub ipset_call_restore ($) {
+ my ($file) = @_;
+
+ # Check if the requested file exists.
+ if (-f $file) {
+ # Run ipset and restore the given set.
+ run("$IPSET restore -f $file");
+ }
+}
+
+sub ipset_cleanup () {
+ # Reload the array of used sets.
+ @ipset_used_sets = &ipset_get_sets();
+
+ # Loop through the array of used sets.
+ foreach my $set (@ipset_used_sets) {
+ # Check if this set is still in use.
+ #
+ # In this case an entry in the loaded sets hash exists.
+ unless($ipset_loaded_sets{$set}) {
+ # Entry does not exist, so this set is not longer
+ # used and can be destroyed.
+ run("$IPSET destroy $set");
+ }
+ }
+}