diff --git a/vpn/base/tasks/main.yml b/vpn/base/tasks/main.yml index 56ef25c..96e025b 100644 --- a/vpn/base/tasks/main.yml +++ b/vpn/base/tasks/main.yml @@ -13,6 +13,47 @@ sysctl_file: "/etc/sysctl.d/local.conf" reload: true +- name: "keep ipv6 addresses on link down" + ansible.posix.sysctl: + name: "net.ipv6.conf.all.keep_addr_on_down" + value: "1" + sysctl_file: "/etc/sysctl.d/local.conf" + reload: true + +- name: "create network directory hierarchy" + ansible.builtin.file: + path: "{{ system_etc_root_directory }}/{{ item }}" + state: "directory" + mode: 0755 + loop: + - "network" + - "network/utils" + - "network/interfaces" + +- name: "ifupdown script" + ansible.builtin.template: + src: "./ifupdown.sh" + dest: "{{ system_etc_root_directory }}/network/utils/ifupdown.sh" + mode: 0755 + +- name: "symlink ifupdown script to /etc/network" + ansible.builtin.file: + src: "{{ system_etc_root_directory }}/network/utils/ifupdown.sh" + dest: "/etc/network/{{ item }}/the-nine-worlds" + state: "link" + loop: + - "if-pre-up.d" + - "if-up.d" + - "if-down.d" + - "if-post-down.d" + +- name: "ensure bridge-utils is not installed" + ansible.builtin.apt: + name: "bridge-utils" + state: "absent" + purge: true + +# TODO: remove once all interfaces have been moved. - name: "script for creating virtual interfaces" ansible.builtin.copy: src: "./ip-link-add.sh" diff --git a/vpn/base/templates/ifupdown.sh b/vpn/base/templates/ifupdown.sh new file mode 100755 index 0000000..74b0a05 --- /dev/null +++ b/vpn/base/templates/ifupdown.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +# Do not run if the deprecated bridge scripts are present. +if [ -f "$(dirname ${0})/bridge" ] +then + echo "$(basename ${0}):"\ + "ifupdown scripts from bridge-utils interfere with the-nine-worlds scripts" 1>&2 + exit 1 +fi + +IFDIR={{ system_etc_root_directory }}/network/interfaces/${IFACE} +IFUPDIR=${IFDIR}/ifup.d +IFDOWNDIR=${IFDIR}/ifdown.d + +if [ "${MODE}" == "start" ] && [ -d ${IFUPDIR} ] +then + /bin/run-parts --exit-on-error ${IFUPDIR} +elif [ "${MODE}" == "stop" ] && [ -d ${IFDOWNDIR} ] +then + /bin/run-parts --reverse ${IFDOWNDIR} +fi diff --git a/vpn/bridge/files/br0 b/vpn/bridge/files/br0 new file mode 100644 index 0000000..a956d2e --- /dev/null +++ b/vpn/bridge/files/br0 @@ -0,0 +1,2 @@ +auto br0 +iface br0 inet6 manual diff --git a/vpn/bridge/files/pre-down-br0-inet.nft b/vpn/bridge/files/pre-down-br0-inet.nft deleted file mode 100644 index e7b5064..0000000 --- a/vpn/bridge/files/pre-down-br0-inet.nft +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env -S nft -f - -flush table inet br0_inet -delete table inet br0_inet diff --git a/vpn/bridge/files/pre-down-br0-ipv4.nft b/vpn/bridge/files/pre-down-br0-ipv4.nft deleted file mode 100644 index 34d95a9..0000000 --- a/vpn/bridge/files/pre-down-br0-ipv4.nft +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env -S nft -f - -flush table ip br0_ipv4 -delete table ip br0_ipv4 diff --git a/vpn/bridge/files/pre-down-br0-ipv6.nft b/vpn/bridge/files/pre-down-br0-ipv6.nft deleted file mode 100644 index 5c5251e..0000000 --- a/vpn/bridge/files/pre-down-br0-ipv6.nft +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env -S nft -f - -flush table ip6 br0_ipv6 -delete table ip6 br0_ipv6 diff --git a/vpn/bridge/tasks/main.yml b/vpn/bridge/tasks/main.yml index 89103f3..f4df354 100644 --- a/vpn/bridge/tasks/main.yml +++ b/vpn/bridge/tasks/main.yml @@ -1,27 +1,39 @@ --- -- name: "post-up nftables scripts" - ansible.builtin.template: - src: "./{{ item }}" - dest: "/usr/local/sbin/{{ item }}" +- name: "create interface directory hierarchy" + ansible.builtin.file: + path: "{{ system_etc_root_directory }}/network/interfaces/{{ item }}" + state: "directory" mode: 0755 loop: - - "post-up-br0-inet.nft" - - "post-up-br0-ipv4.nft" - - "post-up-br0-ipv6.nft" - register: vpn_bridge_post_up_nft + - "br0" + - "br0/ifup.d" + - "br0/ifdown.d" + - "br0/nftables" + +- name: "nftables up script" + ansible.builtin.template: + src: "./nftables/up.nft" + dest: "{{ system_etc_root_directory }}/network/interfaces/br0/nftables/up.nft" + mode: 0644 + register: vpn_bridge_nftables_up + +- name: "interface up scripts" + ansible.builtin.template: + src: "./ifupdown.d/{{ item }}" + dest: "{{ system_etc_root_directory }}/network/interfaces/br0/ifup.d/{{ item }}" + mode: 0755 + loop: + - "00-interface" + - "10-nftables" + - "20-routes" + register: vpn_bridge_interface_up - name: "configure interface" - ansible.builtin.template: + ansible.builtin.copy: src: "./br0" dest: "/etc/network/interfaces.d/br0" mode: 0644 - validate: > - bash -c - 'if ! diff %s /etc/network/interfaces.d/br0 && ip link show dev br0 ; - then - ifdown br0 ; - fi' - register: vpn_bridge_intf + register: vpn_bridge_interface_file - name: "restart interface" ansible.builtin.shell: | @@ -32,15 +44,22 @@ ifup br0 fi when: - vpn_bridge_post_up_nft.changed or - vpn_bridge_intf.changed + vpn_bridge_nftables_up.changed or + vpn_bridge_interface_up.changed or + vpn_bridge_interface_file.changed -- name: "pre-down nftables scripts" - ansible.builtin.copy: - src: "./{{ item }}" - dest: "/usr/local/sbin/{{ item }}" +- name: "nftables down script" + ansible.builtin.template: + src: "./nftables/down.nft" + dest: "{{ system_etc_root_directory }}/network/interfaces/br0/nftables/down.nft" + mode: 0644 + +- name: "interface down scripts" + ansible.builtin.template: + src: "./ifupdown.d/{{ item }}" + dest: "{{ system_etc_root_directory }}/network/interfaces/br0/ifdown.d/{{ item }}" mode: 0755 loop: - - "pre-down-br0-inet.nft" - - "pre-down-br0-ipv4.nft" - - "pre-down-br0-ipv6.nft" + - "00-interface" + - "10-nftables" + - "20-routes" diff --git a/vpn/bridge/templates/br0 b/vpn/bridge/templates/br0 deleted file mode 100644 index 04191b9..0000000 --- a/vpn/bridge/templates/br0 +++ /dev/null @@ -1,40 +0,0 @@ -auto br0 -iface br0 inet6 static - pre-up /usr/local/sbin/ip-link-add.sh $IFACE type bridge - - post-up /usr/local/sbin/post-up-$IFACE-inet.nft - post-up /usr/local/sbin/post-up-$IFACE-ipv6.nft -{% if vpn_bridge_routing_table is defined %} - post-up ip -6 rule add dev $IFACE table {{ vpn_bridge_routing_table }} - post-up ip -6 rule add dev $IFACE to {{ local_inet6_network }} table main priority 1 -{% endif %} - -{% if vpn_bridge_routing_table is defined %} - pre-down ip -6 rule del dev $IFACE to {{ local_inet6_network }} table main priority 1 - pre-down ip -6 rule del dev $IFACE table {{ vpn_bridge_routing_table }} -{% endif %} - pre-down /usr/local/sbin/pre-down-$IFACE-ipv6.nft - pre-down /usr/local/sbin/pre-down-$IFACE-inet.nft - - bridge_stp off - bridge_waitport 0 - bridge_fd 0 - bridge_ports none - - address {{ vpn_bridge_inet6_address }}/{{ vpn_bridge_inet6_prefixlen }} - dad-attempts 0 - -iface br0 inet static - post-up /usr/local/sbin/post-up-$IFACE-ipv4.nft -{% if vpn_bridge_routing_table is defined %} - post-up ip rule add dev $IFACE table {{ vpn_bridge_routing_table }} - post-up ip rule add dev $IFACE to {{ local_inet_network }} table main priority 1 -{% endif %} - -{% if vpn_bridge_routing_table is defined %} - pre-down ip rule del dev $IFACE to {{ local_inet_network }} table main priority 1 - pre-down ip rule del dev $IFACE table {{ vpn_bridge_routing_table }} -{% endif %} - pre-down /usr/local/sbin/pre-down-$IFACE-ipv4.nft - - address {{ vpn_bridge_inet_address }}/{{ vpn_bridge_inet_prefixlen }} diff --git a/vpn/bridge/templates/ifupdown.d/00-interface b/vpn/bridge/templates/ifupdown.d/00-interface new file mode 100755 index 0000000..4d9dd03 --- /dev/null +++ b/vpn/bridge/templates/ifupdown.d/00-interface @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +if [ ${MODE} == "start" ] +then + set -ue +elif [ ${MODE} == "stop" ] +then + set -u +else + echo "$(basename ${0}): mode must be one of either 'start' or 'stop'" 1>&2 + exit 1 +fi + +case ${PHASE} in + "pre-up") + /usr/sbin/ip link add ${IFACE} type bridge + /usr/sbin/ip link set ${IFACE} type bridge forward_delay 0 + + /usr/sbin/sysctl -q -w net.ipv6.conf.br0.autoconf=0 + + /usr/sbin/ip -4 address add {{ vpn_bridge_inet_address }}/{{ vpn_bridge_inet_prefixlen }} dev ${IFACE} + /usr/sbin/ip -6 address add {{ vpn_bridge_inet6_address }}/{{ vpn_bridge_inet6_prefixlen }} dev ${IFACE} nodad + ;; + "post-up") + ;; + "pre-down") + ;; + "post-down") + /usr/sbin/ip -6 address flush dev ${IFACE} + /usr/sbin/ip -4 address flush dev ${IFACE} + + /usr/sbin/ip link delete ${IFACE} + ;; +esac diff --git a/vpn/bridge/templates/ifupdown.d/10-nftables b/vpn/bridge/templates/ifupdown.d/10-nftables new file mode 100755 index 0000000..746ca32 --- /dev/null +++ b/vpn/bridge/templates/ifupdown.d/10-nftables @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +if [ ${MODE} == "start" ] +then + set -ue +elif [ ${MODE} == "stop" ] +then + set -u +else + echo "$(basename ${0}): mode must be one of either 'start' or 'stop'" 1>&2 + exit 1 +fi + +IFDIR={{ system_etc_root_directory }}/network/interfaces/${IFACE} + +case ${PHASE} in + "pre-up") + /usr/bin/envsubst '${IFACE}' < ${IFDIR}/nftables/up.nft | /usr/sbin/nft -f /dev/stdin + ;; + "post-up") + ;; + "pre-down") + ;; + "post-down") + /usr/bin/envsubst '${IFACE}' < ${IFDIR}/nftables/down.nft | /usr/sbin/nft -f /dev/stdin + ;; +esac diff --git a/vpn/bridge/templates/ifupdown.d/20-routes b/vpn/bridge/templates/ifupdown.d/20-routes new file mode 100755 index 0000000..0b49eb2 --- /dev/null +++ b/vpn/bridge/templates/ifupdown.d/20-routes @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +if [ ${MODE} == "start" ] +then + set -ue +elif [ ${MODE} == "stop" ] +then + set -u +else + echo "$(basename ${0}): mode must be one of either 'start' or 'stop'" 1>&2 + exit 1 +fi + +case ${PHASE} in + "pre-up") +{% if vpn_bridge_routing_table is defined %} + /usr/sbin/ip -4 rule add dev $IFACE table {{ vpn_bridge_routing_table }} + /usr/sbin/ip -6 rule add dev $IFACE table {{ vpn_bridge_routing_table }} + + /usr/sbin/ip -4 rule add dev $IFACE to {{ local_inet_network }} table main priority 1 + /usr/sbin/ip -6 rule add dev $IFACE to {{ local_inet6_network }} table main priority 1 +{% endif %} + ;; + "post-up") + ;; + "pre-down") + ;; + "post-down") +{% if vpn_bridge_routing_table is defined %} + /usr/sbin/ip -6 rule del dev $IFACE to {{ local_inet6_network }} table main priority 1 + /usr/sbin/ip -4 rule del dev $IFACE to {{ local_inet_network }} table main priority 1 + + /usr/sbin/ip -6 rule del dev $IFACE table {{ vpn_bridge_routing_table }} + /usr/sbin/ip -4 rule del dev $IFACE table {{ vpn_bridge_routing_table }} +{% endif %} + ;; +esac diff --git a/vpn/bridge/templates/nftables/down.nft b/vpn/bridge/templates/nftables/down.nft new file mode 100644 index 0000000..2431485 --- /dev/null +++ b/vpn/bridge/templates/nftables/down.nft @@ -0,0 +1,8 @@ +flush table ip6 ${IFACE}_ipv6 +delete table ip6 ${IFACE}_ipv6 + +flush table ip ${IFACE}_ipv4 +delete table ip ${IFACE}_ipv4 + +flush table inet ${IFACE}_inet +delete table inet ${IFACE}_inet diff --git a/vpn/bridge/templates/nftables/up.nft b/vpn/bridge/templates/nftables/up.nft new file mode 100644 index 0000000..d9b73fc --- /dev/null +++ b/vpn/bridge/templates/nftables/up.nft @@ -0,0 +1,57 @@ +#!/usr/bin/env -S nft -f + +table inet ${IFACE}_inet { + chain postrouting { + type nat hook postrouting priority 100; + iif ${IFACE} oif { {{ [ + ansible_default_ipv4.interface | default(ansible_default_ipv6.interface), + ansible_default_ipv6.interface | default(ansible_default_ipv4.interface) + ] | unique | join(", ") }} } masquerade; + } +} + +table ip ${IFACE}_ipv4 { + chain prerouting { + type nat hook prerouting priority -100; +{% for forward in vpn_bridge_dnat %} + iif {{ ansible_default_ipv4.interface | default(ansible_default_ipv6.interface) }} tcp dport { {{ forward.ports | join(", ") }} } dnat to {{ forward.inet_address }}; +{% endfor %} + } + + chain forward { + type filter hook forward priority 0; +{% if local_inet_network is defined %} + + ct state established,related accept; + iif ${IFACE} ip daddr {{ local_inet_network }} drop; +{% endif %} +{% if vpn_bridge_local_only_inet_daddr %} + + # Drop all external traffic for these addresses. + ip saddr != {{ vpn_bridge_inet_subnet }} ip daddr { {{ vpn_bridge_local_only_inet_daddr | join(", ") }} } drop; +{% endif %} + } +} + +table ip6 ${IFACE}_ipv6 { + chain prerouting { + type nat hook prerouting priority -100; +{% for forward in vpn_bridge_dnat %} + iif {{ ansible_default_ipv6.interface | default(ansible_default_ipv4.interface) }} tcp dport { {{ forward.ports | join(", ") }} } dnat to {{ forward.inet6_address }}; +{% endfor %} + } + + chain forward { + type filter hook forward priority 0; +{% if local_inet6_network is defined %} + + ct state established,related accept; + iif ${IFACE} ip6 daddr {{ local_inet6_network }} drop; +{% endif %} +{% if vpn_bridge_local_only_inet6_daddr %} + + # Drop all external traffic for these addresses. + ip6 saddr != {{ vpn_bridge_inet6_subnet }} ip6 daddr { {{ vpn_bridge_local_only_inet6_daddr | join(", ") }} } drop; +{% endif %} + } +} diff --git a/vpn/bridge/templates/post-up-br0-inet.nft b/vpn/bridge/templates/post-up-br0-inet.nft deleted file mode 100644 index 5d22b36..0000000 --- a/vpn/bridge/templates/post-up-br0-inet.nft +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env -S nft -f - -table inet br0_inet { - chain postrouting { - type nat hook postrouting priority 100; - iif br0 oif { {{ [ansible_default_ipv4.interface, ansible_default_ipv6.interface] | unique | join(", ") }} } masquerade; - } -} diff --git a/vpn/bridge/templates/post-up-br0-ipv4.nft b/vpn/bridge/templates/post-up-br0-ipv4.nft deleted file mode 100644 index ac1342c..0000000 --- a/vpn/bridge/templates/post-up-br0-ipv4.nft +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env -S nft -f - -table ip br0_ipv4 { - chain prerouting { - type nat hook prerouting priority -100; -{% for forward in vpn_bridge_dnat %} - iif {{ ansible_default_ipv4.interface }} tcp dport { {{ forward.ports | join(", ") }} } dnat to {{ forward.inet_address }}; -{% endfor %} - } - - chain forward { - type filter hook forward priority 0; -{% if local_inet_network is defined %} - - ct state established,related accept; - iif br0 ip daddr {{ local_inet_network }} drop; -{% endif %} -{% if vpn_bridge_local_only_inet_daddr %} - - # Drop all external traffic for these addresses. - ip saddr != {{ vpn_bridge_inet_subnet }} ip daddr { {{ vpn_bridge_local_only_inet_daddr | join(", ") }} } drop; -{% endif %} - } -} diff --git a/vpn/bridge/templates/post-up-br0-ipv6.nft b/vpn/bridge/templates/post-up-br0-ipv6.nft deleted file mode 100644 index c5414e3..0000000 --- a/vpn/bridge/templates/post-up-br0-ipv6.nft +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env -S nft -f - -table ip6 br0_ipv6 { - chain prerouting { - type nat hook prerouting priority -100; -{% for forward in vpn_bridge_dnat %} - iif {{ ansible_default_ipv6.interface }} tcp dport { {{ forward.ports | join(", ") }} } dnat to {{ forward.inet6_address }}; -{% endfor %} - } - - chain forward { - type filter hook forward priority 0; -{% if local_inet6_network is defined %} - - ct state established,related accept; - iif br0 ip6 daddr {{ local_inet6_network }} drop; -{% endif %} -{% if vpn_bridge_local_only_inet6_daddr %} - - # Drop all external traffic for these addresses. - ip6 saddr != {{ vpn_bridge_inet6_subnet }} ip6 daddr { {{ vpn_bridge_local_only_inet6_daddr | join(", ") }} } drop; -{% endif %} - } -}