All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] wg-quick: add embedded-friendly POSIX-shell version
@ 2021-06-16 15:52 Rowan Thorpe
  0 siblings, 0 replies; only message in thread
From: Rowan Thorpe @ 2021-06-16 15:52 UTC (permalink / raw)
  To: wireguard

I had to do extreme configuring to get the latest libreCMC release onto my old
(not officially supported any more, 4MB disk) router in order to have Wireguard
available & usable, and although I succeeded it resulted in a very bare-bones
kernel+OS, even by embedded standards. Aside from Bash obviously being missing
(Busybox provides Ash) I also had to disable some of the usual Busybox & POSIX
configuration options.

I still wanted to use wg-quick though (and assumed others on very small systems
may want it too) so I derived an "embedded-friendly posix shellscript"
variant (with
shims/monkeypatching for missing or deficient executables and for missing
Bash-specific builtin functionality). Usually I wouldn't bother but this is one
rare case where I think it is justified.

I kept its structure as equivalent (and some code identical) to the linux Bash
variant as possible, in the hopes it minimises added maintenance-burden.
Because it has "lowest common denominator" requirements it can be
parity-tested in the same environment as the other variants (but not
fully tested
there for its unique parts of course). I made it opt-in as one of the "wg-quick"
variants by Make env-var (WITH_EMBEDDED + WITH_WGQUICK), but perhaps
the preference is just to keep it separate, named something like "wg-quick-mini"
in the contrib directory..? If so let me know & I'll re-send in that form.

Signed-off-by: Rowan Thorpe <rowan@rowanthorpe.com>
---
 src/Makefile          |  12 +-
 src/wg-quick/posix.sh | 880 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 890 insertions(+), 2 deletions(-)
 create mode 100755 src/wg-quick/posix.sh

diff --git a/src/Makefile b/src/Makefile
index 7b8969a..50553e6 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -15,6 +15,7 @@ RUNSTATEDIR ?= /var/run
 WITH_BASHCOMPLETION ?=
 WITH_WGQUICK ?=
 WITH_SYSTEMDUNITS ?=
+WITH_EMBEDDED ?=

 ifeq ($(WITH_BASHCOMPLETION),)
 ifneq ($(strip $(wildcard $(BASHCOMPDIR))),)
@@ -28,6 +29,9 @@ endif
 ifneq ($(strip $(wildcard $(DESTDIR)/bin/bash)),)
 WITH_WGQUICK := yes
 endif
+ifeq ($(WITH_EMBEDDED),yes)
+WITH_WGQUICK := yes
+endif
 endif
 ifeq ($(WITH_SYSTEMDUNITS),)
 ifneq ($(strip $(wildcard $(SYSTEMDUNITDIR))),)
@@ -91,10 +95,14 @@ install: wg
     @[ "$(WITH_BASHCOMPLETION)" = "yes" ] || exit 0; \
     install -v -d "$(DESTDIR)$(BASHCOMPDIR)" && install -v -m 0644
completion/wg.bash-completion "$(DESTDIR)$(BASHCOMPDIR)/wg"
     @[ "$(WITH_WGQUICK)" = "yes" ] || exit 0; \
-    install -v -m 0755 wg-quick/$(PLATFORM).bash
"$(DESTDIR)$(BINDIR)/wg-quick" && install -v -m 0700 -d
"$(DESTDIR)$(SYSCONFDIR)/wireguard"
+    if [ "$(WITH_EMBEDDED)" = "yes" ]; then \
+    install -v -m 0755 wg-quick/posix.sh "$(DESTDIR)$(BINDIR)/wg-quick"; \
+    else \
+    install -v -m 0755 wg-quick/$(PLATFORM).bash
"$(DESTDIR)$(BINDIR)/wg-quick"; \
+    fi && install -v -m 0700 -d "$(DESTDIR)$(SYSCONFDIR)/wireguard"
     @[ "$(WITH_WGQUICK)" = "yes" ] || exit 0; \
     install -v -m 0644 man/wg-quick.8 "$(DESTDIR)$(MANDIR)/man8/wg-quick.8"
-    @[ "$(WITH_WGQUICK)" = "yes" -a "$(WITH_BASHCOMPLETION)" = "yes"
] || exit 0; \
+    @[ "$(WITH_WGQUICK)" = "yes" -a "$(WITH_BASHCOMPLETION)" = "yes"
-a "$(WITH_EMBEDDED)" != "yes" ] || exit 0; \
     install -v -m 0644 completion/wg-quick.bash-completion
"$(DESTDIR)$(BASHCOMPDIR)/wg-quick"
     @[ "$(WITH_WGQUICK)" = "yes" -a "$(WITH_SYSTEMDUNITS)" = "yes" ]
