├── iptables_exporter ├── __init__.py └── main.py ├── MANIFEST.in ├── setup.cfg ├── .dockerignore ├── requirements.txt ├── iptables-exporter ├── Dockerfile ├── .gitignore ├── setup.py └── README.md /iptables_exporter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/*.py[cod] 2 | **/__pycache__ 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | prometheus-client>=0.9.0 2 | python-iptables>=0.13 3 | -------------------------------------------------------------------------------- /iptables-exporter: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from iptables_exporter.main import main 4 | 5 | 6 | main() 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:focal 2 | 3 | 4 | # Requirements 5 | RUN apt-get update \ 6 | && apt-get install -y iptables python3-iptables python3-prometheus-client \ 7 | && rm -rf /var/lib/apt/lists/* 8 | 9 | EXPOSE 9119 10 | CMD ["iptables-exporter"] 11 | 12 | # Source 13 | COPY iptables-exporter /usr/local/bin/iptables-exporter 14 | COPY iptables_exporter /usr/local/lib/python3.8/dist-packages/iptables_exporter 15 | 16 | RUN chmod 0755 /usr/local/bin/iptables-exporter \ 17 | && python3 -m compileall /usr/local/lib/python3.8/dist-packages/iptables_exporter 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Distribution / packaging 7 | .Python 8 | build/ 9 | develop-eggs/ 10 | dist/ 11 | downloads/ 12 | eggs/ 13 | .eggs/ 14 | lib/ 15 | lib64/ 16 | parts/ 17 | sdist/ 18 | var/ 19 | wheels/ 20 | *.egg-info/ 21 | .installed.cfg 22 | *.egg 23 | MANIFEST 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .coverage.* 40 | .cache 41 | nosetests.xml 42 | coverage.xml 43 | *.cover 44 | .hypothesis/ 45 | .pytest_cache/ 46 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from codecs import open 3 | from setuptools import setup, find_packages 4 | 5 | here = os.path.abspath(os.path.dirname(__file__)) 6 | 7 | with open(os.path.join(here, 'requirements.txt')) as f: 8 | requirements = f.read().splitlines() 9 | 10 | with open(os.path.join(here, 'README.md'), encoding='utf-8') as f: 11 | long_description = f.read() 12 | 13 | setup( 14 | name="iptables-exporter", 15 | version="0.9.3", 16 | description='Prometheus iptables exporter', 17 | long_description=long_description, 18 | long_description_content_type='text/markdown', 19 | 20 | url='https://github.com/madron/iptables-exporter', 21 | author='Massimiliano Ravelli', 22 | author_email='massimiliano.ravelli@gmail.com', 23 | 24 | license='MIT', 25 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 26 | classifiers=[ 27 | 'Development Status :: 3 - Alpha', 28 | 'Environment :: Console', 29 | 'Intended Audience :: System Administrators', 30 | 'Topic :: System :: Monitoring', 31 | 'License :: OSI Approved :: MIT License', 32 | 'Programming Language :: Python :: 3', 33 | 'Programming Language :: Python :: 3.4', 34 | 'Programming Language :: Python :: 3.5', 35 | 'Programming Language :: Python :: 3.6', 36 | 'Programming Language :: Python :: 3.7', 37 | ], 38 | keywords='prometheus monitoring iptables bandwidth', 39 | 40 | packages=find_packages(), 41 | scripts=['iptables-exporter'], 42 | install_requires=requirements, 43 | ) 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Iptables exporter 2 | 3 | A Prometheus exporter that collects traffic data from iptables rules. 4 | 5 | 6 | ## Installation 7 | 8 | pip install iptables-exporter 9 | 10 | 11 | ## Usage 12 | 13 | Test run: 14 | 15 | iptables-exporter --dump-data 16 | 17 | Run iptables-exporter: 18 | 19 | iptables-exporter --port 9119 20 | 21 | Point your browser to http://localhost:9119/metrics 22 | 23 | 24 | ## Docker 25 | 26 | docker run --net=host --cap-add=NET_ADMIN madron/iptables-exporter 27 | 28 | 29 | ## Configure iptables 30 | 31 | Optionally you can monitor specific rules by adding a comment starting with `iptables-exporter`: 32 | 33 | iptables -A INPUT --dport ssh -j ACCEPT -m comment --comment "iptables-exporter ssh traffic" 34 | 35 | collects packets and bytes counter: 36 | 37 | iptables_packets{ip_version="4",table="filter",chain="input",rule="ssh traffic"} 347.0 38 | iptables_bytes{ip_version="4",table="filter",chain="input",rule="ssh traffic"} 44512.0 39 | 40 | More rules with same name: 41 | 42 | iptables -A INPUT -s 10.0.0.0/8 --dport ssh -j ACCEPT -m comment --comment "iptables-exporter ssh traffic" 43 | iptables -A INPUT -s 172.16.0.0/12 --dport ssh -j ACCEPT -m comment --comment "iptables-exporter ssh traffic" 44 | iptables -A INPUT -s 192.168.0.0/16 --dport ssh -j ACCEPT -m comment --comment "iptables-exporter ssh traffic" 45 | 46 | exports total packets and bytes for the 3 rules as they have same ip_version, table, chain and name. 47 | 48 | 49 | ## Used by: 50 | 51 | - [OpenAI](https://openai.com/blog/scaling-kubernetes-to-7500-nodes/) 52 | - [MasterTraining](https://www.mastertraining.it/) 53 | - [MasterVoice](https://www.mastervoice.it/en/) 54 | -------------------------------------------------------------------------------- /iptables_exporter/main.py: -------------------------------------------------------------------------------- 1 | #!/usr//bin/env python 2 | 3 | import argparse 4 | import copy 5 | import re 6 | import iptc 7 | from prometheus_client import generate_latest 8 | from prometheus_client import make_wsgi_app 9 | from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, REGISTRY 10 | from wsgiref.simple_server import make_server 11 | 12 | 13 | TABLES = dict( 14 | filter=iptc.Table.FILTER, 15 | nat=iptc.Table.NAT, 16 | mangle=iptc.Table.MANGLE, 17 | raw=iptc.Table.RAW, 18 | security=iptc.Table.SECURITY, 19 | ) 20 | IP_VERSION_CHOICES = ['4', '6'] 21 | TABLE_CHOICES = TABLES.keys() 22 | DEFAULT_TABLE_CHOICES = ['filter'] 23 | RE = re.compile('(.*)?iptables-exporter (?P.*)$') 24 | 25 | 26 | class IptablesCollector(object): 27 | def __init__(self, ip_versions=IP_VERSION_CHOICES, tables=DEFAULT_TABLE_CHOICES): 28 | self.ip_versions = ip_versions 29 | self.tables = tables 30 | 31 | def collect(self): 32 | # Metrics 33 | iptables_rules = GaugeMetricFamily( 34 | 'iptables_rules', 35 | 'Number of rules', 36 | labels=['ip_version', 'table', 'chain'], 37 | ) 38 | iptables_packets = CounterMetricFamily( 39 | 'iptables_packets', 40 | 'Number of matched packets', 41 | labels=['ip_version', 'table', 'chain', 'rule'], 42 | ) 43 | iptables_bytes = CounterMetricFamily( 44 | 'iptables_bytes', 45 | 'Number of matched bytes', 46 | labels=['ip_version', 'table', 'chain', 'rule'], 47 | ) 48 | for ip_version in self.ip_versions: 49 | labels = dict(ip_version=ip_version) 50 | for table_name in self.tables: 51 | if ip_version == '4': 52 | table = iptc.Table(TABLES[table_name]) 53 | else: 54 | table = iptc.Table6(TABLES[table_name]) 55 | table.refresh() 56 | labels['table'] = table_name 57 | for chain in table.chains: 58 | labels['chain'] = chain.name.lower() 59 | rule_count = 0 60 | for rule in chain.rules: 61 | rule_count += 1 62 | exporter_name = get_exporter_name(rule) 63 | if exporter_name: 64 | labels['rule'] = exporter_name 65 | counter_labels = [labels['ip_version'], labels['table'], labels['chain'], labels['rule']] 66 | packets, bytes = rule.get_counters() 67 | iptables_packets.add_metric(counter_labels, packets) 68 | iptables_bytes.add_metric(counter_labels, bytes) 69 | rules_labels = [labels['ip_version'], labels['table'], labels['chain']] 70 | iptables_rules.add_metric(rules_labels, rule_count) 71 | yield iptables_rules 72 | yield iptables_packets 73 | yield iptables_bytes 74 | 75 | 76 | def get_exporter_name(rule): 77 | name = None 78 | for match in rule.matches: 79 | if 'comment' in match.parameters: 80 | comment = match.parameters['comment'] 81 | match = RE.match(comment) 82 | if match: 83 | return match.groupdict()['name'] 84 | return name 85 | 86 | 87 | def main(): 88 | # Parse arguments 89 | parser = argparse.ArgumentParser( 90 | description='Iptables Prometheus exporter.' 91 | ) 92 | parser.add_argument( 93 | '--address', metavar='IP', type=str, default='', 94 | help='Listening address, default: all' 95 | ) 96 | parser.add_argument( 97 | '--port', metavar='PORT', type=int, default=9119, 98 | help='Listening port, default: 9119' 99 | ) 100 | parser.add_argument( 101 | '--ip-versions', metavar='V', type=str, nargs='+', 102 | choices=IP_VERSION_CHOICES, default=IP_VERSION_CHOICES, 103 | help='List of IP versions, default: {}'.format(', '.join(IP_VERSION_CHOICES)) 104 | ) 105 | parser.add_argument( 106 | '--tables', metavar='TABLE', type=str, nargs='+', 107 | choices=TABLE_CHOICES, default=DEFAULT_TABLE_CHOICES, 108 | help='List of tables, default: {}'.format(', '.join(DEFAULT_TABLE_CHOICES)) 109 | ) 110 | parser.add_argument( 111 | '--dump-data', action='store_true', default=False, 112 | help='Prints collected data and exits' 113 | ) 114 | args = parser.parse_args() 115 | 116 | REGISTRY.register(IptablesCollector(ip_versions=args.ip_versions, tables=args.tables)) 117 | 118 | # Test mode 119 | if args.dump_data: 120 | print(generate_latest(REGISTRY).decode('utf8')) 121 | exit(0) 122 | 123 | # Start http server 124 | app = make_wsgi_app() 125 | httpd = make_server(args.address, args.port, app) 126 | httpd.serve_forever() 127 | --------------------------------------------------------------------------------