From fb0dbf28a0e7979050858256d2040d734b282afe Mon Sep 17 00:00:00 2001 From: Niklas Yann Wettengel Date: Sat, 22 Jan 2022 19:59:11 +0100 Subject: new net with nat64 --- .../install_respondd_poller/files/requirements.txt | 2 + .../files/respondd_poller.py | 147 +++++++++++++++++++++ .../files/respondd_poller.service | 12 ++ roles/install_respondd_poller/tasks/main.yml | 48 +++++++ .../templates/respondd_poller.json.j2 | 7 + 5 files changed, 216 insertions(+) create mode 100644 roles/install_respondd_poller/files/requirements.txt create mode 100644 roles/install_respondd_poller/files/respondd_poller.py create mode 100644 roles/install_respondd_poller/files/respondd_poller.service create mode 100644 roles/install_respondd_poller/tasks/main.yml create mode 100644 roles/install_respondd_poller/templates/respondd_poller.json.j2 (limited to 'roles/install_respondd_poller') diff --git a/roles/install_respondd_poller/files/requirements.txt b/roles/install_respondd_poller/files/requirements.txt new file mode 100644 index 0000000..83bb832 --- /dev/null +++ b/roles/install_respondd_poller/files/requirements.txt @@ -0,0 +1,2 @@ +wgnlpy +requests diff --git a/roles/install_respondd_poller/files/respondd_poller.py b/roles/install_respondd_poller/files/respondd_poller.py new file mode 100644 index 0000000..1eb98a1 --- /dev/null +++ b/roles/install_respondd_poller/files/respondd_poller.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python + +import socket +import ipaddress +import threading +import time +import zlib +import json +import os.path +import sys +from wgnlpy import WireGuard +import requests +from xml.etree import ElementTree + +if not os.path.exists("/etc/respondd_poller.json"): + print("/etc/respondd_poller.json missing") + sys.exit(1) + +interface = None +prefix = None +yanic_addr = None +request = None + +with open("/etc/respondd_poller.json", "r") as f: + config = json.load(f) + if "interface" in config: + interface = config["interface"] + if "prefix" in config: + prefix = ipaddress.IPv6Network(config["prefix"]) + if "yanic_addr" in config and "yanic_port" in config: + yanic_addr = (config["yanic_addr"], int(config["yanic_port"])) + if "request" in config: + request = config["request"].encode("ascii") + +wg = WireGuard() +sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) +last_request = dict() +last_response = dict() + +def get_wg_peers(): + wgpeers = wg.get_interface(interface).peers + for peer in wgpeers: + for ip in wgpeers[peer].allowedips: + if ip.subnet_of(prefix): + yield ip + +def inflate(data): + decompress = zlib.decompressobj(-zlib.MAX_WBITS) + inflated = decompress.decompress(data) + inflated += decompress.flush() + return inflated.decode() + +def cleanup(): + while True: + time.sleep(60) + old = time.monotonic() - 360 + ips = [] + macs = [] + for ip in last_request: + if last_response[ip] < old: + ips.append(ip) + for ip in ips: + del last_response[ip] + del last_request[ip] + +def recv(): + global sock + while True: + data, addr = sock.recvfrom(1500) + sock.sendto(data, yanic_addr) + j = json.loads(inflate(data)) + last_response[ipaddress.IPv6Address(addr[0])] = time.monotonic() + +def send(ip): + global request + try: + sock.sendto(request, (bytearray(str(ip).encode('ascii')), 1001)) + except: + print("failed to send packet to", ip) + return + +def get_http_nodeinfo(ip): + global last_request + now = time.monotonic() + try: + status = requests.get('http://[' + str(ip) + ']/cgi-bin/status') + except: + return + status_tree = ElementTree.fromstring(status.content) + mesh_ifs = [] + interface_list = status_tree.findall(".//*[@data-interface]") + for interface in interface_list: + mesh_ifs.append(interface.attrib["data-interface"]) + for mesh_if in mesh_ifs: + try: + nodeinfo = requests.get('http://[' + str(ip) + ']/cgi-bin/dyn/neighbours-nodeinfo?' + mesh_if) + except: + return + for line in nodeinfo.content.split(b'\n'): + if line.startswith(b'data: {'): + data = line.split(b': ', maxsplit=1)[1] + data = json.loads(data) + if "network" in data and "addresses" in data["network"]: + for address in data["network"]["addresses"]: + if ipaddress.IPv6Network(address).subnet_of(prefix): + node_ip = ipaddress.IPv6Address(address) + if node_ip not in last_request: + last_request[node_ip] = now + last_response[node_ip] = now + +def scan_wg_peers(): + global last_request + while True: + print("scanning wg peers") + request_threads = [] + now = time.monotonic() + for net in get_wg_peers(): + ip = ipaddress.IPv6Address(str(net.network_address) + "1") + if ip not in last_request: + last_request[ip] = now + last_response[ip] = now + request_thread = threading.Thread(target=get_http_nodeinfo, args=(ip,)) + request_thread.start() + request_threads.append(request_thread) + if len(request_threads) > 10: + for thread in request_threads: + thread.join() + request_threads = [] + time.sleep(60) + + +listen_thread = threading.Thread(target=recv) +listen_thread.start() +cleanup_thread = threading.Thread(target=cleanup) +cleanup_thread.start() +scan_thread = threading.Thread(target=scan_wg_peers) +scan_thread.start() + +last_wg_time = 0 + +while True: + now = time.monotonic() + for ip in last_request: + if now - last_request[ip] > 15: + last_request[ip] = now + send(ip) + time.sleep(1) diff --git a/roles/install_respondd_poller/files/respondd_poller.service b/roles/install_respondd_poller/files/respondd_poller.service new file mode 100644 index 0000000..96e309c --- /dev/null +++ b/roles/install_respondd_poller/files/respondd_poller.service @@ -0,0 +1,12 @@ +[Unit] +Description=respondd_poller +After=network.target + +[Service] +ExecStart=/opt/respondd_poller/venv/bin/python -u /opt/respondd_poller/respondd_poller.py +Restart=always +WorkingDirectory=/opt/respondd_poller +Environment=PYTHONPATH=/opt/respondd_poller + +[Install] +WantedBy=multi-user.target diff --git a/roles/install_respondd_poller/tasks/main.yml b/roles/install_respondd_poller/tasks/main.yml new file mode 100644 index 0000000..aa03558 --- /dev/null +++ b/roles/install_respondd_poller/tasks/main.yml @@ -0,0 +1,48 @@ +--- +- name: install respondd_poller dependencies + pacman: + name: + - git + - python-virtualenv + - python-setuptools + state: present + +- name: create venv + command: + cmd: "python -m venv /opt/respondd_poller/venv" + creates: /opt/respondd_poller/venv + +- name: install respondd_poller requirements + copy: + src: requirements.txt + dest: /opt/respondd_poller/requirements.txt + mode: 0644 + +- name: install respondd_poller script + copy: + src: respondd_poller.py + dest: /opt/respondd_poller/respondd_poller.py + mode: 0644 + +- name: install requirements + pip: + requirements: /opt/respondd_poller/requirements.txt + virtualenv: /opt/respondd_poller/venv + +- name: install respondd_poller config + template: + src: respondd_poller.json.j2 + dest: /etc/respondd_poller.json + mode: 0644 + +- name: create respondd_poller service + copy: + src: respondd_poller.service + dest: /etc/systemd/system/respondd_poller.service + mode: 0644 + +- name: start and enable respondd_poller service + systemd: + name: respondd_poller + state: started + enabled: yes diff --git a/roles/install_respondd_poller/templates/respondd_poller.json.j2 b/roles/install_respondd_poller/templates/respondd_poller.json.j2 new file mode 100644 index 0000000..c3f6574 --- /dev/null +++ b/roles/install_respondd_poller/templates/respondd_poller.json.j2 @@ -0,0 +1,7 @@ +{ + "interface":"wgmyk", + "prefix":"2a03:2260:1016::/48", + "yanic_addr": "fe80::41:18ff:fec5:5041%wgmyk", + "yanic_port": 10001, + "request":"GET nodeinfo statistics neighbours" +} -- cgit v1.2.3-54-g00ecf