|| exit 0; \
     install -v -d "$(DESTDIR)$(SYSTEMDUNITDIR)" && install -v -m 0644
systemd/* "$(DESTDIR)$(SYSTEMDUNITDIR)/"
diff --git a/src/wg-quick/posix.sh b/src/wg-quick/posix.sh
new file mode 100755
index 0000000..2ecdcbf
--- /dev/null
+++ b/src/wg-quick/posix.sh
@@ -0,0 +1,880 @@
+#!/bin/sh
+# -*- mode: sh; sh-indentation: 2; sh-basic-offset: 2;
indent-tabs-mode: nil; fill-column: 100; coding: utf-8-unix; -*-
+#
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All
Rights Reserved.
+# Austere posix/embedded variant derived by Rowan Thorpe
<rowan@rowanthorpe.com>, 2021.
+#
+# Sanity checked with:
+#  + shellcheck --check-sourced --external-sources --enable=all
--shell=sh posix.sh
+#  + shfmt -d -ln posix -i 2 -ci posix.sh
+#
+# TODO:
+#  * Create local-vars drop-in functionality (without exploding complexity) to
+#    ensure recursion doesn't shadow vars.
+
+set -e
+
+## setup needed before function-definitions
+
+trap - EXIT
+trap 'exit 1' HUP INT QUIT TERM
+
+# primitive exit-trap stack to keep things manageable
+exit_trap() {
+  case "${1}" in
+    push) EXIT_TRAP="${2}${EXIT_TRAP:+${NL}${EXIT_TRAP}}" ;;
+    pop) EXIT_TRAP="$(printf '%s\n' "${EXIT_TRAP}" | tail -n +2)" ;;
+    *) exit 1 ;;
+  esac
+  #shellcheck disable=SC2064
+  trap "${EXIT_TRAP:--}" EXIT
+}
+
+# embedded systems without char-classes in "tr" need monkeypatching
+if ! [ "$(printf 'aBcD' | tr '[:upper:]' '[:lower:]')" = 'abcd' ]; then
+  REAL_TR="$(command -v tr 2>/dev/null)"
+  tr() {
+    args=''
+    while [ "${#}" -ne 0 ]; do
+      case "${1}" in
+        '[:upper:]')
+          args="${args:+${args} }$(entity_save '[A-Z]')"
+          ;;
+        '[:lower:]')
+          args="${args:+${args} }$(entity_save '[a-z]')"
+          ;;
+        *)
+          args="${args:+${args} }$(entity_save "${1}")"
+          ;;
+      esac
+      shift
+    done
+    eval "${REAL_TR} ${args}"
+    unset args
+  }
+fi
+
+# POSIX shells _may_ not have "type -p" so we need this drop-in
+#shellcheck disable=SC2039
+if [ -n "$(type -p cat 2>/dev/null || :)" ]; then
+  type_p() {
+    type -p "${@}"
+  }
+else
+  type_p() {
+    ret=0
+    for arg; do
+      found=0
+      for path in $(printf %s "${PATH-}" | tr ':' ' '); do
+        if [ -x "${path}/${arg}" ]; then
+          found=1
+          break
+        fi
+      done
+      if [ "${found}" -eq 1 ]; then
+        printf '%s/%s' "${path}" "${arg}"
+      else
+        ret=1
+      fi
+    done
+    unset arg found path
+    if [ "${ret}" -eq 0 ]; then
+      unset ret
+      return 0
+    else
+      unset ret
+      return 1
+    fi
+  }
+fi
+
+# embedded systems without "stat" need this drop-in
+if command -v stat >/dev/null 2>&1; then
+  stat_octal() {
+    stat -c '%04a' "${@}"
+  }
+else
+  stat_octal() {
+    #shellcheck disable=SC2012 disable=SC2034
+    ls -l "${@}" |
+      sed -ne '
+        s/^[-dsbclp]\([-r]\)\([-w]\)\([-xsStT]\)\([-r]\)\([-w]\)\([-xsStT]\)\([-r]\)\([-w]\)\([-xsStT]\)
.*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/g
+        t P
+        b
+        : P
+        p
+      ' |
+      while read -r ur uw ux gr gw gx or ow ox; do
+        out=''
+        spc_sum=0
+        for ctg in u g o; do
+          sum=0
+          for perm in r w x; do
+            var="${ctg}${perm}"
+            eval "val=\"\${${var}}\""
+            #shellcheck disable=SC2154
+            case "${val}" in
+              r) exp=2 ;;
+              w) exp=1 ;;
+              s | t | x) exp=0 ;;
+              - | S | T) exp=-1 ;;
+              *) exit 1 ;;
+            esac
+            case "${val}" in
+              - | w | r | x)
+                spc_exp=-1
+                ;;
+              S | s)
+                case "${var}" in
+                  u*) spc_exp=2 ;;
+                  g*) spc_exp=1 ;;
+                  *) exit 1 ;;
+                esac
+                ;;
+              T | t)
+                case "${var}" in
+                  o*) spc_exp=0 ;;
+                  *) exit 1 ;;
+                esac
+                ;;
+              *)
+                exit 1
+                ;;
+            esac
+            [ "${exp}" -lt 0 ] ||
+              sum=$((sum + $((1 << exp))))
+            [ "${spc_exp}" -lt 0 ] ||
+              spc_sum=$((spc_sum + $((1 << spc_exp))))
+          done
+          out="${out}$(printf %o "${sum}")"
+        done
+        printf '%o%s\n' "${spc_sum}" "${out}"
+      done
+    unset ur uw ux gr gw gx or ow ox ctg spc_sum out perm sum var val
exp spc_exp
+  }
+fi
+
+##
+
+e_body_save() { sed -e "s/'/'\\\\''/g"; }
+
+e_head_save() { sed -e "1s/^/'/"; }
+
+e_tail_save() { sed -e "\$s/\$/'/"; }
+
+e_save() { e_body_save | e_head_save | e_tail_save; }
+
+a_e_wrap() { sed -e '$s/$/ \\/'; }
+
+a_wrap() { sed -e '$s/$/\n /'; }
+
+entity_save() { printf '%s\n' "${1}" | e_save; }
+
+array_save() {
+  for i; do
+    entity_save "${i}" | a_e_wrap
+  done |
+    a_wrap
+  unset i
+}
+
+array_append() {
+  orig_name="${1}"
+  shift
+  new=$(array_save "${@}")
+  eval "
+    eval \"set -- \${${orig_name}}\"
+    set -- \"\${@}\" ${new}
+    ${orig_name}=\$(array_save \"\${@}\")
+  "
+  unset orig_name new
+}
+
+get_mtu() {
+  output="${1}"
+  existing_mtu="${2}"
+  shift 2
+  mtu_match=''
+  dev_match=''
+  mtu_match="$(printf %s "${output}" | sed -ne 's:^.*\<mtu
\([0-9]\+\)\>.*$:\1:; t P; b; : P; p; q')"
+  if [ -z "${mtu_match}" ]; then
+    dev_match="$(printf %s "${output}" | sed -ne 's:^.*\<dev \([^
]\+\)\>.*$:\1:; t P; b; : P; p; q')"
+    [ -z "${dev_match}" ] ||
+      mtu_match="$(ip link show dev "${dev_match}" | sed -ne
's:^.*\<mtu \([0-9]\+\)\>.*$:\1:; t P; b; : P; p; q')"
+  fi
+  if [ -n "${mtu_match}" ] &&
+    [ "${mtu_match}" -gt "${existing_mtu}" ]; then
+    printf %s "${mtu_match}"
+  else
+    printf %s "${existing_mtu}"
+  fi
+  unset output existing_mtu mtu_match dev_match
+}
+
+##
+
+cmd() {
+  printf '[#] %s\n' "${*}" >&2
+  "${@}"
+}
+
+die() {
+  printf '%s: %s\n' "${PROGRAM}" "${*}" >&2
+  exit 1
+}
+
+parse_options() {
+  interface_section=0
+  line=''
+  key=''
+  value=''
+  stripped=''
+  v=''
+  header_line=0
+  CONFIG_FILE="${1}"
+  #shellcheck disable=SC2003
+  ! expr match "${CONFIG_FILE}" '[a-zA-Z0-9_=+.-]\{1,15\}$' >/dev/null ||
+    CONFIG_FILE="${CONFIG_FILE_BASE}/${CONFIG_FILE}.conf"
+  [ -e "${CONFIG_FILE}" ] ||
+    die "\`${CONFIG_FILE}' does not exist"
+  #shellcheck disable=SC2003
+  expr match "${CONFIG_FILE}"
'\(.*/\)\?\([a-zA-Z0-9_=+.-]\{1,15\}\)\.conf$' >/dev/null ||
+    die 'The config file must be a valid interface name, followed by .conf'
+  CONFIG_FILE="$(readlink -f "${CONFIG_FILE}")"
+  if {
+    stat_octal "${CONFIG_FILE}" || :
+    stat_octal "$(printf %s "${CONFIG_FILE}" | sed -e 's:/[^/]*$::')" || :
+  } 2>/dev/null | grep -vq '0$'; then
+    printf 'Warning: `%s'\'' is world accessible\n' "${CONFIG_FILE}" >&2
+  fi
+  INTERFACE="$(printf %s "${CONFIG_FILE}" | sed -e
's:^\(.*/\)\?\([^/.]\+\)\.conf$:\2:')"
+  while read -r line || [ -n "${line}" ]; do
+    stripped="$(printf %s "${line}" | sed -e 's:#.*$::; /^[[:blank:]]*$/d')"
+    key="$(printf %s "${stripped}" | sed -e
's#^[[:blank:]]*\([^=[:blank:]]\+\)[[:blank:]]*=.*$#\1#')"
+    case "${key}" in
+      '['*)
+        if [ "${key}" = '[Interface]' ]; then
+          interface_section=1
+        else
+          interface_section=0
+        fi
+        header_line=1
+        ;;
+      *)
+        header_line=0
+        ;;
+    esac
+    if [ "${header_line}" -eq 0 ] && [ "${interface_section}" -eq 1 ]; then
+      value="$(
+        printf %s "${stripped}" |
+          sed -e
's#^[^=]\+=[[:blank:]]*\([^[:blank:]]\(.*[^[:blank:]]\)\?\)\?[[:blank:]]*$#\1#'
+      )"
+      case "$(printf %s "${key}" | tr '[:upper:]' '[:lower:]')" in
+        address)
+          #shellcheck disable=SC2046
+          array_append ADDRESSES $(printf %s "${value}" | tr ',' ' ')
+          continue
+          ;;
+        mtu)
+          MTU="${value}"
+          continue
+          ;;
+        dns)
+          for v in $(printf %s "${value}" | tr ',' ' '); do
+            #shellcheck disable=SC2003
+            if expr match "${v}" '[0-9.]\+$' >/dev/null || expr match
"${v}" '.*:.*$' >/dev/null; then
+              array_append DNS "${v}"
+            else
+              array_append DNS_SEARCH "${v}"
+            fi
+          done
+          continue
+          ;;
+        table)
+          TABLE="${value}"
+          continue
+          ;;
+        preup)
+          array_append PRE_UP "${value}"
+          continue
+          ;;
+        predown)
+          array_append PRE_DOWN "${value}"
+          continue
+          ;;
+        postup)
+          array_append POST_UP "${value}"
+          continue
+          ;;
+        postdown)
+          array_append POST_DOWN "${value}"
+          continue
+          ;;
+        saveconfig)
+          read_bool SAVE_CONFIG "${value}"
+          continue
+          ;;
+        *)
+          :
+          ;;
+      esac
+    fi
+    WG_CONFIG="${WG_CONFIG:+${WG_CONFIG}${NL}}${line}"
+  done <"${CONFIG_FILE}"
+  unset interface_section line key value stripped v header_line
+}
+
+read_bool() {
+  case "${2}" in
+    true) eval "${1}=1" ;;
+    false) eval "${1}=0" ;;
+    *) die "\`${2}' is neither true nor false" ;;
+  esac
+}
+
+#shellcheck disable=SC2120
+auto_su() {
+  if [ "${UID}" -ne 0 ]; then
+    eval "set -- ${ARGS}"
+    exec sudo -p "${PROGRAM} must be run as root. Please enter the
password for %u to continue: " -- \
+      "${SHELL:-/bin/sh}" -- "${SELF}" "${@}"
+  fi
+}
+
+add_if() {
+  ret=0
+  if ! cmd ip link add "${INTERFACE}" type wireguard; then
+    ret=${?}
+    ! [ -e /sys/module/wireguard ] && command -v
"${WG_QUICK_USERSPACE_IMPLEMENTATION:-wireguard-go}" >/dev/null ||
+      exit "${ret}"
+    printf '[!] Missing WireGuard kernel module. Falling back to slow
userspace implementation.\n' >&2
+    cmd "${WG_QUICK_USERSPACE_IMPLEMENTATION:-wireguard-go}" "${INTERFACE}"
+  fi
+  unset ret
+}
+
+del_if() {
+  table=''
+  [ "${HAVE_SET_DNS-0}" -eq 0 ] || unset_dns
+  [ "${HAVE_SET_FIREWALL-0}" -eq 0 ] || remove_firewall
+  #shellcheck disable=SC2003
+  if [ -z "${TABLE}" ] ||
+    [ "x${TABLE}" = 'xauto' ] &&
+    get_fwmark table &&
+    expr match "$(wg show "${INTERFACE}" allowed-ips)" '.*/0\(
.*\|'"${NL}"'.*\)\?$' >/dev/null; then
+    for proto in -4 -6; do
+      while :; do
+        case "$(ip "${proto}" rule show 2>/dev/null)" in
+          *"lookup ${table}"*)
+            cmd ip "${proto}" rule delete table "${table}"
+            ;;
+          *)
+            break
+            ;;
+        esac
+      done
+      while :; do
+        case "$(ip "${proto}" rule show 2>/dev/null)" in
+          *"from all lookup main suppress_prefixlength 0"*)
+            cmd ip "${proto}" rule delete table main suppress_prefixlength 0
+            ;;
+          *)
+            break
+            ;;
+        esac
+      done
+    done
+    unset proto
+  fi
+  cmd ip link delete dev "${INTERFACE}"
+  unset table
+}
+
+add_addr() {
+  case "${1}" in
+    *:*) proto=-6 ;;
+    *) proto=-4 ;;
+  esac
+  cmd ip "${proto}" address add "${1}" dev "${INTERFACE}"
+  unset proto
+}
+
+set_mtu_up() {
+  mtu=0
+  endpoint=''
+  v6_addr=''
+  if [ -n "${MTU}" ]; then
+    cmd ip link set mtu "${MTU}" up dev "${INTERFACE}"
+  else
+    wg show "${INTERFACE}" endpoints | {
+      while read -r _ endpoint; do
+        v6_addr="$(
+          printf %s "${endpoint}" |
+            sed -ne '
+              s%^\[\([a-z0-9:.]\+\)\]:[0-9]\+$%\1%
+              t P
+              s%^\([a-z0-9:.]\+\):[0-9]\+$%\1%
+              t P
+              b
+              : P
+              p
+            '
+        )"
+        [ -z "${v6_addr}" ] ||
+          mtu="$(get_mtu "$(ip route get "${v6_addr}" || :)" "${mtu}")"
+      done
+      [ "${mtu}" -gt 0 ] ||
+        mtu="$(get_mtu "$(ip route show default || :)" "${mtu}")"
+      [ "${mtu}" -gt 0 ] || mtu=1500
+      cmd ip link set mtu $((mtu - 80)) up dev "${INTERFACE}"
+    }
+  fi
+  unset mtu endpoint v6_addr
+}
+
+resolvconf_iface_prefix() {
+  if ! [ -f /etc/resolvconf/interface-order ]; then
+    iface=''
+    while read -r iface; do
+      #shellcheck disable=SC2003
+      expr match "${iface}" '\([A-Za-z0-9-]\+\)\*$' >/dev/null ||
+        continue
+      printf '%s\n' "${iface}" |
+        sed -e 's/\*\?$/./'
+      break
+    done </etc/resolvconf/interface-order
+    unset iface
+  fi
+}
+
+#shellcheck disable=SC2120
+set_dns() {
+  eval "set -- ${DNS}"
+  if [ ${#} -gt 0 ]; then
+    {
+      printf 'nameserver %s\n' "${@}"
+      eval "set -- ${DNS_SEARCH}"
+      [ ${#} -eq 0 ] ||
+        printf 'search %s\n' "${*}"
+    } | cmd resolvconf -a "$(resolvconf_iface_prefix)${INTERFACE}" -m 0 -x
+    HAVE_SET_DNS=1
+  fi
+}
+
+unset_dns() {
+  eval "set -- ${DNS}"
+  [ ${#} -eq 0 ] ||
+    cmd resolvconf -d "$(resolvconf_iface_prefix)${INTERFACE}" -f
+}
+
+add_route() {
+  case "${1}" in
+    *:*) proto=-6 ;;
+    *) proto=-4 ;;
+  esac
+  if [ "${TABLE}" != off ]; then
+    case "${TABLE}:${1}" in
+      :*)
+        :
+        ;;
+      auto:*)
+        cmd ip "${proto}" route add "${1}" dev "${INTERFACE}" table "${TABLE}"
+        ;;
+      *:*/0)
+        add_default "${1}"
+        ;;
+      *)
+        [ -n "$(ip "${proto}" route show dev "${INTERFACE}" match
"${1}" 2>/dev/null)" ] ||
+          cmd ip "${proto}" route add "${1}" dev "${INTERFACE}"
+        ;;
+    esac
+  fi
+  unset proto
+}
+
+get_fwmark() {
+  fwmark="$(wg show "${INTERFACE}" fwmark)" &&
+    [ -n "${fwmark}" ] &&
+    [ "x${fwmark}" != 'xoff' ] ||
+    return 1
+  eval "${1}=${fwmark}"
+  unset fwmark
+}
+
+remove_firewall() {
+  if type_p nft >/dev/null; then
+    table=''
+    nftcmd=''
+    nft list tables 2>/dev/null | {
+      while read -r table; do
+        case "${table}" in
+          *" wg-quick-${INTERFACE}")
+            nftcmd="${nftcmd:+${nftcmd}${NL}}delete ${table}"
+            ;;
+          *)
+            :
+            ;;
+        esac
+      done
+      if [ -n "${nftcmd}" ]; then
+        printf '%s\n' "${nftcmd}" |
+          cmd nft -f
+      fi
+    }
+    unset table nftcmd
+  fi
+  if type_p iptables >/dev/null; then
+    iptables=''
+    for iptables in iptables ip6tables; do
+      "${iptables}-save" 2>/dev/null | {
+        restore=''
+        found=0
+        line=''
+        while read -r line; do
+          case "${line}" in
+            \** | COMMIT | '-A '*'-m comment --comment "wg-quick(8)
rule for '"${INTERFACE}"'"'*)
+              case "${line}" in
+                -A*)
+                  found=1
+                  ;;
+                *)
+                  :
+                  ;;
+              esac
+              restore="${restore:+${restore}${NL}}-D${line#-A}"
+              ;;
+            *)
+              :
+              ;;
+          esac
+        done
+        [ "${found}" -ne 1 ] ||
+          printf '%s\n' "${restore}" |
+          cmd "${iptables}-restore" -n
+        unset restore found line
+      }
+    done
+    unset iptables
+  fi
+}
+
+add_default() {
+  table=''
+  line=''
+  proto=''
+  iptables=''
+  pf=''
+  marker=''
+  restore=''
+  nftable=''
+  nftcmd=''
+  if ! get_fwmark table; then
+    table=51820
+    while [ -n "$(ip -4 route show table "${table}" 2>/dev/null)" ] ||
+      [ -n "$(ip -6 route show table "${table}" 2>/dev/null)" ]; do
+      table=$((table + 1))
+    done
+    cmd wg set "${INTERFACE}" fwmark "${table}"
+  fi
+  case "${1}" in
+    *:*)
+      proto='-6'
+      iptables='ip6tables'
+      pf='ip6'
+      ;;
+    *)
+      proto='-4'
+      iptables='iptables'
+      pf='ip'
+      ;;
+  esac
+  cmd ip "${proto}" route add "${1}" dev "${INTERFACE}" table "${table}"
+  cmd ip "${proto}" rule add not fwmark "${table}" table "${table}"
+  cmd ip "${proto}" rule add table main suppress_prefixlength 0
+
+  marker="-m comment --comment \"wg-quick(8) rule for ${INTERFACE}\""
+  restore="*raw${NL}"
+  nftable="wg-quick-${INTERFACE}"
+  nftcmd="${nftcmd:+${nftcmd}${NL}}add table ${pf} ${nftable}"
+  nftcmd="${nftcmd:+${nftcmd}${NL}}add chain ${pf} ${nftable} preraw
{ type filter hook prerouting priority -300; }"
+  nftcmd="${nftcmd:+${nftcmd}${NL}}add chain ${pf} ${nftable}
premangle { type filter hook prerouting priority -150; }"
+  nftcmd="${nftcmd:+${nftcmd}${NL}}add chain ${pf} ${nftable}
postmangle { type filter hook postrouting priority -150; }"
+  ip -o "${proto}" addr show dev "${INTERFACE}" 2>/dev/null | {
+    match=''
+    while read -r line; do
+      match="$(
+        printf %s "${line}" |
+          sed -ne 's/^.*inet6\? \([0-9a-f:.]\+\)/[0-9]\+.*$/\1/; t P;
b; : P; p'
+      )"
+      [ -n "${match}" ] ||
+        continue
+      restore="${restore:+${restore}${NL}}-I PREROUTING ! -i
${INTERFACE} -d ${match} -m addrtype ! --src-type LOCAL -j DROP
${marker}"
+      nftcmd="${nftcmd:+${nftcmd}${NL}}add rule ${pf} ${nftable}
preraw iifname != \"${INTERFACE}\" ${pf} daddr ${match} fib saddr type
!= local drop"
+    done
+    restore="${restore:+${restore}${NL}}COMMIT${NL}*mangle${NL}-I
POSTROUTING -m mark --mark ${table} -p udp -j CONNMARK --save-mark
${marker}${NL}-I PREROUTING -p udp -j CONNMARK --restore-mark
${marker}${NL}COMMIT"
+    nftcmd="${nftcmd:+${nftcmd}${NL}}add rule ${pf} ${nftable}
postmangle meta l4proto udp mark ${table} ct mark set mark"
+    nftcmd="${nftcmd:+${nftcmd}${NL}}add rule ${pf} ${nftable}
premangle meta l4proto udp meta mark set ct mark"
+    ! [ "${proto}" = '-4' ] ||
+      cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1
+    if type_p nft >/dev/null; then
+      printf '%s\n' "${nftcmd}" |
+        cmd nft -f
+    else
+      printf '%s\n' "${restore}" |
+        cmd "${iptables}-restore" -n
+    fi
+    unset match
+  }
+  HAVE_SET_FIREWALL=1
+  unset table line proto iptables pf marker restore nftable nftcmd
+}
+
+set_config() {
+  if [ -e /dev/stdin ]; then
+    printf '%s\n' "${WG_CONFIG}" |
+      cmd wg setconf "${INTERFACE}" /dev/stdin
+  else
+    tempfile="$(mktemp)"
+    exit_trap push "rm -f \"${tempfile}\""
+    printf '%s\n' "${WG_CONFIG}" >"${tempfile}"
+    cmd wg setconf "${INTERFACE}" "${tempfile}"
+    rm -f "${tempfile}"
+    exit_trap pop
+    unset tempfile
+  fi
+}
+
+save_config() {
+  old_umask=''
+  new_config=''
+  current_config=''
+  address=''
+  cmd=''
+  addr_match="$(
+    ip -all -brief address show dev "${INTERFACE}" |
+      sed -ne 's#^'"${INTERFACE}"' \+[A-Z]\+ \+\(.\+\)$#\1#; t P; b; : P; p'
+  )"
+  new_config='[Interface]'
+  for address in ${addr_match}; do
+    new_config="${new_config:+${new_config}${NL}}Address = ${address}"
+  done
+  {
+    resolvconf -l "$(resolvconf_iface_prefix)${INTERFACE}" 2>/dev/null ||
+      cat "/etc/resolvconf/run/interface/$(resolvconf_iface_prefix)${INTERFACE}"
2>/dev/null
+  } | {
+    while read -r address; do
+      addr_match="$(
+        printf %s "${address}" |
+          sed -ne 's#^nameserver \([a-zA-Z0-9_=+:%.-]\+\)$#\1#; t P; b; : P; p'
+      )"
+      [ -z "${addr_match}" ] ||
+        new_config="${new_config:+${new_config}${NL}}DNS = ${addr_match}"
+    done
+    if [ -n "${MTU}" ]; then
+      mtu_match="$(
+        ip link show dev "${INTERFACE}" |
+          sed -ne 's/^.*mtu \([0-9]\+\).*$/\1/; t P; b; : P; p'
+      )"
+      [ -z "${mtu_match}" ] ||
+        new_config="${new_config:+${new_config}${NL}}MTU = ${mtu_match}"
+    fi
+    [ -z "${TABLE}" ] ||
+      new_config="${new_config:+${new_config}${NL}}Table = ${TABLE}"
+    [ "${SAVE_CONFIG}" -eq 0 ] ||
+      new_config="${new_config:+${new_config}${NL}}SaveConfig = true"
+    eval "set -- ${PRE_UP}"
+    for cmd; do
+      new_config="${new_config:+${new_config}${NL}}PreUp = ${cmd}"
+    done
+    eval "set -- ${POST_UP}"
+    for cmd; do
+      new_config="${new_config:+${new_config}${NL}}PostUp = ${cmd}"
+    done
+    eval "set -- ${PRE_DOWN}"
+    for cmd; do
+      new_config="${new_config:+${new_config}${NL}}PreDown = ${cmd}"
+    done
+    eval "set -- ${POST_DOWN}"
+    for cmd; do
+      new_config="${new_config:+${new_config}${NL}}PostDown = ${cmd}"
+    done
+    old_umask="$(umask)"
+    umask 077
+    current_config="$(cmd wg showconf "${INTERFACE}")"
+    exit_trap push "rm -f \"${CONFIG_FILE}.tmp\""
+    printf '%s\n' "${current_config}" |
+      sed -e "s#\\[Interface\\]\$#$(
+        printf %s "${new_config}" |
+          sed -e '$!s/$/\\n/' |
+          tr -d '\n'
+      )#" >"${CONFIG_FILE}.tmp" ||
+      die 'Could not write configuration file'
+    sync "${CONFIG_FILE}.tmp"
+    mv "${CONFIG_FILE}.tmp" "${CONFIG_FILE}" ||
+      die 'Could not move configuration file'
+    exit_trap pop
+    umask "${old_umask}"
+    unset new_config current_config old_umask cmd mtu_match addr_match address
+  }
+}
+
+execute_hooks() {
+  for hook; do
+    hook="$(
+      printf %s "${hook}" |
+        sed -e "s^%i^${INTERFACE}^g"
+    )"
+    printf '[#] %s\n' "${hook}" >&2
+    (eval "${hook}")
+  done
+  unset hook
+}
+
+cmd_usage() {
+  cat >&2 <<-_EOF
+    Usage: ${PROGRAM} [ up | down | save | strip ] [ CONFIG_FILE | INTERFACE ]
+
+      CONFIG_FILE is a configuration file, whose filename is the interface name
+      followed by \`.conf'. Otherwise, INTERFACE is an interface name, with
+      configuration found at ${CONFIG_FILE_BASE}/INTERFACE.conf. It
is to be readable
+      by wg(8)'s \`setconf' sub-command, with the exception of the
following additions
+      to the [Interface] section, which are handled by ${PROGRAM}:
+
+      - Address: may be specified one or more times and contains one or more
+        IP addresses (with an optional CIDR mask) to be set for the interface.
+      - DNS: an optional DNS server to use while the device is up.
+      - MTU: an optional MTU for the interface; if unspecified,
auto-calculated.
+      - Table: an optional routing table to which routes will be added; if
+        unspecified or \`auto', the default table is used. If \`off', no routes
+        are added.
+      - PreUp, PostUp, PreDown, PostDown: script snippets which will
be executed
+        by bash(1) at the corresponding phases of the link, most commonly used
+        to configure DNS. The string \`%i' is expanded to INTERFACE.
+      - SaveConfig: if set to \`true', the configuration is saved
from the current
+        state of the interface upon shutdown.
+
+    See wg-quick(8) for more info and examples.
+    _EOF
+}
+
+cmd_up() {
+  i=''
+  [ -z "$(ip link show dev "${INTERFACE}" 2>/dev/null)" ] ||
+    die "\`${INTERFACE}' already exists"
+  exit_trap push 'del_if'
+  eval "execute_hooks ${PRE_UP}"
+  add_if
+  set_config
+  eval "set -- ${ADDRESSES}"
+  for i; do
+    add_addr "${i}"
+  done
+  set_mtu_up
+  set_dns
+  for i in $(
+    wg show "${INTERFACE}" allowed-ips |
+      while read -r _ j; do
+        for k in ${j}; do
+          #shellcheck disable=SC2003
+          ! expr match "${k}" '[0-9a-z:.]\+/[0-9]\+$' >/dev/null ||
+            printf '%s\n' "${k}"
+        done
+      done |
+      sort -nr -k 2 -t /
+    unset j k
+  ); do
+    add_route "${i}"
+  done
+  eval "execute_hooks ${POST_UP}"
+  unset i
+  exit_trap pop
+}
+
+cmd_down() {
+  case " $(wg show interfaces) " in
+    *" ${INTERFACE} "*) : ;;
+    *) die "\`${INTERFACE}' is not a WireGuard interface" ;;
+  esac
+  eval "execute_hooks ${PRE_DOWN}"
+  [ "${SAVE_CONFIG}" -eq 0 ] ||
+    save_config
+  del_if
+  unset_dns || :
+  remove_firewall || :
+  eval "execute_hooks ${POST_DOWN}"
+}
+
+cmd_save() {
+  case " $(wg show interfaces) " in
+    *" ${INTERFACE} "*) : ;;
+    *) die "\`${INTERFACE}' is not a WireGuard interface" ;;
+  esac
+  save_config
+}
+
+cmd_strip() { printf '%s\n' "${WG_CONFIG}"; }
+
+##
+
+EXIT_TRAP=''
+LC_ALL=C
+SELF="$(readlink -f "${0}")"
+PATH="$(printf %s "${SELF}" | sed -e 's:/[^/]*$::'):${PATH}"
+export LC_ALL PATH
+[ -n "${UID-}" ] || UID="$(id -u)"
+[ -n "${CONFIG_FILE_BASE}" ] ||
+  CONFIG_FILE_BASE='/etc/wireguard'
+NL='
+'
+WG_CONFIG=''
+INTERFACE=''
+ADDRESSES=$(array_save)
+MTU=''
+DNS=$(array_save)
+DNS_SEARCH=$(array_save)
+TABLE=''
+PRE_UP=$(array_save)
+POST_UP=$(array_save)
+PRE_DOWN=$(array_save)
+POST_DOWN=$(array_save)
+SAVE_CONFIG=0
+CONFIG_FILE=''
+PROGRAM="$(printf %s "${0}" | sed -e 's:^.*/\([^/]*\)$:\1:')"
+ARGS=$(array_save "${@}")
+HAVE_SET_DNS=0
+HAVE_SET_FIREWALL=0
+
+# ~~ function override insertion point ~~
+
+case "${#}:${1}" in
+  1:--help | 1:-h | 1:help)
+    cmd_usage
+    ;;
+  2:up | 2:down | 2:save | 2:strip)
+    auto_su
+    parse_options "${2}"
+    case "${1}" in
+      up)
+        cmd_up
+        ;;
+      down)
+        cmd_down
+        ;;
+      save)
+        cmd_save
+        ;;
+      strip)
+        cmd_strip
+        ;;
+      *)
+        :
+        ;;
+    esac
+    ;;
+  *)
+    cmd_usage
+    exit 1
+    ;;
+esac
-- 
2.31.1

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2021-08-08 23:13 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-06-16 15:52 [PATCH] wg-quick: add embedded-friendly POSIX-shell version Rowan Thorpe

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.