From 31d7191863ae26f9201321a0df82082a4c52be55 Mon Sep 17 00:00:00 2001 From: Oliver Lorenz Date: Tue, 25 Aug 2026 20:59:04 +0200 Subject: [PATCH] Add Ansible role for GRE backbone tunnels as alternative to lib/gre.sh Models the setup and monitoring behavior of lib/gre.sh (ip link/ip addr commands plus the ping-based watchdog cron) as a declarative gre_tunnels role using systemd-networkd .netdev/.network units and a systemd timer, instead of imperative shell run from the init script. Co-Authored-By: Claude Sonnet 5 --- ansible/group_vars/ffc_servers.yml | 17 +++ ansible/roles/gre_tunnels/defaults/main.yml | 24 +++++ ansible/roles/gre_tunnels/handlers/main.yml | 14 +++ ansible/roles/gre_tunnels/tasks/main.yml | 100 ++++++++++++++++++ .../templates/gre-tunnel.netdev.j2 | 9 ++ .../templates/gre-tunnel.network.j2 | 17 +++ .../templates/gre-watchdog.service.j2 | 9 ++ .../gre_tunnels/templates/gre-watchdog.sh.j2 | 32 ++++++ .../templates/gre-watchdog.timer.j2 | 11 ++ ansible/site.yml | 6 ++ 10 files changed, 239 insertions(+) create mode 100644 ansible/group_vars/ffc_servers.yml create mode 100644 ansible/roles/gre_tunnels/defaults/main.yml create mode 100644 ansible/roles/gre_tunnels/handlers/main.yml create mode 100644 ansible/roles/gre_tunnels/tasks/main.yml create mode 100644 ansible/roles/gre_tunnels/templates/gre-tunnel.netdev.j2 create mode 100644 ansible/roles/gre_tunnels/templates/gre-tunnel.network.j2 create mode 100644 ansible/roles/gre_tunnels/templates/gre-watchdog.service.j2 create mode 100644 ansible/roles/gre_tunnels/templates/gre-watchdog.sh.j2 create mode 100644 ansible/roles/gre_tunnels/templates/gre-watchdog.timer.j2 create mode 100644 ansible/site.yml diff --git a/ansible/group_vars/ffc_servers.yml b/ansible/group_vars/ffc_servers.yml new file mode 100644 index 0000000..6084587 --- /dev/null +++ b/ansible/group_vars/ffc_servers.yml @@ -0,0 +1,17 @@ +--- +# gre_wanip must be set per host (host_vars), matching WANIP in +# conf/general.local.conf. + +# Equivalent to GRE_PEERS in conf/general.conf. +gre_peers: + - { name: brewster, ip: 217.79.184.109 } + - { name: newton, ip: 78.46.48.196 } + - { name: archimedes, ip: 162.252.172.175 } + - { name: descartes, ip: 144.76.4.100 } + - { name: kohn, ip: 91.194.84.98 } + - { name: spooner, ip: 51.158.152.84 } + - { name: hawking, ip: 163.172.25.179 } + - { name: noether, ip: 93.186.197.162 } + +# Equivalent to LOG_TO in conf/general.conf. +gre_watchdog_mail_to: "crew@chemnitz.freifunk.net" diff --git a/ansible/roles/gre_tunnels/defaults/main.yml b/ansible/roles/gre_tunnels/defaults/main.yml new file mode 100644 index 0000000..0ace11d --- /dev/null +++ b/ansible/roles/gre_tunnels/defaults/main.yml @@ -0,0 +1,24 @@ +--- +# Public IPv4 address of this server, equivalent to WANIP in conf/general.conf +gre_wanip: "" + +# Priority prefix for the systemd-networkd unit files (controls apply order) +gre_networkd_priority: "25" + +# GRE backbone peers, equivalent to GRE_PEERS in conf/general.conf. +# The peer whose ip matches gre_wanip is skipped automatically. +# Example: +# gre_peers: +# - { name: brewster, ip: 217.79.184.109 } +# - { name: newton, ip: 78.46.48.196 } +gre_peers: [] + +# Periodic liveness check, equivalent to gre_cron()/gre_check_tunnel() plus +# the "watchdog" cron entry described in README.md. +gre_watchdog_enabled: true +gre_watchdog_script_path: /usr/local/lib/ffc/gre-watchdog.sh +gre_watchdog_interval: "5min" + +# Mail recipient for alerts, equivalent to LOG_TO in conf/general.conf. +# Leave empty to only log to syslog and skip mail alerts. +gre_watchdog_mail_to: "" diff --git a/ansible/roles/gre_tunnels/handlers/main.yml b/ansible/roles/gre_tunnels/handlers/main.yml new file mode 100644 index 0000000..23914e1 --- /dev/null +++ b/ansible/roles/gre_tunnels/handlers/main.yml @@ -0,0 +1,14 @@ +--- +- name: restart systemd-networkd + ansible.builtin.systemd: + name: systemd-networkd + state: restarted + +- name: reload systemd daemon + ansible.builtin.systemd: + daemon_reload: true + +- name: restart gre-watchdog timer + ansible.builtin.systemd: + name: gre-watchdog.timer + state: restarted diff --git a/ansible/roles/gre_tunnels/tasks/main.yml b/ansible/roles/gre_tunnels/tasks/main.yml new file mode 100644 index 0000000..9c0e8e2 --- /dev/null +++ b/ansible/roles/gre_tunnels/tasks/main.yml @@ -0,0 +1,100 @@ +--- +- name: Verify required GRE variables are set + ansible.builtin.assert: + that: + - gre_wanip is defined and gre_wanip | length > 0 + - gre_peers is defined + fail_msg: "gre_wanip and gre_peers must be set (see role defaults)" + +- name: Ensure systemd-networkd is enabled and running + ansible.builtin.systemd: + name: systemd-networkd + enabled: true + state: started + +- name: Determine active GRE peers (exclude ourselves) + ansible.builtin.set_fact: + gre_active_peers: "{{ gre_peers | rejectattr('ip', 'equalto', gre_wanip) | list }}" + +- name: Deploy GRE tunnel netdev units + ansible.builtin.template: + src: gre-tunnel.netdev.j2 + dest: "/etc/systemd/network/{{ gre_networkd_priority }}-gre-{{ item.name }}.netdev" + owner: root + group: root + mode: "0644" + loop: "{{ gre_active_peers }}" + loop_control: + label: "{{ item.name }}" + notify: restart systemd-networkd + +- name: Deploy GRE tunnel network units + ansible.builtin.template: + src: gre-tunnel.network.j2 + dest: "/etc/systemd/network/{{ gre_networkd_priority }}-gre-{{ item.name }}.network" + owner: root + group: root + mode: "0644" + loop: "{{ gre_active_peers }}" + loop_control: + label: "{{ item.name }}" + notify: restart systemd-networkd + +- name: Find existing GRE unit files + ansible.builtin.find: + paths: /etc/systemd/network + patterns: "*-gre-*.netdev,*-gre-*.network" + register: gre_existing_units + +- name: Remove GRE unit files for peers no longer configured + ansible.builtin.file: + path: "{{ item.path }}" + state: absent + loop: "{{ gre_existing_units.files }}" + loop_control: + label: "{{ item.path | basename }}" + when: > + (item.path | basename | regex_replace('^[0-9]+-gre-(.*)\.(netdev|network)$', '\1')) + not in (gre_active_peers | map(attribute='name') | list) + notify: restart systemd-networkd + +- name: Deploy GRE tunnel watchdog script + ansible.builtin.template: + src: gre-watchdog.sh.j2 + dest: "{{ gre_watchdog_script_path }}" + owner: root + group: root + mode: "0755" + when: gre_watchdog_enabled + +- name: Deploy GRE tunnel watchdog systemd service + ansible.builtin.template: + src: gre-watchdog.service.j2 + dest: /etc/systemd/system/gre-watchdog.service + owner: root + group: root + mode: "0644" + when: gre_watchdog_enabled + notify: + - reload systemd daemon + - restart gre-watchdog timer + +- name: Deploy GRE tunnel watchdog systemd timer + ansible.builtin.template: + src: gre-watchdog.timer.j2 + dest: /etc/systemd/system/gre-watchdog.timer + owner: root + group: root + mode: "0644" + when: gre_watchdog_enabled + notify: + - reload systemd daemon + - restart gre-watchdog timer + +- name: Enable and start GRE tunnel watchdog timer + ansible.builtin.systemd: + name: gre-watchdog.timer + enabled: true + state: started + daemon_reload: true + when: gre_watchdog_enabled diff --git a/ansible/roles/gre_tunnels/templates/gre-tunnel.netdev.j2 b/ansible/roles/gre_tunnels/templates/gre-tunnel.netdev.j2 new file mode 100644 index 0000000..c054684 --- /dev/null +++ b/ansible/roles/gre_tunnels/templates/gre-tunnel.netdev.j2 @@ -0,0 +1,9 @@ +{{ ansible_managed | comment }} +[NetDev] +Name=gre-{{ item.name }} +Kind=gretap + +[Tunnel] +Local={{ gre_wanip }} +Remote={{ item.ip }} +TTL=255 diff --git a/ansible/roles/gre_tunnels/templates/gre-tunnel.network.j2 b/ansible/roles/gre_tunnels/templates/gre-tunnel.network.j2 new file mode 100644 index 0000000..c382c44 --- /dev/null +++ b/ansible/roles/gre_tunnels/templates/gre-tunnel.network.j2 @@ -0,0 +1,17 @@ +{{ ansible_managed | comment }} +[Match] +Name=gre-{{ item.name }} + +[Link] +MTUBytes=1426 + +[Network] +LinkLocalAddressing=no + +[Address] +Address=169.254.{{ gre_wanip.split('.')[2] }}.{{ gre_wanip.split('.')[3] }}/32 +Peer=169.254.{{ item.ip.split('.')[2] }}.{{ item.ip.split('.')[3] }}/32 +Scope=link + +[Address] +Address=fe80::ffc:{{ '%x' % (gre_wanip.split('.')[2] | int) }}:{{ '%x' % (gre_wanip.split('.')[3] | int) }}/64 diff --git a/ansible/roles/gre_tunnels/templates/gre-watchdog.service.j2 b/ansible/roles/gre_tunnels/templates/gre-watchdog.service.j2 new file mode 100644 index 0000000..bc14ed4 --- /dev/null +++ b/ansible/roles/gre_tunnels/templates/gre-watchdog.service.j2 @@ -0,0 +1,9 @@ +{{ ansible_managed | comment }} +[Unit] +Description=Freifunk Chemnitz GRE tunnel watchdog +After=systemd-networkd.service +Wants=systemd-networkd.service + +[Service] +Type=oneshot +ExecStart={{ gre_watchdog_script_path }} diff --git a/ansible/roles/gre_tunnels/templates/gre-watchdog.sh.j2 b/ansible/roles/gre_tunnels/templates/gre-watchdog.sh.j2 new file mode 100644 index 0000000..f9e8706 --- /dev/null +++ b/ansible/roles/gre_tunnels/templates/gre-watchdog.sh.j2 @@ -0,0 +1,32 @@ +#!/bin/bash +# {{ ansible_managed }} +# +# Checks every gre-* interface for reachability by pinging the all-routers +# multicast address and looking for duplicate replies from the remote end. +# Equivalent to gre_cron()/gre_check_tunnel() in lib/gre.sh. + +set -u + +mail_to="{{ gre_watchdog_mail_to }}" + +log_msg() { + logger "[FFC] $(date '+%Y-%m-%d %H:%M:%S') $1" +} + +log_alert() { + log_msg "$1" + if [ -n "$mail_to" ]; then + mail -s "[FFC] Server Alert - $(hostname -f)" "$mail_to" <<-EOF + $(date '+%Y-%m-%d %H:%M:%S') + + $1 + EOF + fi +} + +for iface in $(ls /sys/class/net | grep '^gre-'); do + dup_count=$(ping6 -c5 -i1 "ff02::2%${iface}" 2>/dev/null | grep -c DUP) + if [ "$dup_count" -eq 0 ]; then + log_alert "GRE tunnel seems down: $iface" + fi +done diff --git a/ansible/roles/gre_tunnels/templates/gre-watchdog.timer.j2 b/ansible/roles/gre_tunnels/templates/gre-watchdog.timer.j2 new file mode 100644 index 0000000..dc34e3c --- /dev/null +++ b/ansible/roles/gre_tunnels/templates/gre-watchdog.timer.j2 @@ -0,0 +1,11 @@ +{{ ansible_managed | comment }} +[Unit] +Description=Run the Freifunk Chemnitz GRE tunnel watchdog periodically + +[Timer] +OnBootSec=5min +OnUnitActiveSec={{ gre_watchdog_interval }} +AccuracySec=30s + +[Install] +WantedBy=timers.target diff --git a/ansible/site.yml b/ansible/site.yml new file mode 100644 index 0000000..8cd338d --- /dev/null +++ b/ansible/site.yml @@ -0,0 +1,6 @@ +--- +- name: Configure Freifunk Chemnitz GRE backbone tunnels + hosts: ffc_servers + become: true + roles: + - gre_tunnels