]> git.ipfire.org Git - ipfire-2.x.git/blobdiff - config/firewall/rules.pl
firewall: Make blocking all traffic impossible on HOSTILE
[ipfire-2.x.git] / config / firewall / rules.pl
index 927c1f2ba00028406fb257d0e731479d5b1b72d6..d713049867f4ee7a9cb198cc85cf8225dd7eadf5 100644 (file)
@@ -59,6 +59,9 @@ my @PRIVATE_NETWORKS = (
 # 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=();
@@ -70,7 +73,8 @@ my %confignatfw=();
 my %locationsettings = (
        "LOCATIONBLOCK_ENABLED" => "off"
 );
-my %loaded_ipset_lists=();
+my %ipset_loaded_sets = ();
+my @ipset_used_sets = ();
 
 my $configfwdfw                = "${General::swroot}/firewall/config";
 my $configinput            = "${General::swroot}/firewall/input";
@@ -96,6 +100,9 @@ if (-e "$locationfile") {
 # Get all available locations.
 my @locations = &Location::Functions::get_locations();
 
+# 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;
@@ -114,12 +121,12 @@ undef (@dummy);
 &main();
 
 sub main {
+       # Get currently used ipset 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);
@@ -134,9 +141,15 @@ sub main {
        # Load Location block rules.
        &locationblock();
 
+       # Load rules to block hostile networks.
+       &drop_hostile_networks();
+
        # 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");
@@ -189,9 +202,6 @@ sub flush {
        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 {
@@ -639,7 +649,8 @@ sub time_convert_to_minutes {
 }
 
 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") {
@@ -669,11 +680,32 @@ sub locationblock {
                        &ipset_restore($location);
 
                        # Call iptables and create rule to use the loaded ipset list.
-                       run("$IPTABLES -A LOCATIONBLOCK -m set --match-set CC_$location src -j DROP");
+                       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 get_protocols {
        my $hash = shift;
        my $key = shift;
@@ -887,24 +919,92 @@ sub firewall_is_in_subnet {
        return 0;
 }
 
+sub ipset_get_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(@ipset_used_sets, $set);
+       }
+
+       # Display used sets in debug mode.
+       if($DEBUG) {
+               print "Used ipset sets:\n";
+               print "@ipset_used_sets\n\n";
+       }
+}
+
 sub ipset_restore ($) {
-       my ($list) = @_;
+       my ($set) = @_;
 
-       my $file_prefix = "ipset4";
-       my $db_file = "$Location::Functions::ipset_db_directory/$list.$file_prefix";
+       # Empty variable to store the db file, which should be
+       # restored by ipset.
+       my $db_file;
 
-       # Check if the network list already has been loaded.
-       if($loaded_ipset_lists{$list}) {
+       # 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 generated file exists.
-       if (-f $db_file) {
-               # Run ipset and restore the list of the given country code.
-               run("$IPSET restore < $db_file");
+       # 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");
+               }
+       }
+
+       # Store the restored set to the hash to prevent from loading it again.
+       $ipset_loaded_sets{$set} = "1";
+}
+
+sub ipset_call_restore ($) {
+       my ($file) = @_;
 
-               # Store the restored list name to the hash to prevent from loading it again.
-               $loaded_ipset_lists{$list} = "1";
+       # Check if the requested file exists.
+       if (-f $file) {
+               # Run ipset and restore the given set.
+               run("$IPSET restore -f $file");
+       }
+}
+
+sub ipset_cleanup () {
+       # 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");
+               }
        }
 }