diff --git a/ipsec_exporter.py b/ipsec_exporter.py new file mode 100644 index 0000000..8f94055 --- /dev/null +++ b/ipsec_exporter.py @@ -0,0 +1,5 @@ +from src.app import App + +if __name__ == "__main__": + app = App() + app.main() \ No newline at end of file diff --git a/src/app.py b/src/app.py new file mode 100644 index 0000000..9b9e262 --- /dev/null +++ b/src/app.py @@ -0,0 +1,42 @@ +from argparse import * +from src.prometheus_metrics_server import * +from src.metrics_source import * + +class App: + args: Namespace + + def __init__(self): + parser = ArgumentParser(description="IPsec Prometheus exporter for Libreswan") + parser.add_argument("-a", "--address", dest="address", required=False, type=str, default="0.0.0.0", help="Server IP address") + parser.add_argument("-p", "--port", dest="port", required=False, type=int, default=9446, help="Server port") + parser.add_argument("-i", "--interval", dest="interval", required=False, type=int, default=1, help="Metrics read interval (in seconds)") + self.args = parser.parse_args() + + def main(self): + server = PrometheusMetricsServer(self.args.port, "IPsec exporter") + server.address = self.args.address + server.interval = self.args.interval + + globalstatus_source = CommandMetricsSource("sudo ipsec globalstatus") + globalstatus_source.add_metric("ipsec_current_states", r"current\.states\.(?P\w+)=(?P\d+)") + globalstatus_source.add_metric("ipsec_current_states_iketype", r"current\.states\.iketype\.(?P\w+)=(?P\d+)") + globalstatus_source.add_metric("ipsec_current_states_enumerate", r"current\.states\.enumerate\.(?P\w+)=(?P\d+)") + globalstatus_source.add_metric("ipsec_total_ipsec_type", r"total\.ipsec\.type\.(?P\w+)=(?P\d+)") + globalstatus_source.add_metric("ipsec_total_traffic", r"total\.(?P\w+)\.traffic\.(?P\w+)=(?P\d+)") + globalstatus_source.add_metric("ipsec_total_ike", r"total\.ike\.(?P\w+)\.(?P\w+)=(?P\d+)") + globalstatus_source.add_metric("ipsec_total_ikev2_redirect", r"total\.ike\.ikev2\.redirect\.(?P\w+)=(?P\d+)") + globalstatus_source.add_metric("ipsec_total_pamauth", r"total\.pamauth\.(?P\w+)=(?P\d+)") + globalstatus_source.add_metric("ipsec_total_iketcp", r"total\.iketcp\.(?P\w+)\.(?P\w+)=(?P\d+)") + globalstatus_source.add_metric("ipsec_total_ike_encr", r"total\.(?P\w+)\.encr\.(?P\w+)=(?P\d+)") + globalstatus_source.add_metric("ipsec_total_ike_integ", r"total\.(?P\w+)\.integ\.(?P\w+)=(?P\d+)") + globalstatus_source.add_metric("ipsec_total_ike_group", r"total\.(?P\w+)\.group\.(?P\w+)=(?P\d+)") + globalstatus_source.add_metric("ipsec_total_ike_notifies_error", r"total\.(?P\w+)\.(?P\w+)\.notifies\.error\.(?P\w+)=(?P\d+)") + globalstatus_source.add_metric("ipsec_total_ikev2_notifies_status", r"total\.ikev2\.(?P\w+)\.notifies\.status\.(?P\w+)=(?P\d+)") + server.add_metrics_source(globalstatus_source) + + custom_metrics_source = CustomMetricsSource() + custom_metrics_source.add_metric(IPsecTrafficCustomMetric("ipsec_traffic")) + server.add_metrics_source(custom_metrics_source) + + server.run() + \ No newline at end of file diff --git a/src/metric.py b/src/metric.py new file mode 100644 index 0000000..cdaf78c --- /dev/null +++ b/src/metric.py @@ -0,0 +1,74 @@ +from abc import abstractmethod +from prometheus_client import * +from re import Pattern +import re +import os + + + +class Metric: + gauge: Gauge + + def __init__(self, name: str, labels: list[str], description: str = ""): + self.gauge = Gauge( + name, + description, + labels + ) + + +class CommandMetric(Metric): + regex: Pattern + + def __init__(self, name: str, regex: str, description: str = ""): + self.regex = re.compile(regex) + + labels = list(self.regex.groupindex.keys()) + labels.remove("VALUE") + + super().__init__(name, labels, description) + + def update(self, command_output: str): + self.gauge.clear() + + results = re.finditer(self.regex, command_output) + + for result in results: + groups = result.groupdict() + value = groups.pop("VALUE") + self.gauge.labels(*list(groups.values())).set(int(value)) + + +class CustomMetric(Metric): + def __init__(self, name: str, labels: list[str], description: str = ""): + super().__init__(name, labels, description) + + @abstractmethod + def update(self): + pass + + +class IPsecTrafficCustomMetric(CustomMetric): + def __init__(self, name: str, description: str = ""): + labels = [ + "lease", + "connection", + "direction" + ] + super().__init__(name, labels, description) + + def update(self): + self.gauge.clear() + + trafficstatus = os.popen("sudo ipsec trafficstatus").read() + + trafficstatus_results = re.finditer(r""""(?P.+)"\[\d+\] \d+\.\d+\.\d+\.\d+, type=\w+, add_time=\d+, inBytes=(?P\d+), outBytes=(?P\d+), maxBytes=.+, id='.+', lease=(?P\d+\.\d+\.\d+\.\d+\/\d+)""", trafficstatus) + + for result in trafficstatus_results: + lease = result.groupdict()["lease"] + connection = result.groupdict()["connection"] + in_value = result.groupdict()["IN_VALUE"] + out_value = result.groupdict()["OUT_VALUE"] + + self.gauge.labels(lease, connection, "in").set(int(in_value)) + self.gauge.labels(lease, connection, "out").set(int(out_value)) \ No newline at end of file diff --git a/src/metrics_source.py b/src/metrics_source.py new file mode 100644 index 0000000..0ba3a6f --- /dev/null +++ b/src/metrics_source.py @@ -0,0 +1,43 @@ +from abc import ABC, abstractmethod +from src.metric import * +import os + + + +class MetricsSource: + _metrics : list[Metric] + + def __init__(self): + self._metrics = [] + + @abstractmethod + def update(self): + pass + + +class CommandMetricsSource(MetricsSource): + command : str + + def __init__(self, command: str): + super().__init__() + self.command = command + + def add_metric(self, name: str, regex: str, description: str = ""): + self._metrics.append(CommandMetric(name, regex, description)) + + def update(self): + output = os.popen(self.command).read() + for metric in self._metrics: + metric.update(output) + + +class CustomMetricsSource(MetricsSource): + def __init__(self): + super().__init__() + + def add_metric(self, custom_metric : CustomMetric): + self._metrics.append(custom_metric) + + def update(self): + for metric in self._metrics: + metric.update() \ No newline at end of file diff --git a/src/prometheus_metrics_server.py b/src/prometheus_metrics_server.py new file mode 100644 index 0000000..3e6d517 --- /dev/null +++ b/src/prometheus_metrics_server.py @@ -0,0 +1,32 @@ +import prometheus_client +import time +from src.metrics_source import * + + + +class PrometheusMetricsServer: + _metrics_sources: list[MetricsSource] + + address: str + port: int + interval: int + server_name: str + + def __init__(self, port: int, server_name: str): + self._metrics_sources = [] + + self.address = "0.0.0.0" + self.port = port + self.interval = 1 + self.server_name = server_name + + def add_metrics_source(self, source: MetricsSource): + self._metrics_sources.append(source) + + def run(self): + prometheus_client.start_http_server(self.port, addr=self.address) + print(f"{self.server_name} is running on {self.address}:{self.port}") + while True: + for source in self._metrics_sources: + source.update() + time.sleep(self.interval) \ No newline at end of file