Last active
March 12, 2023 12:48
-
-
Save parthibx24/5b32b0704f0c5eb8d44d0a5e4f0ed84b to your computer and use it in GitHub Desktop.
wg-quick that works anywhere (tested in openwrt). by rowan[at]rowanthorpe.com
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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 at zx2c4.com>. All Rights Reserved. | |
| # Austere posix/embedded variant derived by Rowan Thorpe <rowan at rowanthorpe.com>, 2021. | |
| # | |
| # original patch : https://lists.zx2c4.com/pipermail/wireguard/2021-June/006924.html | |
| # | |
| # 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 /etc/wireguard/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 | |
| set_mtu_up | |
| eval "set -- ${ADDRESSES}" | |
| for i; do | |
| add_addr "${i}" | |
| done | |
| 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment