packages/net/strongswan/files/swanctl.init
2023-12-21 18:20:17 +08:00

739 lines
17 KiB
Bash

#!/bin/sh /etc/rc.common
START=90
STOP=10
USE_PROCD=1
PROG=/usr/lib/ipsec/charon
. $IPKG_INSTROOT/lib/functions.sh
. $IPKG_INSTROOT/lib/functions/network.sh
STRONGSWAN_CONF_FILE=/etc/strongswan.conf
STRONGSWAN_VAR_CONF_FILE=/var/ipsec/strongswan.conf
SWANCTL_CONF_FILE=/etc/swanctl/swanctl.conf
SWANCTL_VAR_CONF_FILE=/var/swanctl/swanctl.conf
WAIT_FOR_INTF=0
CONFIG_FAIL=0
time2seconds() {
local timestring="$1"
local multiplier number suffix
suffix="${timestring//[0-9 ]}"
number="${timestring%%$suffix}"
[ "$number$suffix" != "$timestring" ] && return 1
case "$suffix" in
""|s)
multiplier=1 ;;
m)
multiplier=60 ;;
h)
multiplier=3600 ;;
d)
multiplier=86400 ;;
*)
return 1 ;;
esac
echo $(( number * multiplier ))
}
seconds2time() {
local seconds="$1"
if [ $seconds -eq 0 ]; then
echo "0s"
elif [ $((seconds % 86400)) -eq 0 ]; then
echo "$((seconds / 86400))d"
elif [ $((seconds % 3600)) -eq 0 ]; then
echo "$((seconds / 3600))h"
elif [ $((seconds % 60)) -eq 0 ]; then
echo "$((seconds / 60))m"
else
echo "${seconds}s"
fi
}
file_reset() {
: > "$1"
}
xappend() {
local file="$1"
local indent="$2"
shift 2
for cmd in "$@"; do
echo "$indent$cmd" >> "$file"
done
}
swan_reset() {
file_reset "$STRONGSWAN_VAR_CONF_FILE"
}
swan_xappend() {
xappend "$STRONGSWAN_VAR_CONF_FILE" "$@"
}
swan_xappend0() {
swan_xappend "" "$@"
}
swan_xappend1() {
swan_xappend " " "$@"
}
swan_xappend2() {
swan_xappend " " "$@"
}
swan_xappend3() {
swan_xappend " " "$@"
}
swan_xappend4() {
swan_xappend " " "$@"
}
swanctl_reset() {
file_reset "$SWANCTL_VAR_CONF_FILE"
}
swanctl_xappend() {
xappend "$SWANCTL_VAR_CONF_FILE" "$@"
}
swanctl_xappend0() {
swanctl_xappend "" "$@"
}
swanctl_xappend1() {
swanctl_xappend " " "$@"
}
swanctl_xappend2() {
swanctl_xappend " " "$@"
}
swanctl_xappend3() {
swanctl_xappend " " "$@"
}
swanctl_xappend4() {
swanctl_xappend " " "$@"
}
warning() {
echo "WARNING: $@" >&2
}
fatal() {
echo "ERROR: $@" >&2
CONFIG_FAIL=1
}
append_var() {
local var="$2" value="$1" delim="${3:- }"
append "$var" "$value" "$delim"
}
is_aead() {
local cipher="$1"
case "$cipher" in
aes*gcm*|aes*ccm*|aes*gmac*)
return 0 ;;
chacha20poly1305)
return 0 ;;
esac
return 1
}
config_esp_proposal() {
local conf="$1"
local encryption_algorithm
local hash_algorithm
local dh_group
config_get encryption_algorithm "$conf" encryption_algorithm
config_get hash_algorithm "$conf" hash_algorithm
config_get dh_group "$conf" dh_group
# check for AEAD and clobber hash_algorithm if set
if is_aead "$encryption_algorithm" && [ -n "$hash_algorithm" ]; then
fatal "Can't have $hash_algorithm with $encryption_algorithm"
hash_algorithm=
fi
[ -n "$encryption_algorithm" ] && \
crypto="${crypto:+${crypto},}${encryption_algorithm}${hash_algorithm:+-${hash_algorithm}}${dh_group:+-${dh_group}}"
}
iter_esp_proposal() {
local conf="$1"
local var="$2"
local crypto=""
config_list_foreach "$conf" crypto_proposal config_esp_proposal
export -n "$var=$crypto"
}
config_ike_proposal() {
local conf="$1"
local encryption_algorithm
local hash_algorithm
local dh_group
local prf_algorithm
config_get encryption_algorithm "$conf" encryption_algorithm
config_get hash_algorithm "$conf" hash_algorithm
config_get dh_group "$conf" dh_group
config_get prf_algorithm "$conf" prf_algorithm
# check for AEAD and clobber hash_algorithm if set
if is_aead "$encryption_algorithm" && [ -n "$hash_algorithm" ]; then
fatal "Can't have $hash_algorithm with $encryption_algorithm"
hash_algorithm=
fi
[ -n "$encryption_algorithm" ] && \
crypto="${crypto:+${crypto},}${encryption_algorithm}${hash_algorithm:+-${hash_algorithm}}${prf_algorithm:+-${prf_algorithm}}${dh_group:+-${dh_group}}"
}
iter_ike_proposal() {
local conf="$1"
local var="$2"
local crypto=""
config_list_foreach "$conf" crypto_proposal config_ike_proposal
export -n "$var=$crypto"
}
config_child() {
# Generic ipsec conn section shared by tunnel and transport
local conf="$1"
local mode="$2"
local hw_offload
local interface
local ipcomp
local priority
local local_subnet
local local_nat
local updown
local firewall
local remote_subnet
local lifetime
local dpdaction
local closeaction
local startaction
local if_id
local rekeytime
local rekeybytes
local lifebytes
local rekeypackets
local lifepackets
local replay_window
config_get startaction "$conf" startaction "route"
config_get local_nat "$conf" local_nat ""
config_get updown "$conf" updown ""
config_get firewall "$conf" firewall ""
config_get lifetime "$conf" lifetime ""
config_get dpdaction "$conf" dpdaction "none"
config_get closeaction "$conf" closeaction "none"
config_get if_id "$conf" if_id ""
config_get rekeytime "$conf" rekeytime ""
config_get_bool ipcomp "$conf" ipcomp 0
config_get interface "$conf" interface ""
config_get hw_offload "$conf" hw_offload ""
config_get priority "$conf" priority ""
config_get rekeybytes "$conf" rekeybytes ""
config_get lifebytes "$conf" lifebytes ""
config_get rekeypackets "$conf" rekeypackets ""
config_get lifepackets "$conf" lifepackets ""
config_get replay_window "$conf" replay_window ""
config_list_foreach "$conf" local_subnet append_var local_subnet ","
config_list_foreach "$conf" remote_subnet append_var remote_subnet ","
local esp_proposal
iter_esp_proposal "$conf" esp_proposal
# translate from ipsec to swanctl
case "$startaction" in
add)
startaction="none" ;;
route)
startaction="trap" ;;
start|none|trap)
# already using new syntax
;;
*)
fatal "Startaction $startaction unknown"
startaction=
;;
esac
case "$closeaction" in
none|clear)
closeaction="none" ;;
hold)
closeaction="trap" ;;
restart)
closeaction="start" ;;
trap|start)
# already using new syntax
;;
*)
fatal "Closeaction $closeaction unknown"
closeaction=
;;
esac
[ -n "$closeaction" -a "$closeaction" != "none" ] && warning "Closeaction $closeaction can cause instability"
case "$dpdaction" in
none)
dpddelay="0s"
dpdaction=
;;
clear)
;;
hold)
dpdaction="trap" ;;
restart)
dpdaction="start" ;;
trap|start)
# already using new syntax
;;
*)
fatal "Dpdaction $dpdaction unknown"
dpdaction=
;;
esac
case "$hw_offload" in
yes|no|auto|"")
;;
*)
fatal "hw_offload value $hw_offload invalid"
hw_offload=""
;;
esac
[ -n "$local_nat" ] && local_subnet="$local_nat"
swanctl_xappend3 "$conf {"
[ -n "$local_subnet" ] && swanctl_xappend4 "local_ts = $local_subnet"
[ -n "$remote_subnet" ] && swanctl_xappend4 "remote_ts = $remote_subnet"
[ -n "$hw_offload" ] && swanctl_xappend4 "hw_offload = $hw_offload"
[ $ipcomp -eq 1 ] && swanctl_xappend4 "ipcomp = 1"
[ -n "$interface" ] && swanctl_xappend4 "interface = $interface"
[ -n "$priority" ] && swanctl_xappend4 "priority = $priority"
[ -n "$if_id" ] && swanctl_xappend4 "if_id_in = $if_id" "if_id_out = $if_id"
[ -n "$startaction" -a "$startaction" != "none" ] && swanctl_xappend4 "start_action = $startaction"
[ -n "$closeaction" -a "$closeaction" != "none" ] && swanctl_xappend4 "close_action = $closeaction"
swanctl_xappend4 "esp_proposals = $esp_proposal"
swanctl_xappend4 "mode = $mode"
if [ -n "$lifetime" ]; then
swanctl_xappend4 "life_time = $lifetime"
elif [ -n "$rekeytime" ]; then
swanctl_xappend4 "life_time = $(seconds2time $(((110 * $(time2seconds $rekeytime)) / 100)))"
fi
[ -n "$rekeytime" ] && swanctl_xappend4 "rekey_time = $rekeytime"
if [ -n "$lifebytes" ]; then
swanctl_xappend4 "life_bytes = $lifebytes"
elif [ -n "$rekeybytes" ]; then
swanctl_xappend4 "life_bytes = $(((110 * rekeybytes) / 100))"
fi
[ -n "$rekeybytes" ] && swanctl_xappend4 "rekey_bytes = $rekeybytes"
if [ -n "$lifepackets" ]; then
swanctl_xappend4 "life_packets = $lifepackets"
elif [ -n "$rekeypackets" ]; then
swanctl_xappend4 "life_packets = $(((110 * rekeypackets) / 100))"
fi
[ -n "$rekeypackets" ] && swanctl_xappend4 "rekey_packets = $rekeypackets"
[ -n "$inactivity" ] && swanctl_xappend4 "inactivity = $inactivity"
[ -n "$updown" ] && swanctl_xappend4 "updown = $updown"
[ -n "$dpdaction" ] && swanctl_xappend4 "dpd_action = $dpdaction"
[ -n "$replay_window" ] && swanctl_xappend4 "replay_window = $replay_window"
swanctl_xappend3 "}"
}
config_tunnel() {
config_child "$1" "tunnel"
}
config_transport() {
config_child "$1" "transport"
}
config_pool() {
local conf="$1"
local addrs
local dns
local nbns
local dhcp
local netmask
local server
local subnet
local split_include
local split_exclude
config_get addrs "$conf" addrs
config_list_foreach "$conf" dns append_var dns ","
config_list_foreach "$conf" nbns append_var nbns ","
config_list_foreach "$conf" dhcp append_var dhcp ","
config_list_foreach "$conf" netmask append_var netmask ","
config_list_foreach "$conf" server append_var server ","
config_list_foreach "$conf" subnet append_var subnet ","
config_list_foreach "$conf" split_include append_var split_include ","
config_list_foreach "$conf" split_exclude append_var split_exclude ","
swanctl_xappend1 "$conf {"
[ -n "$addrs" ] && swanctl_xappend2 "addrs = $addrs"
[ -n "$dns" ] && swanctl_xappend2 "dns = $dns"
[ -n "$nbns" ] && swanctl_xappend2 "nbns = $nbns"
[ -n "$dhcp" ] && swanctl_xappend2 "dhcp = $dhcp"
[ -n "$netmask" ] && swanctl_xappend2 "netmask = $netmask"
[ -n "$server" ] && swanctl_xappend2 "server = $server"
[ -n "$subnet" ] && swanctl_xappend2 "subnet = $subnet"
[ -n "$split_include" ] && swanctl_xappend2 "split_include = $split_include"
[ -n "$split_exclude" ] && swanctl_xappend2 "split_exclude = $split_exclude"
swanctl_xappend1 "}"
}
config_remote() {
local conf="$1"
local enabled
local gateway
local local_sourceip
local local_ip
local remote_gateway
local pre_shared_key
local auth_method
local keyingtries
local dpddelay
local inactivity
local keyexchange
local fragmentation
local mobike
local local_cert
local local_key
local ca_cert
local rekeytime
local remote_ca_certs
local pools
config_get_bool enabled "$conf" enabled 0
[ $enabled -eq 0 ] && return
config_get gateway "$conf" gateway
config_get pre_shared_key "$conf" pre_shared_key
config_get auth_method "$conf" authentication_method
config_get local_identifier "$conf" local_identifier ""
config_get remote_identifier "$conf" remote_identifier ""
config_get local_ip "$conf" local_ip "%any"
config_get keyingtries "$conf" keyingtries "3"
config_get dpddelay "$conf" dpddelay "30s"
config_get inactivity "$conf" inactivity
config_get keyexchange "$conf" keyexchange "ikev2"
config_get fragmentation "$conf" fragmentation "yes"
config_get_bool mobike "$conf" mobike 1
config_get local_cert "$conf" local_cert ""
config_get local_key "$conf" local_key ""
config_get ca_cert "$conf" ca_cert ""
config_get rekeytime "$conf" rekeytime
config_get overtime "$conf" overtime
config_list_foreach "$conf" local_sourceip append_var local_sourceip ","
config_list_foreach "$conf" remote_ca_certs append_var remote_ca_certs ","
config_list_foreach "$conf" pools append_var pools ","
case "$fragmentation" in
0)
fragmentation="no" ;;
1)
fragmentation="yes" ;;
yes|accept|force|no)
# already using new syntax
;;
*)
fatal "Fragmentation $fragmentation not supported"
fragmentation=
;;
esac
[ "$gateway" = "any" ] && remote_gateway="%any" || remote_gateway="$gateway"
if [ -n "$local_key" ]; then
[ "$(dirname "$local_key")" != "." ] && \
fatal "local_key $local_key can't be pathname"
[ -f "/etc/swanctl/private/$local_key" ] || \
fatal "local_key $local_key not found"
fi
local ike_proposal
iter_ike_proposal "$conf" ike_proposal
[ -n "$firewall" ] && fatal "Firewall not supported"
if [ "$auth_method" = pubkey ]; then
if [ -n "$ca_cert" ]; then
[ "$(dirname "$ca_cert")" != "." ] && \
fatal "ca_cert $ca_cert can't be pathname"
[ -f "/etc/swanctl/x509ca/$ca_cert" ] || \
fatal "ca_cert $ca_cert not found"
fi
if [ -n "$local_cert" ]; then
[ "$(dirname "$local_cert")" != "." ] && \
fatal "local_cert $local_cert can't be pathname"
[ -f "/etc/swanctl/x509/$local_cert" ] || \
fatal "local_cert $local_cert not found"
fi
fi
swanctl_xappend0 "# config for $conf"
swanctl_xappend0 "connections {"
swanctl_xappend1 "$conf {"
swanctl_xappend2 "local_addrs = $local_ip"
swanctl_xappend2 "remote_addrs = $remote_gateway"
[ -n "$local_sourceip" ] && swanctl_xappend2 "vips = $local_sourceip"
[ -n "$fragmentation" ] && swanctl_xappend2 "fragmentation = $fragmentation"
[ -n "$pools" ] && swanctl_xappend2 "pools = $pools"
swanctl_xappend2 "local {"
swanctl_xappend3 "auth = $auth_method"
[ -n "$local_identifier" ] && swanctl_xappend3 "id = \"$local_identifier\""
[ "$auth_method" = pubkey ] && [ -n "$local_cert" ] && \
swanctl_xappend3 "certs = $local_cert"
swanctl_xappend2 "}"
swanctl_xappend2 "remote {"
swanctl_xappend3 "auth = $auth_method"
[ -n "$remote_identifier" ] && swanctl_xappend3 "id = \"$remote_identifier\""
[ -n "$remote_ca_certs" ] && swanctl_xappend3 "cacerts = \"$remote_ca_certs\""
swanctl_xappend2 "}"
swanctl_xappend2 "children {"
config_list_foreach "$conf" tunnel config_tunnel
config_list_foreach "$conf" transport config_transport
swanctl_xappend2 "}"
case "$keyexchange" in
ike)
;;
ikev1)
swanctl_xappend2 "version = 1" ;;
ikev2)
swanctl_xappend2 "version = 2" ;;
*)
fatal "Keyexchange $keyexchange not supported"
keyexchange=
;;
esac
[ $mobike -eq 1 ] && swanctl_xappend2 "mobike = yes" || swanctl_xappend2 "mobike = no"
if [ -n "$rekeytime" ]; then
swanctl_xappend2 "rekey_time = $rekeytime"
if [ -z "$overtime" ]; then
overtime=$(seconds2time $(($(time2seconds $rekeytime) / 10)))
fi
fi
[ -n "$overtime" ] && swanctl_xappend2 "over_time = $overtime"
swanctl_xappend2 "proposals = $ike_proposal"
[ -n "$dpddelay" ] && swanctl_xappend2 "dpd_delay = $dpddelay"
[ "$keyingtries" = "%forever" ] && swanctl_xappend2 "keyingtries = 0" || swanctl_xappend2 "keyingtries = $keyingtries"
swanctl_xappend1 "}"
swanctl_xappend0 "}"
if [ "$auth_method" = pubkey ]; then
swanctl_xappend0 ""
if [ -n "$ca_cert" ]; then
swanctl_xappend0 "authorities {"
swanctl_xappend1 "$conf {"
swanctl_xappend2 "cacert = $ca_cert"
swanctl_xappend1 "}"
swanctl_xappend0 "}"
fi
elif [ "$auth_method" = psk ]; then
swanctl_xappend0 ""
swanctl_xappend0 "secrets {"
swanctl_xappend1 "ike-$conf {"
swanctl_xappend2 "secret = $pre_shared_key"
if [ -n "$local_identifier" ]; then
swanctl_xappend2 "id1 = $local_identifier"
if [ -n "$remote_identifier" ]; then
swanctl_xappend2 "id2 = $remote_identifier"
fi
fi
swanctl_xappend1 "}"
swanctl_xappend0 "}"
else
fatal "AuthenticationMode $auth_mode not supported"
fi
swanctl_xappend0 "pools {"
config_list_foreach "$conf" pools config_pool
swanctl_xappend0 "}"
swanctl_xappend0 ""
}
do_preamble() {
swanctl_xappend0 "# generated by /etc/init.d/swanctl"
}
config_ipsec() {
local conf="$1"
local rtinstall_enabled
local routing_table
local routing_table_id
local interface
local interface_list
config_get debug "$conf" debug 0
config_get_bool rtinstall_enabled "$conf" rtinstall_enabled 1
[ $rtinstall_enabled -eq 1 ] && install_routes=yes || install_routes=no
# prepare extra charon config option ignore_routing_tables
for routing_table in $(config_get "$conf" "ignore_routing_tables"); do
if [ "$routing_table" -ge 0 ] 2>/dev/null; then
routing_table_id=$routing_table
else
routing_table_id=$(sed -n '/[ \t]*[0-9]\+[ \t]\+'$routing_table'[ \t]*$/s/[ \t]*\([0-9]\+\).*/\1/p' /etc/iproute2/rt_tables)
fi
[ -n "$routing_table_id" ] && append routing_tables_ignored "$routing_table_id"
done
config_list_foreach "$conf" interface append_var interface_list
if [ -z "$interface_list" ]; then
WAIT_FOR_INTF=0
else
for interface in $interface_list; do
network_get_device device $interface
[ -n "$device" ] && append device_list "$device" ","
done
[ -n "$device_list" ] && WAIT_FOR_INTF=0 || WAIT_FOR_INTF=1
fi
}
do_postamble() {
swan_xappend0 "# generated by /etc/init.d/swanctl"
swan_xappend0 "charon {"
swan_xappend1 "install_routes = $install_routes"
[ -n "$routing_tables_ignored" ] && swan_xappend1 "ignore_routing_tables = $routing_tables_ignored"
[ -n "$device_list" ] && swan_xappend1 "interfaces_use = $device_list"
swan_xappend1 "start-scripts {"
swan_xappend2 "load-all = /usr/sbin/swanctl --load-all --noprompt"
swan_xappend1 "}"
swan_xappend1 "syslog {"
swan_xappend2 "identifier = ipsec"
swan_xappend2 "daemon {"
swan_xappend3 "default = $debug"
swan_xappend2 "}"
swan_xappend1 "}"
swan_xappend0 "}"
}
prepare_env() {
mkdir -p /var/ipsec /var/swanctl
swan_reset
swanctl_reset
do_preamble
# needed by do_postamble
local debug install_routes routing_tables_ignored device_list
config_load ipsec
config_foreach config_ipsec ipsec
config_foreach config_remote remote
do_postamble
}
service_running() {
swanctl --stats > /dev/null 2>&1
}
reload_service() {
running && {
prepare_env
[ $WAIT_FOR_INTF -eq 0 ] && {
swanctl --load-all --noprompt
return
}
}
start
}
stop_service() {
swan_reset
swanctl_reset
}
service_triggers() {
procd_add_reload_trigger "ipsec"
config load "ipsec"
}
start_service() {
prepare_env
[ $WAIT_FOR_INTF -eq 1 ] && return
if [ $CONFIG_FAIL -ne 0 ]; then
procd_set_param error "Invalid configuration"
return
fi
procd_open_instance
procd_set_param command $PROG
procd_set_param file $SWANCTL_CONF_FILE
procd_append_param file /etc/swanctl/conf.d/*.conf
procd_append_param file $STRONGSWAN_CONF_FILE
procd_set_param respawn
procd_close_instance
}