diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..85ed0ea
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,144 @@
+Pipfile*
+# Created by https://www.gitignore.io/api/vim,python
+# Edit at https://www.gitignore.io/?templates=vim,python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+### Python Patch ###
+.venv/
+
+### Vim ###
+# Swap
+[._]*.s[a-v][a-z]
+[._]*.sw[a-p]
+[._]s[a-rt-v][a-z]
+[._]ss[a-gi-z]
+[._]sw[a-p]
+
+# Session
+Session.vim
+
+# Temporary
+.netrwhist
+*~
+# Auto-generated tag files
+tags
+# Persistent undo
+[._]*.un~
+
+# End of https://www.gitignore.io/api/vim,python
diff --git a/firewall.nft b/firewall.nft
index 16eb0f3..a3d3c1b 100644
--- a/firewall.nft
+++ b/firewall.nft
@@ -73,10 +73,4 @@ table inet firewall {
}
table nat {
-# TODO : on met 27 Ips internes par ip publiques
-# Users : 193.48.225.10-119
-# Prerezotage 193.48.225.240-254
-# Aloes : 193.48.225.120-129
-# Federez : 193.48.225.130-199
-# Server : 193.48.225.200-209
}
diff --git a/firewall.py b/firewall.py
index 621362a..b81ed93 100644
--- a/firewall.py
+++ b/firewall.py
@@ -89,12 +89,35 @@ class Parser:
return netaddr.EUI(mac, dialect=netaddr.mac_unix_expanded)
@staticmethod
def IPv4(ip):
- """Check an IPv4 validity."""
- return netaddr.IPAddress(ip, version=4)
+ """Check an IPv4 validity.
+
+ Args:
+ ip: can either be a tuple (in this case returns an IPRange), a
+ single IP address or a IP Network.
+ """
+ if isinstance(ip, tuple):
+ begin, end = ip
+ return netaddr.IPRange(begin, end, version=4)
+ try:
+ return netaddr.IPAddress(ip, version=4)
+ except ValueError:
+ return netaddr.IPNetwork(ip, version=4)
@staticmethod
def IPv6(ip):
- """Check a IPv6 validity."""
- return netaddr.IPAddress(ip, version=6)
+ """Check a IPv6 validity.
+
+ Args:
+ ip: can either be a tuple (in this case returns an IPRange), a
+ single IP address or a IP Network.
+ """
+ if isinstance(ip, tuple):
+ begin, end = ip
+ return netaddr.IPRange(begin, end, version=6)
+ try:
+ return netaddr.IPAddress(ip, version=6)
+ except ValueError:
+ return netaddr.IPNetwork(ip, version=6)
+
@staticmethod
def protocol(protocol):
"""Check a protocol validity."""
@@ -120,13 +143,17 @@ class NetfilterSet:
ADDRESS_FAMILIES = {'ip', 'ip6', 'inet', 'arp', 'bridge', 'netdev'}
+ FLAGS = {'constant', 'interval', 'timeout'}
+
def __init__(self,
name,
type_, # e.g.: ('MAC', 'IPv4')
target_content=None,
use_sudo=True,
address_family='inet', # Manage both IPv4 and IPv6.
- table_name='filter'):
+ table_name='filter',
+ flags = []
+ ):
self.name = name
self.content = set()
# self.type
@@ -135,6 +162,7 @@ class NetfilterSet:
# self.address_family
self.set_address_family(address_family)
self.table = table_name
+ self.set_flags(flags)
sudo = ["/usr/bin/sudo"] * int(bool(use_sudo))
self.nft = [*sudo, "/usr/sbin/nft"]
if target_content:
@@ -167,6 +195,13 @@ class NetfilterSet:
'Invalid address_family: "{}".'.format(address_family))
self.address_family = address_family
+ def set_flags(self, flags_):
+ """Check set flags validity before saving them."""
+ for f in flags_:
+ if f not in self.FLAGS:
+ raise ValueError('Invalid flag: "{}".'.format(f))
+ self.flags = _flags
+
def create_in_kernel(self):
"""Create the set, removing existing set if needed."""
# Delete set if it exists with wrong type
@@ -191,10 +226,11 @@ class NetfilterSet:
"""Create the non-existing set, creating table if needed."""
create_set = [
*self.nft,
- 'add set {addr_family} {table} {set_} {{ type {type_} ; }}'.format(
+ 'add set {addr_family} {table} {set_} {{ type {type_} ; flags {flags}}}'.format(
addr_family=self.address_family, table=self.table,
set_=self.name,
- type_=' . '.join(self.TYPES[i] for i in self.type))
+ type_=' . '.join(self.TYPES[i] for i in self.type)),
+ flags=', '.join(self.flags)
]
return_code = CommandExec.run(create_set, allowed_return_codes=(0, 1))
if return_code == 0:
diff --git a/nat.py b/nat.py
new file mode 100644
index 0000000..856d2dc
--- /dev/null
+++ b/nat.py
@@ -0,0 +1,188 @@
+#! /usr/bin/python3
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+# Copyright © 2019 Hugo Levy-Falk
+
+"""
+Creates the nat set.
+"""
+
+import logging
+from configparser import ConfigParser
+
+import netaddr
+
+from firewall import NetfilterSet
+
+CONFIG = ConfigParser()
+CONFIG.read('config.ini')
+
+
+def create_nat(name, range_in, range_out, first_port, last_port):
+ """Create two nftables tables for the nat:
+ - _address : which link a (or a range of) local address to a
+ public address;
+ - _port : which links a local address to a
+ range of ports.
+
+ Args:
+ name: name of the sets
+ range_in: an IPRange with the private IP address
+ range_out: an IPRange with the public IP address
+ first_port: the first port used for the nat
+ last_port: the last port used for the nat
+ Returns:
+ (_address, _port) which are NetfilterSet
+ """
+ assert last_port >= first_port, (name + ": Your first_port "
+ "is lower than your last_port")
+ nb_private_by_public = range_in.size / range_out.size
+ nb_port_by_ip = (last_port - first_port + 1) / nb_private_by_public
+
+ ports = []
+ ips = []
+
+ port = first_port
+ for ip, port in range_in:
+ ports.append((
+ str(netaddr.IPAddress(ip)),
+ "%d-%d" % (port, port+nb_port_by_ip)
+ ))
+ port += nb_port_by_ip + 1
+ if port >= last_port:
+ port = first_port
+ ip = range_in.first
+ for ip_out in range_out:
+ ips.append((
+ '-'.join([
+ str(netaddr.IPAddress(ip)),
+ str(netaddr.IPAddress(ip+nb_private_by_public))
+ ]),
+ str(ip_out)
+ ))
+ ip += nb_private_by_public + 1
+
+ return (
+ NetfilterSet(
+ target_content=ips,
+ type_=('IPv4', 'IPv4'),
+ name=name,
+ table_name='nat',
+ ),
+ NetfilterSet(
+ target_content=ports,
+ type_=('IPv4', 'port'),
+ name=name,
+ table_name='nat',
+ ),
+ )
+
+
+def create_nat_adherent():
+ range_in = netaddr.IPRange(CONFIG['range_in_adherent'])
+ range_out = netaddr.IPRange(CONFIG['range_out_adherent'])
+ first_port = CONFIG['first_port_adherent']
+ last_port = CONFIG['last_port_adherent']
+ return create_nat(
+ 'adherent',
+ range_in,
+ range_out,
+ first_port,
+ last_port
+ )
+
+
+def create_nat_federez():
+ range_in = netaddr.IPRange(CONFIG['range_in_federez'])
+ range_out = netaddr.IPRange(CONFIG['range_out_federez'])
+ first_port = CONFIG['first_port_federez']
+ last_port = CONFIG['last_port_federez']
+ return create_nat(
+ 'federez',
+ range_in,
+ range_out,
+ first_port,
+ last_port
+ )
+
+
+def create_nat_aloes():
+ range_in = netaddr.IPRange(CONFIG['range_in_aloes'])
+ range_out = netaddr.IPRange(CONFIG['range_out_aloes'])
+ first_port = CONFIG['first_port_aloes']
+ last_port = CONFIG['last_port_aloes']
+ return create_nat(
+ 'aloes',
+ range_in,
+ range_out,
+ first_port,
+ last_port
+ )
+
+
+def create_nat_admin():
+ range_in = netaddr.IPRange(CONFIG['range_in_admin'])
+ range_out = netaddr.IPRange(CONFIG['range_out_admin'])
+ first_port = CONFIG['first_port_admin']
+ last_port = CONFIG['last_port_admin']
+ return create_nat(
+ 'admin',
+ range_in,
+ range_out,
+ first_port,
+ last_port
+ )
+
+
+def create_nat_prerezotage():
+ range_in = netaddr.IPRange(CONFIG['range_in_prerezotage'])
+ range_out = netaddr.IPRange(CONFIG['range_out_prerezotage'])
+ first_port = CONFIG['first_port_prerezotage']
+ last_port = CONFIG['last_port_prerezotage']
+ return create_nat(
+ 'prerezotage',
+ range_in,
+ range_out,
+ first_port,
+ last_port
+ )
+
+
+def main():
+ logging.info("Creating adherent nat...")
+ address, port = create_nat_adherent()
+ address.manage()
+ port.manage()
+ logging.info("Done.")
+ logging.info("Creating federez nat...")
+ address, port = create_nat_federez()
+ address.manage()
+ port.manage()
+ logging.info("Done.")
+ logging.info("Creating aloes nat...")
+ address, port = create_nat_aloes()
+ address.manage()
+ port.manage()
+ logging.info("Done.")
+ logging.info("Creating admin nat...")
+ address, port = create_nat_admin()
+ address.manage()
+ port.manage()
+ logging.info("Done.")
+ logging.info("Creating prerezotage nat...")
+ address, port = create_nat_prerezotage()
+ address.manage()
+ port.manage()
+ logging.info("Done.")