1#!/bin/sh23# -*- mode: sh; sh-indentation: 2; sh-basic-offset: 2; indent-tabs-mode: nil; fill-column: 100; coding: utf-8-unix; -*-4#5# SPDX-License-Identifier: GPL-2.06#7# Copyright (C) 2015-2020 Jason A. Donenfeld <Jason at zx2c4.com>. All Rights Reserved.8# Austere posix/embedded variant derived by Rowan Thorpe <rowan at rowanthorpe.com>, 2021.9#10# original patch : https://lists.zx2c4.com/pipermail/wireguard/2021-June/006924.html11#12# Sanity checked with:13# + shellcheck --check-sourced --external-sources --enable=all --shell=sh posix.sh14# + shfmt -d -ln posix -i 2 -ci posix.sh15#16# TODO:17# * Create local-vars drop-in functionality (without exploding complexity) to18# ensure recursion doesn't shadow vars.1920set -e2122## setup needed before function-definitions2324trap - EXIT25trap 'exit 1' HUP INT QUIT TERM2627# primitive exit-trap stack to keep things manageable28exit_trap() {29 case "${1}" in30 push) EXIT_TRAP="${2}${EXIT_TRAP:+${NL}${EXIT_TRAP}}" ;;31 pop) EXIT_TRAP="$(printf '%s\n' "${EXIT_TRAP}" | tail -n +2)" ;;32 *) exit 1 ;;33 esac34 #shellcheck disable=SC206435 trap "${EXIT_TRAP:--}" EXIT36}3738# embedded systems without char-classes in "tr" need monkeypatching39if ! [ "$(printf 'aBcD' | tr '[:upper:]' '[:lower:]')" = 'abcd' ]; then40 REAL_TR="$(command -v tr 2>/dev/null)"41 tr() {42 args=''43 while [ "${#}" -ne 0 ]; do44 case "${1}" in45 '[:upper:]')46 args="${args:+${args} }$(entity_save '[A-Z]')"47 ;;48 '[:lower:]')49 args="${args:+${args} }$(entity_save '[a-z]')"50 ;;51 *)52 args="${args:+${args} }$(entity_save "${1}")"53 ;;54 esac55 shift56 done57 eval "${REAL_TR} ${args}"58 unset args59 }60fi6162# POSIX shells _may_ not have "type -p" so we need this drop-in63#shellcheck disable=SC203964if [ -n "$(type -p cat 2>/dev/null || :)" ]; then65 type_p() {66 type -p "${@}"67 }68else69 type_p() {70 ret=071 for arg; do72 found=073 for path in $(printf %s "${PATH-}" | tr ':' ' '); do74 if [ -x "${path}/${arg}" ]; then75 found=176 break77 fi78 done79 if [ "${found}" -eq 1 ]; then80 printf '%s/%s' "${path}" "${arg}"81 else82 ret=183 fi84 done85 unset arg found path86 if [ "${ret}" -eq 0 ]; then87 unset ret88 return 089 else90 unset ret91 return 192 fi93 }94fi9596# embedded systems without "stat" need this drop-in97if command -v stat >/dev/null 2>&1; then98 stat_octal() {99 stat -c '%04a' "${@}"100 }101else102 stat_octal() {103 #shellcheck disable=SC2012 disable=SC2034104 ls -l "${@}" |105 sed -ne '106 s/^[-dsbclp]\([-r]\)\([-w]\)\([-xsStT]\)\([-r]\)\([-w]\)\([-xsStT]\)\([-r]\)\([-w]\)\([-xsStT]\).*$/\1 \2 \3 \4 \5 \6 \7 \8 \9/g107 t P108 b109 : P110 p111 ' |112 while read -r ur uw ux gr gw gx or ow ox; do113 out=''114 spc_sum=0115 for ctg in u g o; do116 sum=0117 for perm in r w x; do118 var="${ctg}${perm}"119 eval "val=\"\${${var}}\""120 #shellcheck disable=SC2154121 case "${val}" in122 r) exp=2 ;;123 w) exp=1 ;;124 s | t | x) exp=0 ;;125 - | S | T) exp=-1 ;;126 *) exit 1 ;;127 esac128 case "${val}" in129 - | w | r | x)130 spc_exp=-1131 ;;132 S | s)133 case "${var}" in134 u*) spc_exp=2 ;;135 g*) spc_exp=1 ;;136 *) exit 1 ;;137 esac138 ;;139 T | t)140 case "${var}" in141 o*) spc_exp=0 ;;142 *) exit 1 ;;143 esac144 ;;145 *)146 exit 1147 ;;148 esac149 [ "${exp}" -lt 0 ] ||150 sum=$((sum + $((1 << exp))))151 [ "${spc_exp}" -lt 0 ] ||152 spc_sum=$((spc_sum + $((1 << spc_exp))))153 done154 out="${out}$(printf %o "${sum}")"155 done156 printf '%o%s\n' "${spc_sum}" "${out}"157 done158 unset ur uw ux gr gw gx or ow ox ctg spc_sum out perm sum var val exp spc_exp159 }160fi161162##163164e_body_save() { sed -e "s/'/'\\\\''/g"; }165166e_head_save() { sed -e "1s/^/'/"; }167168e_tail_save() { sed -e "\$s/\$/'/"; }169170e_save() { e_body_save | e_head_save | e_tail_save; }171172a_e_wrap() { sed -e '$s/$/ \\/'; }173174a_wrap() { sed -e '$s/$/\n /'; }175176entity_save() { printf '%s\n' "${1}" | e_save; }177178array_save() {179 for i; do180 entity_save "${i}" | a_e_wrap181 done |182 a_wrap183 unset i184}185186array_append() {187 orig_name="${1}"188 shift189 new=$(array_save "${@}")190 eval "191 eval \"set -- \${${orig_name}}\"192 set -- \"\${@}\" ${new}193 ${orig_name}=\$(array_save \"\${@}\")194 "195 unset orig_name new196}197198get_mtu() {199 output="${1}"200 existing_mtu="${2}"201 shift 2202 mtu_match=''203 dev_match=''204 mtu_match="$(printf %s "${output}" | sed -ne 's:^.*\<mtu \([0-9]\+\)\>.*$:\1:; t P; b; : P; p; q')"205 if [ -z "${mtu_match}" ]; then206 dev_match="$(printf %s "${output}" | sed -ne 's:^.*\<dev \([^]\+\)\>.*$:\1:; t P; b; : P; p; q')"207 [ -z "${dev_match}" ] ||208 mtu_match="$(ip link show dev "${dev_match}" | sed -ne 's:^.*\<mtu \([0-9]\+\)\>.*$:\1:; t P; b; : P; p; q')"209 fi210 if [ -n "${mtu_match}" ] &&211 [ "${mtu_match}" -gt "${existing_mtu}" ]; then212 printf %s "${mtu_match}"213 else214 printf %s "${existing_mtu}"215 fi216 unset output existing_mtu mtu_match dev_match217}218219##220221cmd() {222 printf '[#] %s\n' "${*}" >&2223 "${@}"224}225226die() {227 printf '%s: %s\n' "${PROGRAM}" "${*}" >&2228 exit 1229}230231parse_options() {232 interface_section=0233 line=''234 key=''235 value=''236 stripped=''237 v=''238 header_line=0239 CONFIG_FILE="${1}"240 #shellcheck disable=SC2003241 ! expr match "${CONFIG_FILE}" '[a-zA-Z0-9_=+.-]\{1,15\}$' >/dev/null ||242 CONFIG_FILE="${CONFIG_FILE_BASE}/${CONFIG_FILE}.conf"243 [ -e "${CONFIG_FILE}" ] ||244 die "\`${CONFIG_FILE}' does not exist"245 #shellcheck disable=SC2003246 expr match "${CONFIG_FILE}" '\(.*/\)\?\([a-zA-Z0-9_=+.-]\{1,15\}\)\.conf$' >/dev/null ||247 die 'The config file must be a valid interface name, followed by .conf'248 CONFIG_FILE="$(readlink -f "${CONFIG_FILE}")"249 if {250 stat_octal "${CONFIG_FILE}" || :251 stat_octal "$(printf %s "${CONFIG_FILE}" | sed -e 's:/[^/]*$::')" || :252 } 2>/dev/null | grep -vq '0$'; then253 printf 'Warning: `%s'\'' is world accessible\n' "${CONFIG_FILE}" >&2254 fi255 INTERFACE="$(printf %s "${CONFIG_FILE}" | sed -e 's:^\(.*/\)\?\([^/.]\+\)\.conf$:\2:')"256 while read -r line || [ -n "${line}" ]; do257 stripped="$(printf %s "${line}" | sed -e 's:#.*$::; /^[[:blank:]]*$/d')"258 key="$(printf %s "${stripped}" | sed -e 's#^[[:blank:]]*\([^=[:blank:]]\+\)[[:blank:]]*=.*$#\1#')"259 case "${key}" in260 '['*)261 if [ "${key}" = '[Interface]' ]; then262 interface_section=1263 else264 interface_section=0265 fi266 header_line=1267 ;;268 *)269 header_line=0270 ;;271 esac272 if [ "${header_line}" -eq 0 ] && [ "${interface_section}" -eq 1 ]; then273 value="$(274 printf %s "${stripped}" |275 sed -e 's#^[^=]\+=[[:blank:]]*\([^[:blank:]]\(.*[^[:blank:]]\)\?\)\?[[:blank:]]*$#\1#'276 )"277 case "$(printf %s "${key}" | tr '[:upper:]' '[:lower:]')" in278 address)279 #shellcheck disable=SC2046280 array_append ADDRESSES $(printf %s "${value}" | tr ',' ' ')281 continue282 ;;283 mtu)284 MTU="${value}"285 continue286 ;;287 dns)288 for v in $(printf %s "${value}" | tr ',' ' '); do289 #shellcheck disable=SC2003290 if expr match "${v}" '[0-9.]\+$' >/dev/null || expr match "${v}" '.*:.*$' >/dev/null; then291 array_append DNS "${v}"292 else293 array_append DNS_SEARCH "${v}"294 fi295 done296 continue297 ;;298 table)299 TABLE="${value}"300 continue301 ;;302 preup)303 array_append PRE_UP "${value}"304 continue305 ;;306 predown)307 array_append PRE_DOWN "${value}"308 continue309 ;;310 postup)311 array_append POST_UP "${value}"312 continue313 ;;314 postdown)315 array_append POST_DOWN "${value}"316 continue317 ;;318 saveconfig)319 read_bool SAVE_CONFIG "${value}"320 continue321 ;;322 *)323 :324 ;;325 esac326 fi327 WG_CONFIG="${WG_CONFIG:+${WG_CONFIG}${NL}}${line}"328 done <"${CONFIG_FILE}"329 unset interface_section line key value stripped v header_line330}331332read_bool() {333 case "${2}" in334 true) eval "${1}=1" ;;335 false) eval "${1}=0" ;;336 *) die "\`${2}' is neither true nor false" ;;337 esac338}339340#shellcheck disable=SC2120341auto_su() {342 : ${SUDO:="doas"}343 if [ "${UID}" -ne 0 ]; then344 eval "set -- ${ARGS}"345 exec $SUDO "/bin/sh" -- "${SELF}" "${@}"346 fi347}348349add_if() {350 ret=0351 if ! cmd ip link add "${INTERFACE}" type wireguard; then352 ret=${?}353 ! [ -e /sys/module/wireguard ] && command -v "${WG_QUICK_USERSPACE_IMPLEMENTATION:-wireguard-go}" >/dev/null ||354 exit "${ret}"355 printf '[!] Missing WireGuard kernel module. Falling back to slow userspace implementation.\n' >&2356 cmd "${WG_QUICK_USERSPACE_IMPLEMENTATION:-wireguard-go}" "${INTERFACE}"357 fi358 unset ret359}360361del_if() {362 table=''363 [ "${HAVE_SET_DNS-0}" -eq 0 ] || unset_dns364 [ "${HAVE_SET_FIREWALL-0}" -eq 0 ] || remove_firewall365 #shellcheck disable=SC2003366 if [ -z "${TABLE}" ] ||367 [ "x${TABLE}" = 'xauto' ] &&368 get_fwmark table &&369 expr match "$(wg show "${INTERFACE}" allowed-ips)" '.*/0\(.*\|'"${NL}"'.*\)\?$' >/dev/null; then370 for proto in -4 -6; do371 while :; do372 case "$(ip "${proto}" rule show 2>/dev/null)" in373 *"lookup ${table}"*)374 cmd ip "${proto}" rule delete table "${table}"375 ;;376 *)377 break378 ;;379 esac380 done381 while :; do382 case "$(ip "${proto}" rule show 2>/dev/null)" in383 *"from all lookup main suppress_prefixlength 0"*)384 cmd ip "${proto}" rule delete table main suppress_prefixlength 0385 ;;386 *)387 break388 ;;389 esac390 done391 done392 unset proto393 fi394 cmd ip link delete dev "${INTERFACE}"395unset table396}397398add_addr() {399 case "${1}" in400 *:*) proto=-6 ;;401 *) proto=-4 ;;402 esac403 cmd ip "${proto}" address add "${1}" dev "${INTERFACE}"404 unset proto405}406407set_mtu_up() {408 mtu=0409 endpoint=''410 v6_addr=''411 if [ -n "${MTU}" ]; then412 cmd ip link set mtu "${MTU}" up dev "${INTERFACE}"413 else414 wg show "${INTERFACE}" endpoints | {415 while read -r _ endpoint; do416 v6_addr="$(417 printf %s "${endpoint}" |418 sed -ne '419 s%^\[\([a-z0-9:.]\+\)\]:[0-9]\+$%\1%420 t P421 s%^\([a-z0-9:.]\+\):[0-9]\+$%\1%422 t P423 b424 : P425 p426 '427 )"428 [ -z "${v6_addr}" ] ||429 mtu="$(get_mtu "$(ip route get "${v6_addr}" || :)" "${mtu}")"430 done431 [ "${mtu}" -gt 0 ] ||432 mtu="$(get_mtu "$(ip route show default || :)" "${mtu}")"433 [ "${mtu}" -gt 0 ] || mtu=1500434 cmd ip link set mtu $((mtu - 80)) up dev "${INTERFACE}"435 }436 fi437 unset mtu endpoint v6_addr438}439440resolvconf_iface_prefix() {441 if ! [ -f /etc/resolvconf/interface-order ]; then442 iface=''443 while read -r iface; do444 #shellcheck disable=SC2003445 expr match "${iface}" '\([A-Za-z0-9-]\+\)\*$' >/dev/null ||446 continue447 printf '%s\n' "${iface}" |448 sed -e 's/\*\?$/./'449 break450 done </etc/resolvconf/interface-order451 unset iface452 fi453}454455#shellcheck disable=SC2120456set_dns() {457 eval "set -- ${DNS}"458 if [ ${#} -gt 0 ]; then459 {460 printf 'nameserver %s\n' "${@}"461 eval "set -- ${DNS_SEARCH}"462 [ ${#} -eq 0 ] ||463 printf 'search %s\n' "${*}"464 } | cmd resolvconf -a "$(resolvconf_iface_prefix)${INTERFACE}" -m 0 -x465 HAVE_SET_DNS=1466 fi467}468469unset_dns() {470 eval "set -- ${DNS}"471 [ ${#} -eq 0 ] ||472 cmd resolvconf -d "$(resolvconf_iface_prefix)${INTERFACE}" -f473}474475add_route() {476 case "${1}" in477 *:*) proto=-6 ;;478 *) proto=-4 ;;479 esac480 if [ "${TABLE}" != off ]; then481 case "${TABLE}:${1}" in482 auto:*)483 cmd ip "${proto}" route add "${1}" dev "${INTERFACE}" table "${TABLE}"484 ;;485 *:*/0)486 add_default "${1}"487 ;;488 *)489 [ -n "$(ip "${proto}" route show dev "${INTERFACE}" match "${1}" 2>/dev/null)" ] ||490 cmd ip "${proto}" route add "${1}" dev "${INTERFACE}"491 ;;492 esac493 fi494 unset proto495}496497get_fwmark() {498 fwmark="$(wg show "${INTERFACE}" fwmark)" &&499 [ -n "${fwmark}" ] &&500 [ "x${fwmark}" != 'xoff' ] ||501 return 1502 eval "${1}=${fwmark}"503 unset fwmark504}505506remove_firewall() {507 if type_p nft >/dev/null; then508 table=''509 nftcmd=''510 nft list tables 2>/dev/null | {511 while read -r table; do512 case "${table}" in513 *" wg-quick-${INTERFACE}")514 nftcmd="${nftcmd:+${nftcmd}${NL}}delete ${table}"515 ;;516 *)517 :518 ;;519 esac520 done521 if [ -n "${nftcmd}" ]; then522 printf '%s\n' "${nftcmd}" |523 cmd nft -f524 fi525 }526 unset table nftcmd527 fi528 if type_p iptables >/dev/null; then529 iptables=''530 for iptables in iptables ip6tables; do531 "${iptables}-save" 2>/dev/null | {532 restore=''533 found=0534 line=''535 while read -r line; do536 case "${line}" in537 \** | COMMIT | '-A '*'-m comment --comment "wg-quick(8) rule for '"${INTERFACE}"'"'*)538 case "${line}" in539 -A*)540 found=1541 ;;542 *)543 :544 ;;545 esac546 restore="${restore:+${restore}${NL}}-D${line#-A}"547 ;;548 *)549 :550 ;;551 esac552 done553 [ "${found}" -ne 1 ] ||554 printf '%s\n' "${restore}" |555 cmd "${iptables}-restore" -n556 unset restore found line557 }558 done559 unset iptables560 fi561}562563add_default() {564 table=''565 line=''566 proto=''567 iptables=''568 pf=''569 marker=''570 restore=''571 nftable=''572 nftcmd=''573 if ! get_fwmark table; then574 table=51820575 while [ -n "$(ip -4 route show table "${table}" 2>/dev/null)" ] ||576 [ -n "$(ip -6 route show table "${table}" 2>/dev/null)" ]; do577 table=$((table + 1))578 done579 cmd wg set "${INTERFACE}" fwmark "${table}"580 fi581 case "${1}" in582 *:*)583 proto='-6'584 iptables='ip6tables'585 pf='ip6'586 ;;587 *)588 proto='-4'589 iptables='iptables'590 pf='ip'591 ;;592 esac593 cmd ip "${proto}" route add "${1}" dev "${INTERFACE}" table "${table}"594 cmd ip "${proto}" rule add not fwmark "${table}" table "${table}"595 cmd ip "${proto}" rule add table main suppress_prefixlength 0596597 marker="-m comment --comment \"wg-quick(8) rule for ${INTERFACE}\""598 restore="*raw${NL}"599 nftable="wg-quick-${INTERFACE}"600 nftcmd="${nftcmd:+${nftcmd}${NL}}add table ${pf} ${nftable}"601 nftcmd="${nftcmd:+${nftcmd}${NL}}add chain ${pf} ${nftable} preraw { type filter hook prerouting priority -300; }"602 nftcmd="${nftcmd:+${nftcmd}${NL}}add chain ${pf} ${nftable} premangle { type filter hook prerouting priority -150; }"603 nftcmd="${nftcmd:+${nftcmd}${NL}}add chain ${pf} ${nftable} postmangle { type filter hook postrouting priority -150; }"604 ip -o "${proto}" addr show dev "${INTERFACE}" 2>/dev/null | {605 match=''606 while read -r line; do607 match="$(608 printf %s "${line}" |609 sed -ne 's/^.*inet6\? \([0-9a-f:.]\+\)/[0-9]\+.*$/\1/; t P; b; : P; p'610 )"611 [ -n "${match}" ] ||612 continue613 restore="${restore:+${restore}${NL}}-I PREROUTING ! -i ${INTERFACE} -d ${match} -m addrtype ! --src-type LOCAL -j DROP ${marker}"614 nftcmd="${nftcmd:+${nftcmd}${NL}}add rule ${pf} ${nftable} preraw iifname != \"${INTERFACE}\" ${pf} daddr ${match} fib saddr type != local drop"615 done616 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"617 nftcmd="${nftcmd:+${nftcmd}${NL}}add rule ${pf} ${nftable} postmangle meta l4proto udp mark ${table} ct mark set mark"618 nftcmd="${nftcmd:+${nftcmd}${NL}}add rule ${pf} ${nftable} premangle meta l4proto udp meta mark set ct mark"619 ! [ "${proto}" = '-4' ] ||620 cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1621 if type_p nft >/dev/null; then622 printf '%s\n' "${nftcmd}" |623 cmd nft -f624 else625 printf '%s\n' "${restore}" |626 cmd "${iptables}-restore" -n627 fi628 unset match629 }630 HAVE_SET_FIREWALL=1631 unset table line proto iptables pf marker restore nftable nftcmd632}633634set_config() {635 if [ -e /dev/stdin ]; then636 printf '%s\n' "${WG_CONFIG}" |637 cmd wg setconf "${INTERFACE}" /dev/stdin638 else639 tempfile="$(mktemp)"640 exit_trap push "rm -f \"${tempfile}\""641 printf '%s\n' "${WG_CONFIG}" >"${tempfile}"642 cmd wg setconf "${INTERFACE}" "${tempfile}"643 rm -f "${tempfile}"644 exit_trap pop645 unset tempfile646 fi647}648649save_config() {650 old_umask=''651 new_config=''652 current_config=''653 address=''654 cmd=''655 addr_match="$(656 ip -all -brief address show dev "${INTERFACE}" |657 sed -ne 's#^'"${INTERFACE}"' \+[A-Z]\+ \+\(.\+\)$#\1#; t P; b; : P; p'658 )"659 new_config='[Interface]'660 for address in ${addr_match}; do661 new_config="${new_config:+${new_config}${NL}}Address = ${address}"662 done663 {664 resolvconf -l "$(resolvconf_iface_prefix)${INTERFACE}" 2>/dev/null ||665 cat "/etc/resolvconf/run/interface/$(resolvconf_iface_prefix)${INTERFACE}" 2>/dev/null666 } | {667 while read -r address; do668 addr_match="$(669 printf %s "${address}" |670 sed -ne 's#^nameserver \([a-zA-Z0-9_=+:%.-]\+\)$#\1#; t P; b; : P; p'671 )"672 [ -z "${addr_match}" ] ||673 new_config="${new_config:+${new_config}${NL}}DNS = ${addr_match}"674 done675 if [ -n "${MTU}" ]; then676 mtu_match="$(677 ip link show dev "${INTERFACE}" |678 sed -ne 's/^.*mtu \([0-9]\+\).*$/\1/; t P; b; : P; p'679 )"680 [ -z "${mtu_match}" ] ||681 new_config="${new_config:+${new_config}${NL}}MTU = ${mtu_match}"682 fi683 [ -z "${TABLE}" ] ||684 new_config="${new_config:+${new_config}${NL}}Table = ${TABLE}"685 [ "${SAVE_CONFIG}" -eq 0 ] ||686 new_config="${new_config:+${new_config}${NL}}SaveConfig = true"687 eval "set -- ${PRE_UP}"688 for cmd; do689 new_config="${new_config:+${new_config}${NL}}PreUp = ${cmd}"690 done691 eval "set -- ${POST_UP}"692 for cmd; do693 new_config="${new_config:+${new_config}${NL}}PostUp = ${cmd}"694 done695 eval "set -- ${PRE_DOWN}"696 for cmd; do697 new_config="${new_config:+${new_config}${NL}}PreDown = ${cmd}"698 done699 eval "set -- ${POST_DOWN}"700 for cmd; do701 new_config="${new_config:+${new_config}${NL}}PostDown = ${cmd}"702 done703 old_umask="$(umask)"704 umask 077705 current_config="$(cmd wg showconf "${INTERFACE}")"706 exit_trap push "rm -f \"${CONFIG_FILE}.tmp\""707 printf '%s\n' "${current_config}" |708 sed -e "s#\\[Interface\\]\$#$(709 printf %s "${new_config}" |710 sed -e '$!s/$/\\n/' |711 tr -d '\n'712 )#" >"${CONFIG_FILE}.tmp" ||713 die 'Could not write configuration file'714 sync "${CONFIG_FILE}.tmp"715 mv "${CONFIG_FILE}.tmp" "${CONFIG_FILE}" ||716 die 'Could not move configuration file'717 exit_trap pop718 umask "${old_umask}"719 unset new_config current_config old_umask cmd mtu_match addr_match address720 }721}722723execute_hooks() {724 for hook; do725 hook="$(726 printf %s "${hook}" |727 sed -e "s^%i^${INTERFACE}^g"728 )"729 printf '[#] %s\n' "${hook}" >&2730 (eval "${hook}")731 done732 unset hook733}734735cmd_usage() {736 cat >&2 <<-_EOF737 Usage: $PROGRAM [ up | down | save | strip ] [ CONFIG_FILE | INTERFACE ]738 CONFIG_FILE is a configuration file, whose filename is the interface name739 followed by \`.conf'. Otherwise, INTERFACE is an interface name, with740 configuration found at /etc/wireguard/INTERFACE.conf. It is to be readable741 by wg(8)'s \`setconf' sub-command, with the exception of the following additions742 to the [Interface] section, which are handled by $PROGRAM:743 - Address: may be specified one or more times and contains one or more744 IP addresses (with an optional CIDR mask) to be set for the interface.745 - DNS: an optional DNS server to use while the device is up.746 - MTU: an optional MTU for the interface; if unspecified, auto-calculated.747 - Table: an optional routing table to which routes will be added; if748 unspecified or \`auto', the default table is used. If \`off', no routes749 are added.750 - PreUp, PostUp, PreDown, PostDown: script snippets which will be executed751 by bash(1) at the corresponding phases of the link, most commonly used752 to configure DNS. The string \`%i' is expanded to INTERFACE.753 - SaveConfig: if set to \`true', the configuration is saved from the current754 state of the interface upon shutdown.755 See wg-quick(8) for more info and examples.756 _EOF757}758759cmd_up() {760 i=''761 [ -z "$(ip link show dev "${INTERFACE}" 2>/dev/null)" ] ||762 die "\`${INTERFACE}' already exists"763 exit_trap push 'del_if'764 eval "execute_hooks ${PRE_UP}"765 add_if766 set_config767 set_mtu_up768 eval "set -- ${ADDRESSES}"769 for i; do770 add_addr "${i}"771 done772 set_dns773 for i in $(774 for k in `wg show "${INTERFACE}" allowed-ips`; do775 #shellcheck disable=SC2003776 ! expr match "${k}" '[0-9a-z:.]\+/[0-9]\+$' >/dev/null ||777 printf '%s\n' "${k}"778 done | sort -nr -k 2779 unset j k780 ); do781 add_route "${i}"782 done783 eval "execute_hooks ${POST_UP}"784 unset i785 exit_trap pop786}787788cmd_down() {789 case " $(wg show interfaces) " in790 *" ${INTERFACE} "*) : ;;791 *) die "\`${INTERFACE}' is not a WireGuard interface" ;;792 esac793 eval "execute_hooks ${PRE_DOWN}"794 [ "${SAVE_CONFIG}" -eq 0 ] ||795 save_config796 del_if797 unset_dns || :798 remove_firewall || :799 eval "execute_hooks ${POST_DOWN}"800}801802cmd_save() {803 case " $(wg show interfaces) " in804 *" ${INTERFACE} "*) : ;;805 *) die "\`${INTERFACE}' is not a WireGuard interface" ;;806 esac807 save_config808}809810cmd_strip() { printf '%s\n' "${WG_CONFIG}"; }811812##813814EXIT_TRAP=''815LC_ALL=C816SELF="$(readlink -f "${0}")"817PATH="$(printf %s "${SELF}" | sed -e 's:/[^/]*$::'):${PATH}"818export LC_ALL PATH819[ -n "${UID-}" ] || UID="$(id -u)"820[ -n "${CONFIG_FILE_BASE}" ] ||821 CONFIG_FILE_BASE='/etc/wireguard'822NL='823'824WG_CONFIG=''825INTERFACE=''826ADDRESSES=$(array_save)827MTU=''828DNS=$(array_save)829DNS_SEARCH=$(array_save)830TABLE=''831PRE_UP=$(array_save)832POST_UP=$(array_save)833PRE_DOWN=$(array_save)834POST_DOWN=$(array_save)835SAVE_CONFIG=0836CONFIG_FILE=''837PROGRAM="$(printf %s "${0}" | sed -e 's:^.*/\([^/]*\)$:\1:')"838ARGS=$(array_save "${@}")839HAVE_SET_DNS=0840HAVE_SET_FIREWALL=0841842# ~~ function override insertion point ~~843844case "${#}:${1}" in845 1:--help | 1:-h | 1:help)846 cmd_usage847 ;;848 2:up | 2:down | 2:save | 2:strip)849 auto_su850 parse_options "${2}"851 case "${1}" in852 up)853 cmd_up854 ;;855 down)856 cmd_down857 ;;858 save)859 cmd_save860 ;;861 strip)862 cmd_strip863 ;;864 *)865 :866 ;;867 esac868 ;;869 *)870 cmd_usage871 exit 1872 ;;873esac