Firewall

Vedi anche

  • A collection of scripts I have written and/or adapted that I currently use on my systems as automated tasks 1

  • Simple shell script for GNU/Linux, built on iptables, which is able to filter incoming packets based on accepted port numbers and countries. It is aimed to SOHO users. 2

  • Linux Iptables Just Block By Country - nixCraft 3

  • Simple stateful firewall - ArchWiki 4

  • iptables - ArchWiki 5

  • 25 Most Frequently Used Linux IPTables Rules Examples 6

Use iptables to block IP addresses by country for inbound ports on a server. This is a whitelist: what is not explicitly allowed is blocked. This is useful, for example, to filter most SSH bruteforce attacks while leaving a webserver freely available.

Nota

This is certainly not the most efficient approach at filtering: you can easily end with thousands of iptables rules that need to be scanned one by one!

Setup

Run as user

Instruction number

root

*

  1. install the dependencies

    apt-get install iptables python3-yaml python3-requests iptables-persistent
    
  2. answer Yes to all questions

  3. install fpyutils. See reference

  4. create the jobs directories. See reference

    mkdir -p /home/jobs/{scripts,services}/by-user/root
    chmod 700 -R /home/jobs/{scripts,services}/by-user/root
    
  5. create the script

    /home/jobs/scripts/by-user/root/iptables_geoport.py
      1#!/usr/bin/env python3
      2#
      3# iptables_geoport.py
      4#
      5# Copyright (C) 2020-2022 Franco Masotti (franco \D\o\T masotti {-A-T-} tutanota \D\o\T com)
      6#
      7# This program is free software; you can redistribute it and/or modify
      8# it under the terms of the GNU General Public License as published by
      9# the Free Software Foundation; either version 2 of the License, or
     10# (at your option) any later version.
     11#
     12# This program is distributed in the hope that it will be useful,
     13# but WITHOUT ANY WARRANTY; without even the implied warranty of
     14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     15# GNU General Public License for more details.
     16#
     17# You should have received a copy of the GNU General Public License along
     18# with this program; if not, write to the Free Software Foundation, Inc.,
     19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
     20
     21# OLD NOTICES
     22# ===========
     23# See url for more info - http://www.cyberciti.biz/faq/?p=3402
     24# Author: nixCraft <www.cyberciti.biz> under GPL v.2.0+
     25# Post Author: frnmst (Franco Masotti) (franco \D\o\T masotti {-A-T-} tutanota \D\o\T com)
     26# New version heavily based on https://wiki.archlinux.org/index.php/Simple_stateful_firewall
     27#   https://wiki.archlinux.org/index.php/Iptables
     28#   and a little on http://www.thegeekstuff.com/2011/06/iptables-rules-examples/ as well as nixCraft for the bash stuff.
     29# ===========
     30r"""iptables_geoport.py."""
     31
     32import copy
     33import ipaddress
     34import os
     35import pathlib
     36import shlex
     37import sys
     38import urllib
     39
     40import fpyutils
     41import requests
     42import yaml
     43
     44
     45class UserNotRoot(Exception):
     46    r"""The user running the script is not root."""
     47
     48
     49##################
     50# Basic commands #
     51##################
     52
     53
     54def reset_rules():
     55    r"""Reset the chains and the tables."""
     56    # https://wiki.archlinux.org/index.php/Iptables#Resetting_rules
     57    #
     58    # Copyright (C) 2020 Arch Wiki contributors.
     59    # Permission is granted to copy, distribute and/or modify this document
     60    # under the terms of the GNU Free Documentation License, Version 1.3
     61    # or any later version published by the Free Software Foundation;
     62    # with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
     63    # A copy of the license is included in the section entitled "GNU
     64    # Free Documentation License".
     65    commands = dict()
     66    commands['flush_tcp_chain'] = 'iptables --flush TCP'
     67    commands['flush_udp_chain'] = 'iptables --flush UDP'
     68    commands['flush_input_chain'] = 'iptables --flush INPUT'
     69    commands['flush_output_chain'] = 'iptables --flush OUTPUT'
     70    commands['flush_logging_chain'] = 'iptables --flush LOGGING'
     71
     72    commands['delete_tcp_chain'] = 'iptables --delete-chain TCP'
     73    commands['delete_udp_chain'] = 'iptables --delete-chain UDP'
     74    commands['delete_logging_chain'] = 'iptables --delete-chain LOGGING'
     75
     76    commands['flush_mangle_table'] = 'iptables --table mangle --flush'
     77    commands['delete_mangle_chain'] = 'iptables --table mangle --delete-chain'
     78    commands['flush_raw_table'] = 'iptables --table raw --flush'
     79    commands['delete_raw_chain'] = 'iptables --table raw --delete-chain'
     80    commands['flush_security_table'] = 'iptables --table security --flush'
     81    commands['delete_security_chain'] = 'iptables --table security --delete-chain'
     82    commands['accept_input'] = 'iptables --policy INPUT ACCEPT'
     83    commands['accept_forward'] = 'iptables --policy FORWARD ACCEPT'
     84    commands['accept_output'] = 'iptables --policy OUTPUT ACCEPT'
     85
     86    # sys._getframe().f_code.co_name is the function name.
     87    fix_dict_keys(commands, sys._getframe().f_code.co_name, '__')
     88    return commands
     89
     90
     91def initialize_basic_chains() -> dict:
     92    r"""Apply some basic rules for a single machine."""
     93    # https://wiki.archlinux.org/index.php/Simple_stateful_firewall#Firewall_for_a_single_machine
     94    #
     95    # Copyright (C) 2020 Arch Wiki contributors.
     96    # Permission is granted to copy, distribute and/or modify this document
     97    # under the terms of the GNU Free Documentation License, Version 1.3
     98    # or any later version published by the Free Software Foundation;
     99    # with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
    100    # A copy of the license is included in the section entitled "GNU
    101    # Free Documentation License".
    102    commands = dict()
    103
    104    # Output traffic is NOT filtered.
    105    commands['output_chain'] = 'iptables --policy OUTPUT ACCEPT'
    106
    107    # Create two user defined chains that will define tcp an udp protocol rules later.
    108    commands['tcp_chain'] = 'iptables --new-chain TCP'
    109    commands['udp_chain'] = 'iptables --new-chain UDP'
    110
    111    # For a single machine, however, we simply set the policy of the FORWARD chain to DROP and move on
    112    commands['drop_forward'] = 'iptables --policy FORWARD DROP'
    113
    114    # The first rule added to the INPUT chain will allow traffic that belongs
    115    # to established connections, or new valid traffic that is related to these
    116    # connections such as ICMP errors, or echo replies.
    117    commands[
    118        'allow_realted_established'] = 'iptables --append INPUT --match conntrack --ctstate RELATED,ESTABLISHED --jump ACCEPT'
    119
    120    # loopback interface INPUT traffic enabled for ping and debugging stuff.
    121    commands[
    122        'loopback'] = 'iptables --append INPUT --in-interface lo --jump ACCEPT'
    123
    124    # Drop all invalid INPUT (i.e. damaged) packets.
    125    # To do this connection must be tracked (conntrack)
    126    # and connection state (cstate) is set to INVALID.
    127    commands[
    128        'invalid_input'] = 'iptables --append INPUT --match conntrack --ctstate INVALID --jump DROP'
    129
    130    # Allow icmp type 8 (i.e. ping) to all interfaces.
    131    commands[
    132        'ping'] = 'iptables --append INPUT --protocol icmp --icmp-type 8 --match conntrack --ctstate NEW --jump ACCEPT'
    133
    134    # TCP snd UDP chains are connected to INPUT chains.
    135    # These two user-defined chains will manage the ports.
    136    # Remember that tcp uses SYN to initialize a connection, unlike UDP
    137    commands[
    138        'connect_tcp_chain'] = 'iptables --append INPUT --protocol tcp --syn -m conntrack --ctstate NEW --jump TCP'
    139    commands[
    140        'connect_udp_chain'] = 'iptables --append INPUT --protocol udp -m conntrack --ctstate NEW --jump UDP'
    141
    142    fix_dict_keys(commands, sys._getframe().f_code.co_name, '__')
    143    return commands
    144
    145
    146def initialize_logging_chain() -> dict:
    147    r"""Create the logging chain."""
    148    # https://wiki.archlinux.org/index.php/Iptables#Logging
    149    #
    150    # Copyright (C) 2020 Arch Wiki contributors.
    151    # Permission is granted to copy, distribute and/or modify this document
    152    # under the terms of the GNU Free Documentation License, Version 1.3
    153    # or any later version published by the Free Software Foundation;
    154    # with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
    155    # A copy of the license is included in the section entitled "GNU
    156    # Free Documentation License".
    157    commands = dict()
    158
    159    commands['logging_chain'] = 'iptables --new-chain LOGGING'
    160    commands[
    161        'connect_logging_chain'] = 'iptables --append INPUT --jump LOGGING'
    162    commands[
    163        'logging_limit'] = 'iptables --append LOGGING --match limit --limit 2/hour --limit-burst 10 --jump LOG'
    164
    165    fix_dict_keys(commands, sys._getframe().f_code.co_name, '__')
    166    return commands
    167
    168
    169def initialize_blocking_rules(drop_packets: bool = True,
    170                              logging: bool = True) -> dict:
    171    r"""Initialize blocking rules."""
    172    # https://wiki.archlinux.org/index.php/Simple_stateful_firewall#Firewall_for_a_single_machine
    173    #
    174    # Copyright (C) 2020 Arch Wiki contributors.
    175    # Permission is granted to copy, distribute and/or modify this document
    176    # under the terms of the GNU Free Documentation License, Version 1.3
    177    # or any later version published by the Free Software Foundation;
    178    # with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
    179    # A copy of the license is included in the section entitled "GNU
    180    # Free Documentation License".
    181    commands = dict()
    182
    183    if logging:
    184        chain = 'LOGGING'
    185    else:
    186        chain = 'INPUT'
    187    if drop_packets:
    188        # Drop everything.
    189        commands['drop'] = 'iptables --append ' + chain + ' --jump DROP'
    190    else:
    191        # RFC compilant.
    192        commands[
    193            'rfc_tcp'] = 'iptables --append ' + chain + ' --protocol tcp --jump REJECT --reject-with tcp-rst'
    194        commands[
    195            'rfc_udp'] = 'iptables --append ' + chain + ' --protocol udp --jump REJECT --reject-with icmp-port-unreachable'
    196        # Other protocols are usually not used, so REJECT those packets with icmp-proto-unreachable.
    197        commands[
    198            'proto_unreachable'] = 'iptables --append ' + chain + ' --jump REJECT --reject-with icmp-proto-unreachable'
    199
    200    fix_dict_keys(commands, sys._getframe().f_code.co_name, '__')
    201    return commands
    202
    203
    204def set_accepted_rules(ports: dict, accepted_ips: dict) -> dict:
    205    r"""Set accepted rules."""
    206    assert_input_ports_struct(ports)
    207    assert_accepted_ips_struct(accepted_ips)
    208
    209    commands = dict()
    210    # O(ports*chains*ips) <= O(ips^3) because of how this script works.
    211    i = 0
    212    for port in ports:
    213        source = ports[port]['source']
    214        ips = accepted_ips[source]
    215        chains, protocols = get_chains_and_protocols(ports[port]['protocol'])
    216        for w, chain in enumerate(chains):
    217            for ip in ips:
    218                commands[str(i)] = generate_accepted_rule_command(
    219                    chain, protocols[w], port, ip)
    220                i += 1
    221
    222    fix_dict_keys(commands, sys._getframe().f_code.co_name, '__')
    223    return commands
    224
    225
    226def set_patch_rules(rules: list) -> dict:
    227    r"""Pass raw commands directly."""
    228    for r in rules:
    229        if not isinstance(r, str):
    230            raise TypeError
    231
    232    commands = dict()
    233    for i, rule in enumerate(rules):
    234        commands[str(i)] = rule
    235
    236    fix_dict_keys(commands, sys._getframe().f_code.co_name, '__')
    237    return commands
    238
    239
    240def initialize_drop_rules() -> dict:
    241    r"""Initialize drop rules."""
    242    commands = dict()
    243    commands['drop'] = 'iptables --policy INPUT DROP'
    244
    245    fix_dict_keys(commands, sys._getframe().f_code.co_name, '__')
    246    return commands
    247
    248
    249#########
    250# Utils #
    251#########
    252
    253
    254def get_chains_and_protocols(protocol: str) -> tuple:
    255    r"""Compute the iptables chain and protocol values."""
    256    if protocol not in ['tcp', 'udp', 'all']:
    257        raise ValueError
    258
    259    chains = list()
    260    protocols = list()
    261    if protocol == 'tcp':
    262        chains.append('TCP')
    263        protocols.append('tcp')
    264    elif protocol == 'udp':
    265        chains.append('UDP')
    266        protocols.append('udp')
    267    elif protocol == 'all':
    268        chains.append('TCP')
    269        chains.append('UDP')
    270        protocols.append('tcp')
    271        protocols.append('udp')
    272
    273    return chains, protocols
    274
    275
    276def generate_accepted_rule_command(chain: str, protocol: str, port: str,
    277                                   remote_ip: str) -> str:
    278    r"""Generate a single command for the accepted rules."""
    279    check_port(port)
    280    check_ip_address(remote_ip)
    281
    282    return ('iptables --append ' + chain + ' --protocol ' + protocol +
    283            ' --dport ' + port + ' --source ' + remote_ip + ' --jump ACCEPT')
    284
    285
    286def fix_dict_keys(dictionary: dict, prefix: str, separator: str):
    287    r"""Fix the keys of a dictionary by adding a prefix and separator."""
    288    d = copy.deepcopy(dictionary)
    289    for key in d:
    290        dictionary[prefix + separator + key] = d[key]
    291        del dictionary[key]
    292
    293
    294def load_zone_file(zone_file: str) -> list:
    295    r"""Load zone file."""
    296    zones = list()
    297    with open(zone_file, 'r') as f:
    298        line = f.readline().strip()
    299        while line:
    300            check_ip_address(line)
    301            zones.append(line)
    302            line = f.readline().strip()
    303
    304    return zones
    305
    306
    307def load_zone_files(zone_files: list) -> list:
    308    r"""Load all the zone files content in a flat data structure."""
    309    for zf in zone_files:
    310        if not isinstance(zf, str):
    311            raise TypeError
    312
    313    zones = list()
    314    for zf in zone_files:
    315        zones += load_zone_file(zf)
    316
    317    return zones
    318
    319
    320def get_filename_from_url(url: str) -> str:
    321    r"""Use some tricks to get the filemame from a URL."""
    322    return pathlib.PurePath(urllib.parse.urlparse(url).path).name
    323
    324
    325def download_zone_file(url: str, dst_directory: str) -> str:
    326    r"""Save the zone file."""
    327    filename = get_filename_from_url(url)
    328    pathlib.Path(dst_directory).mkdir(mode=0o700, parents=True, exist_ok=True)
    329    full_path = str(pathlib.Path(dst_directory, filename))
    330    try:
    331        r = requests.get(url)
    332        with open(full_path, 'w') as f:
    333            f.write(r.text)
    334    except requests.ConnectionError:
    335        pass
    336
    337    return full_path
    338
    339
    340def download_zone_files(urls: list, cache_directory: str) -> list:
    341    r"""Download multiple zone files."""
    342    for u in urls:
    343        if not isinstance(u, str):
    344            raise TypeError
    345
    346    files = list()
    347    for u in urls:
    348        files.append(download_zone_file(u, cache_directory))
    349
    350    return files
    351
    352
    353def update_accepted_ips_structure(accepted_ips: dict, zones: list):
    354    r"""Update some data structures."""
    355    assert_accepted_ips_struct(accepted_ips)
    356    assert_zones_struct(zones)
    357
    358    accepted_ips['wan'] = zones
    359    accepted_ips['all'] = accepted_ips['lan'] + accepted_ips['wan']
    360
    361
    362def get_packet_policy(invalid_packet_policy: str) -> bool:
    363    r"""Update a variable."""
    364    if invalid_packet_policy not in ['polite', 'rude']:
    365        raise ValueError
    366
    367    drop = True
    368    if invalid_packet_policy == 'rude':
    369        drop = True
    370    elif invalid_packet_policy == 'polite':
    371        drop = False
    372    return drop
    373
    374
    375##############
    376# Assertions #
    377##############
    378
    379
    380def check_ip_address(ip: str):
    381    r"""Verify that we are dealing with a network address."""
    382    ipaddress.ip_network(ip, strict=True)
    383
    384
    385def check_port(port: str):
    386    r"""Check that the input port is a valid port number."""
    387    if not port.isdigit():
    388        raise TypeError
    389    if not (int(port) >= 0 and int(port) <= (2**16) - 1):
    390        raise ValueError
    391
    392
    393def assert_zones_struct(zones: list):
    394    r"""Check that the data structure is a list of ip addresses."""
    395    for z in zones:
    396        if not isinstance(z, str):
    397            raise TypeError
    398        check_ip_address(z)
    399
    400
    401def assert_accepted_ips_struct(accepted_ips: dict):
    402    r"""Check that the data structure is a list of ip addresses."""
    403    for ips in accepted_ips:
    404        if not isinstance(accepted_ips[ips], list):
    405            raise TypeError
    406        for j in accepted_ips[ips]:
    407            if not isinstance(j, str):
    408                raise TypeError
    409            check_ip_address(j)
    410
    411
    412def assert_input_ports_struct(ports: dict):
    413    r"""Check that the data structure is correct."""
    414    for port in ports:
    415        check_port(port)
    416        if not isinstance(ports[port], dict):
    417            raise TypeError
    418        if 'source' not in ports[port]:
    419            raise ValueError
    420        if 'protocol' not in ports[port]:
    421            raise ValueError
    422        if ports[port]['source'] not in ['lan', 'wan', 'all']:
    423            raise ValueError
    424
    425
    426############
    427# Pipeline #
    428############
    429
    430if __name__ == '__main__':
    431    if os.getuid() != 0:
    432        raise UserNotRoot
    433
    434    # Load the configuration.
    435    configuration_file = shlex.quote(sys.argv[1])
    436    config = yaml.load(open(configuration_file, 'r'), Loader=yaml.SafeLoader)
    437    dry_run = config['dry_run']
    438    cache_directory = config['cache_directory']
    439    zone_files = config['accepted_ips']['wan']
    440    accepted_ips = dict()
    441    accepted_ips['lan'] = config['accepted_ips']['lan']
    442    accepted_ips['wan'] = list()
    443    patch_rules = config['patch_rules']
    444    set_patch_rules_first = config['set_patch_rules_first']
    445    input_ports = config['input_ports']
    446    logging = config['logging_enabled']
    447    invalid_packet_policy = config['invalid_packet_policy']
    448    drop_packets = get_packet_policy(invalid_packet_policy)
    449
    450    # Get the data.
    451    zones = load_zone_files(download_zone_files(zone_files, cache_directory))
    452    update_accepted_ips_structure(accepted_ips, zones)
    453
    454    # Apply the rules.
    455    reset = reset_rules()
    456    basic_chains = initialize_basic_chains()
    457    logging_chain = initialize_logging_chain()
    458    blocking_rules = initialize_blocking_rules(drop_packets, logging)
    459    rules = set_accepted_rules(input_ports, accepted_ips)
    460    patch = set_patch_rules(patch_rules)
    461    drop_by_default = initialize_drop_rules()
    462
    463    # Merge the rules.
    464    if set_patch_rules_first:
    465        commands = {
    466            **reset,
    467            **basic_chains,
    468            **logging_chain,
    469            **blocking_rules,
    470            **patch,
    471            **rules,
    472            **drop_by_default
    473        }
    474    else:
    475        commands = {
    476            **reset,
    477            **basic_chains,
    478            **logging_chain,
    479            **blocking_rules,
    480            **rules,
    481            **patch,
    482            **drop_by_default
    483        }
    484
    485    # Apply the rules.
    486    for c in commands:
    487        fpyutils.shell.execute_command_live_output(commands[c],
    488                                                   dry_run=dry_run)
    
  6. create a configuration file

    /home/jobs/scripts/by-user/root/iptables_geoport.yaml
     1#
     2# iptables_geoport.yaml
     3#
     4# Copyright (C) 2020-2022 Franco Masotti (franco \D\o\T masotti {-A-T-} tutanota \D\o\T com)
     5#
     6# This program is free software; you can redistribute it and/or modify
     7# it under the terms of the GNU General Public License as published by
     8# the Free Software Foundation; either version 2 of the License, or
     9# (at your option) any later version.
    10#
    11# This program is distributed in the hope that it will be useful,
    12# but WITHOUT ANY WARRANTY; without even the implied warranty of
    13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14# GNU General Public License for more details.
    15#
    16# You should have received a copy of the GNU General Public License along
    17# with this program; if not, write to the Free Software Foundation, Inc.,
    18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
    19
    20# If set to true print the commands that would be executed.
    21dry_run: true
    22
    23logging_enabled: true
    24
    25# {rude,polite}
    26invalid_packet_policy: 'rude'
    27
    28# The path where the ip addresses list will be saved.
    29cache_directory: './.cache'
    30
    31# Have a look at https://www.ipdeny.com/ipblocks/
    32accepted_ips:
    33  wan:
    34    - 'https://www.ipdeny.com/ipblocks/data/countries/it.zone'
    35  lan:
    36    - '192.168.0.0/24'
    37
    38# If set to 'true' the "patch rules" will be applied before the "input ports" rules.
    39set_patch_rules_first: true
    40
    41# Raw rules that override the default ones.
    42# Use "[]" if you do not need patch rules.
    43patch_rules:
    44  - 'iptables -A TCP -p tcp --dport 80 -j ACCEPT'
    45  - 'iptables -A TCP -p tcp --dport 443 -j ACCEPT'
    46
    47  # SSH LAN only.
    48  - 'iptables -A TCP -s 192.168.0.0/24 -p tcp -m tcp --dport 22 -j ACCEPT'
    49
    50# source:       {lan,wan,all}
    51# protocol:     {tcp,udp,all}
    52input_ports:
    53  '2222':
    54    source:     'lan'
    55    protocol:   'tcp'
    56  '2223':
    57    source:     'lan'
    58    protocol:   'tcp'
    59  '5555':
    60    source:     'lan'
    61    protocol:   'tcp'
    62  '53':
    63    source:     'lan'
    64    protocol:   'all'
    65  '631':
    66    source:     'lan'
    67    protocol:   'tcp'
    68  '8100':
    69    source:     'lan'
    70    protocol:   'tcp'
    71
    72  # Required for SANE.
    73  '6566':
    74    source:     'lan'
    75    protocol:   'all'
    76
    77  # Move SSH rules here for performance reasons.
    78  '22':
    79    source:     'wan'
    80    protocol:   'tcp'
    

    Nota

    Rules are scanned sequentially. Move frequently used rules upper in the file to improve performance.

  7. use this Systemd service unit file

    /home/jobs/services/by-user/root/iptables-geoport.service
     1[Unit]
     2Description=Apply the iptables geoport rules
     3Wants=network-online.target
     4After=network-online.target
     5Requires=netfilter-persistent.service
     6After=netfilter-persistent.service
     7
     8[Service]
     9Type=simple
    10# Answer Yes (default value) to dpkg-reconfigure.
    11ExecStart=/usr/bin/bash -c '/home/jobs/scripts/by-user/root/iptables_geoport.py /home/jobs/scripts/by-user/root/iptables_geoport.yaml && [ -f /sbin/dpkg-reconfigure ] && dpkg-reconfigure --frontend noninteractive iptables-persistent'
    12
    13User=root
    14Group=root
    

SANE

Vedi anche

  • SANE - ArchWiki 8

  • Secure use of iptables and connection tracking helpers 7

Run as user

Instruction number

root

*

To be able to use a remote scanner with SANE using this setup you need to follow these steps

  1. open TCP and UDP ports 6566

    /home/jobs/scripts/by-user/root/iptables_geoport.yaml (extract)
    65  '631':
    66    source:     'lan'
    67    protocol:   'tcp'
    68  '8100':
    69    source:     'lan'
    70    protocol:   'tcp'
    71
    72  # Required for SANE.
    73  '6566':
    74    source:     'lan'
    75    protocol:   'all'
    76
    77  # Move SSH rules here for performance reasons.
    78  '22':
    79    source:     'wan'
    80    protocol:   'tcp'
    
  2. add this file to load the kernel module

    /etc/modprobe.d/nf_conntrack.conf
    1options nf_conntrack nf_conntrack_helper=1
    
  3. add this file to load the kernel module

    /etc/modules-load.d/nf_conntrack_sane.conf
    1nf_conntrack_sane
    
  4. reboot

  5. check if the rules are active: 1 should be returned by the cat command

    cat /proc/sys/net/netfilter/nf_conntrack_helper
    1
    

Footnotes

1

https://software.franco.net.eu.org/frnmst/automated-tasks GNU GPLv3+, copyright (c) 2019-2022, Franco Masotti

2

https://software.franco.net.eu.org/frnmst-archives/iptables-geoport-directives GNU GPLv3+ and GNU GPLv2+, copyright (c) 2015 Franco Masotti

3

http://www.cyberciti.biz/faq/?p=3402 GPL v.2.0+, copyright (c) Author: nixCraft <www.cyberciti.biz>

4

https://wiki.archlinux.org/index.php/Simple_stateful_firewall GNU Free Documentation License 1.3 or late, copyright (c) ArchWiki contributor

5

https://wiki.archlinux.org/index.php/Iptables GNU Free Documentation License 1.3 or late, copyright (c) ArchWiki contributor

6

http://www.thegeekstuff.com/2011/06/iptables-rules-examples/ unknown license

7

https://wiki.archlinux.org/title/SANE#Firewall GNU Free Documentation License 1.3 or late, copyright (c) ArchWiki contributor

8

https://home.regit.org/wp-content/uploads/2011/11/secure-conntrack-helpers.html unknown license