mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-28 02:51:11 +08:00
487 lines
16 KiB
Bash
487 lines
16 KiB
Bash
#!/bin/bash /etc/rc.common
|
|
|
|
. "${IPKG_INSTROOT}/lib/functions/network.sh"
|
|
|
|
START=97
|
|
STOP=90
|
|
USE_PROCD=1
|
|
|
|
# natmap
|
|
[ -x "$(command -v nft)" ] && FW='fw4' || FW='fw3'
|
|
NAME=natmap
|
|
GLOBALSECTION=global
|
|
NATMAPSECTION=natmap
|
|
PROG=/usr/bin/$NAME
|
|
|
|
STATUS_PATH=/var/run/natmap
|
|
|
|
config_load "$NAME"
|
|
|
|
|
|
# define global var: DEF_WAN DEF_WAN6 NIC_* NIC6_*
|
|
define_nic() {
|
|
local dev sub addr
|
|
# get all active NICs
|
|
for dev in $(ls /sys/class/net/); do
|
|
#ipv4
|
|
sub=$(ip -o -4 addr|sed -En "s|.*${dev}\s+inet\s+([0-9\./]+).*|\1|gp")
|
|
eval "NIC_${dev//[[:punct:]]/_}=\"\$sub\""
|
|
#ipv6
|
|
sub=$(ip -o -6 addr|sed -En "s|.*${dev}\s+inet6\s+([A-Za-z0-9\./:]+).*|\1|gp")
|
|
# ref: https://github.com/openwrt/openwrt/blob/main/package/base-files/files/lib/functions/network.sh#L53 #network_get_subnet6()
|
|
for _ in $sub; do
|
|
for addr in $sub; do
|
|
case "$addr" in fe[8ab]?:*|f[cd]??:*)
|
|
continue
|
|
esac
|
|
sub=$addr; break
|
|
done
|
|
# Attempt to return first non-fe80::/10 range
|
|
for addr in $sub; do
|
|
case "$addr" in fe[8ab]?:*)
|
|
continue
|
|
esac
|
|
sub=$addr; break
|
|
done
|
|
# Return first item
|
|
for addr in $sub; do
|
|
sub=$addr; break
|
|
done
|
|
done
|
|
eval "NIC6_${dev//[[:punct:]]/_}=\"\$sub\""
|
|
done
|
|
# get default gateway 0.0.0.0/::
|
|
network_find_wan DEF_WAN true
|
|
network_find_wan6 DEF_WAN6 true
|
|
|
|
return 0
|
|
}
|
|
define_nic
|
|
|
|
load_interfaces() {
|
|
local bind_ifname enable
|
|
|
|
config_get bind_ifname "$1" bind_ifname
|
|
config_get_bool enable "$1" enable 0
|
|
|
|
[ "$enable" = 1 ] && interfaces=" $(uci -q show network|grep "device='$bind_ifname'"|cut -f2 -d'.') $interfaces"
|
|
}
|
|
|
|
# define global var: GLOBAL_*
|
|
define_global() {
|
|
[ "$2" == "0" ] || { >&2 echo "$(basename $0): section $1 validation failed"; return 1; }
|
|
|
|
local error=0
|
|
local v ucivv="enable def_tcp_stun def_udp_stun def_http_server def_fwmark_value def_tcp_interval def_udp_interval def_udp_stun_cycle"
|
|
for v in $ucivv; do
|
|
[ -z "$(config_get $1 $v)" ] && grep -qEv "^(def_fwmark_value)$" <<< "$v" && {
|
|
err_msg__empty $1 $v
|
|
let error++
|
|
}
|
|
eval "GLOBAL_$v=\$$v"
|
|
done
|
|
|
|
[ "$error" -gt 0 ] && return 1 || return 0
|
|
}
|
|
|
|
validate_section_global() {
|
|
uci_load_validate "$NAME" "$GLOBALSECTION" "$1" "$2" \
|
|
'enable:bool:0' \
|
|
'def_tcp_stun:string' \
|
|
'def_udp_stun:string' \
|
|
'def_http_server:string' \
|
|
'def_fwmark_value:string' \
|
|
'def_tcp_interval:and(uinteger, min(1)):30' \
|
|
'def_udp_interval:and(uinteger, min(1)):15' \
|
|
'def_udp_stun_cycle:uinteger:5' \
|
|
'test_port:and(port, min(1))'
|
|
}
|
|
|
|
validate_section_natmap() {
|
|
uci_load_validate "$NAME" "$NATMAPSECTION" "$1" "$2" \
|
|
'enable:bool:0' \
|
|
'interval:and(uinteger, min(1))' \
|
|
'stun_cycle:uinteger' \
|
|
'stun_server:string' \
|
|
'http_server:string' \
|
|
'fwmark_value:string' \
|
|
'comment:string' \
|
|
'udp_mode:bool:0' \
|
|
'family:or("ipv4", "ipv6"):ipv4' \
|
|
'bind_ifname:network' \
|
|
'port:or(port, portrange)' \
|
|
'port_pointer:bool:0' \
|
|
'forward:bool:0' \
|
|
'forward_mode:or("dnat", "via"):via' \
|
|
'natloopback:bool:1' \
|
|
'forward_congestion:string' \
|
|
'forward_timeout:and(uinteger, min(1))' \
|
|
'forward_target:or(hostname,ipaddr(1))' \
|
|
'forward_port:port' \
|
|
'refresh:bool:0' \
|
|
'clt_script:file' \
|
|
'clt_scheme:or("http", "https"):http' \
|
|
'clt_web_port:and(port, min(1))' \
|
|
'clt_username:string' \
|
|
'clt_password:string' \
|
|
'notify_enable:bool:0' \
|
|
'notify_script:file' \
|
|
'notify_tokens:list(string)' \
|
|
'notify_custom_domain:hostname' \
|
|
'notify_text:string' \
|
|
'ddns_enable:bool:0' \
|
|
'ddns_script:file' \
|
|
'ddns_tokens:list(string)' \
|
|
'ddns_a:hostname' \
|
|
'ddns_aaaa:hostname' \
|
|
'ddns_srv:hostname' \
|
|
'ddns_srv_serv:string' \
|
|
'ddns_srv_proto:string:tcp' \
|
|
'ddns_srv_target:hostname' \
|
|
'ddns_srv_priority:range(0, 65535):0' \
|
|
'ddns_srv_weight:range(0, 65535):65535' \
|
|
'ddns_https:hostname' \
|
|
'ddns_https_target:or(".", hostname):.' \
|
|
'ddns_https_svcparams:string:alpn="h2,http/1.1"' \
|
|
'ddns_https_priority:range(1, 65535):1' \
|
|
'custom_script:file' \
|
|
'log_stdout:bool:1' \
|
|
'log_stderr:bool:1'
|
|
}
|
|
|
|
# ip_match <hostname>
|
|
hostname_match() {
|
|
[ "$#" -ge 1 ] || return 1
|
|
local _hostname="$1"
|
|
|
|
echo "$_hostname" | grep -E "^[[:alnum:]][-[:alnum:]]{0,62}(\.[[:alnum:]][-[:alnum:]]{0,62})*\.?$"
|
|
}
|
|
|
|
# ip_match <family> <ipaddr>
|
|
ip_match() {
|
|
[ "$#" -ge 2 ] || return 1
|
|
local _family="$1"
|
|
local _ip="$2"
|
|
|
|
case "$_family" in
|
|
ipv4)
|
|
echo "$_ip" | grep -E "^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$"
|
|
;;
|
|
ipv6)
|
|
echo "$_ip" | grep -E "^(([[:xdigit:]]{1,4}:){7}[[:xdigit:]]{1,4}|([[:xdigit:]]{1,4}:){1,7}:|([[:xdigit:]]{1,4}:){1,6}:[[:xdigit:]]{1,4}|([[:xdigit:]]{1,4}:){1,5}(:[[:xdigit:]]{1,4}){1,2}|([[:xdigit:]]{1,4}:){1,4}(:[[:xdigit:]]{1,4}){1,3}|([[:xdigit:]]{1,4}:){1,3}(:[[:xdigit:]]{1,4}){1,4}|([[:xdigit:]]{1,4}:){1,2}(:[[:xdigit:]]{1,4}){1,5}|[[:xdigit:]]{1,4}:(:[[:xdigit:]]{1,4}){1,6}|:((:[[:xdigit:]]{1,4}){1,7}|:)|\
|
|
fe80:(:[[:xdigit:]]{0,4}){0,4}%\w+|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)|([[:xdigit:]]{1,4}:){1,4}:((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?))$"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# fwmark_match <fwmark>
|
|
fwmark_match() {
|
|
echo "$1" | grep -E '^(0|0x[[:xdigit:]]+|[1-9][0-9]*|0[0-7]+)$'
|
|
}
|
|
|
|
natmap_instance() {
|
|
|
|
procd_open_instance "$1"
|
|
procd_set_param command "$PROG" \
|
|
${interval:+-k "$interval"} \
|
|
${stun_cycle:+-c "$stun_cycle"} \
|
|
${stun_server:+-s "$stun_server"} \
|
|
${http_server:+-h "$http_server"} \
|
|
${fwmark_value:+-f "$fwmark_value"} \
|
|
${port:+-b "$([ "$port_pointer" = 1 ] && echo ${port/-/\~} || echo $port)"} \
|
|
|
|
[ "${family}" = ipv4 ] && procd_append_param command -4
|
|
[ "${family}" = ipv6 ] && procd_append_param command -6
|
|
[ "${udp_mode}" = 1 ] && procd_append_param command -u
|
|
|
|
procd_set_param netdev "br-lan"
|
|
[ -n "${bind_ifname}" ] && {
|
|
procd_append_param command -i "$bind_ifname"
|
|
procd_append_param netdev "$bind_ifname"
|
|
} || {
|
|
local ifname
|
|
network_get_device ifname "$DEF_WAN" && procd_append_param netdev "$ifname"
|
|
network_get_device ifname "$DEF_WAN6" && procd_append_param netdev "$ifname"
|
|
}
|
|
|
|
if [ "$forward" = 1 -a "$forward_mode" = "via" -a -n "${forward_target}" ]; then
|
|
procd_append_param command ${forward_congestion:+-C "$forward_congestion"} ${forward_timeout:+-T "$forward_timeout"} -t "$forward_target" -p "$forward_port"
|
|
fi
|
|
|
|
procd_set_param env "SECTIONID=$1"
|
|
procd_append_param env "COMMENT=$comment"
|
|
if [ "$forward" = 1 ]; then
|
|
# Rewrite the dest_port(forward port) using a public port
|
|
[ "$forward_port" = 0 -a "$forward_mode" = "dnat" ] && procd_append_param env "RWFW=1"
|
|
|
|
if [ "$refresh" = 1 ]; then
|
|
procd_append_param env "REFRESH=$clt_script"
|
|
json_init
|
|
json_add_string scheme "$clt_scheme"
|
|
json_add_int web_port "$clt_web_port"
|
|
json_add_string username "$clt_username"
|
|
json_add_string password "$clt_password"
|
|
json_add_string host "$forward_target"
|
|
procd_append_param env "REFRESH_PARAM=$(json_dump)"
|
|
fi
|
|
fi
|
|
|
|
if [ "$notify_enable" = 1 ]; then
|
|
procd_append_param env "NOTIFY=$notify_script"
|
|
json_init
|
|
json_add_string custom_domain "$notify_custom_domain"
|
|
json_add_string text "$notify_text"
|
|
json_add_string tokens "$notify_tokens"
|
|
procd_append_param env "NOTIFY_PARAM=$(json_dump)"
|
|
fi
|
|
|
|
if [ "$ddns_enable" = 1 ]; then
|
|
procd_append_param env "DDNS=$ddns_script"
|
|
json_init
|
|
json_add_string tokens "$ddns_tokens"
|
|
[ "$family" = ipv4 ] && json_add_string hostype "A" && json_add_string host "$ddns_a"
|
|
[ "$family" = ipv6 ] && json_add_string hostype "AAAA" && json_add_string host "$ddns_aaaa"
|
|
[ -n "$ddns_srv" ] && {
|
|
json_add_string srv "$ddns_srv"
|
|
json_add_string srv_serv "$ddns_srv_serv"
|
|
json_add_string srv_proto "$ddns_srv_proto"
|
|
json_add_string srv_target "$ddns_srv_target"
|
|
json_add_int srv_priority "$ddns_srv_priority"
|
|
json_add_int srv_weight "$ddns_srv_weight"
|
|
}
|
|
[ -n "$ddns_https" ] && {
|
|
json_add_string https "$ddns_https"
|
|
json_add_string https_target "$ddns_https_target"
|
|
json_add_string https_svcparams "$ddns_https_svcparams"
|
|
json_add_int https_priority "$ddns_https_priority"
|
|
}
|
|
procd_append_param env "DDNS_PARAM=$(json_dump)"
|
|
fi
|
|
|
|
[ -n "${custom_script}" ] && procd_append_param env "CUSTOM_SCRIPT=${custom_script}"
|
|
procd_append_param command -e /usr/lib/natmap/update.sh
|
|
|
|
procd_set_param respawn
|
|
procd_set_param stdout "$log_stdout"
|
|
procd_set_param stderr "$log_stderr"
|
|
|
|
procd_open_data
|
|
# configure firewall
|
|
json_add_array firewall
|
|
if [ "$forward" = 1 -a "$forward_mode" = "dnat" ]; then
|
|
json_add_object ''
|
|
json_add_string type redirect
|
|
json_add_string target DNAT
|
|
json_add_string name "$1"
|
|
json_add_string family "$family"
|
|
json_add_string proto "$proto"
|
|
json_add_string src "$($FW -q device $bind_ifname)"
|
|
#json_add_string src_dip "$bind_ip" # fix for #11 (The external address of the firewall will not be updated), fw4 cannot add `iifname` rule in `<action>_<fw zone>` chain
|
|
json_add_string src_dport "$port"
|
|
#json_add_string dest '' # zlan or zwan
|
|
json_add_string dest_ip "$forward_target"
|
|
[ -n "$PUBPORT" ] \
|
|
&& json_add_string dest_port "$PUBPORT" && unset PUBPORT \
|
|
|| json_add_string dest_port "$forward_port"
|
|
json_add_boolean reflection $natloopback
|
|
json_close_object
|
|
fi
|
|
if [ "$forward" = 1 -a "$forward_mode" = "via" -o "$forward" = 0 ]; then
|
|
json_add_object ''
|
|
json_add_string type rule
|
|
json_add_string target ACCEPT
|
|
json_add_string name "$1"
|
|
json_add_string family "$family"
|
|
json_add_string proto "$proto"
|
|
json_add_string direction in
|
|
json_add_string src "$($FW -q device $bind_ifname)"
|
|
#json_add_string dest '' # '' = input
|
|
#json_add_string dest_ip "$bind_ip" # fix for #11 (The external address of the firewall will not be updated), fw4 cannot add `iifname` rule in `<action>_<fw zone>` chain
|
|
json_add_string dest_port "$port"
|
|
json_close_object
|
|
fi
|
|
json_close_array
|
|
procd_close_data
|
|
|
|
procd_close_instance
|
|
}
|
|
|
|
clear_status_files() {
|
|
local f sid="$1"
|
|
|
|
mkdir -p "${STATUS_PATH}" 2>/dev/null
|
|
pushd "${STATUS_PATH}" >/dev/null
|
|
if [ -n "$sid" ]; then
|
|
for f in *.json; do
|
|
[ "$(jsonfilter -q -i $f -e '@.sid')" = "$sid" ] || continue
|
|
rm -f $f; break
|
|
done
|
|
else
|
|
find . -type f -print0 | xargs -0 rm -f --
|
|
fi
|
|
popd >/dev/null
|
|
}
|
|
|
|
# err_msg__empty <section> <option>
|
|
err_msg__empty() {
|
|
>&2 echo "$(basename $0): section $1 option $2 cannot be empty"
|
|
}
|
|
|
|
launcher() {
|
|
[ "$2" = 0 ] || { >&2 echo "$(basename $0): section $1 validation failed"; return 1; }
|
|
[ "$enable" = 0 ] && return 0
|
|
|
|
# global options
|
|
if [ "$udp_mode" = 1 ]; then
|
|
local interval=${interval:-$GLOBAL_def_udp_interval} \
|
|
stun_cycle=${stun_cycle:-$GLOBAL_def_udp_stun_cycle} \
|
|
stun_server=${stun_server:-$GLOBAL_def_udp_stun} \
|
|
http_server='' \
|
|
;
|
|
else
|
|
local interval=${interval:-$GLOBAL_def_tcp_interval} \
|
|
stun_cycle='' \
|
|
stun_server=${stun_server:-$GLOBAL_def_tcp_stun} \
|
|
http_server=${http_server:-$GLOBAL_def_http_server} \
|
|
;
|
|
fi
|
|
local fwmark_value=${fwmark_value:-$GLOBAL_def_fwmark_value}
|
|
# natmap options
|
|
local error=0
|
|
local proto ifname bind_ip lan_addr
|
|
[ "$udp_mode" = 1 ] && proto=udp || proto=tcp
|
|
case "$family" in
|
|
ipv4) network_get_device ifname "$DEF_WAN";;
|
|
ipv6) network_get_device ifname "$DEF_WAN6";;
|
|
esac
|
|
local bind_ifname=${bind_ifname:-$ifname}
|
|
[ -z "$bind_ifname" ] && >&2 echo "$(basename $0): section $1 option bind_ifname parsing failed, there may be no $family network connection" && let error++
|
|
case "$family" in
|
|
ipv4)
|
|
eval "bind_ip=\"\${NIC_${bind_ifname//[[:punct:]]/_}%/*}\""
|
|
lan_addr="${NIC_br_lan%/*}"
|
|
;;
|
|
ipv6)
|
|
eval "bind_ip=\"\${NIC6_${bind_ifname//[[:punct:]]/_}%/*}\""
|
|
lan_addr="${NIC6_br_lan%/*}"
|
|
;;
|
|
esac
|
|
[ -z "$bind_ip" ] && >&2 echo "$(basename $0): section $1 option bind_ip parsing failed, there may be no $family network connection" && let error++
|
|
[ -z "$port" ] && err_msg__empty $1 port && let error++
|
|
grep -qE '^([1-9]\d*)(-([1-9]\d*))?$' <<< "$port" || { >&2 echo "$(basename $0): section $1 option port '$port' is invalid"; let error++; }
|
|
[ -z "$fwmark_value" -o -n "$(fwmark_match "$fwmark_value")" ] || { >&2 echo "$(basename $0): section $1 option fwmark_value '$fwmark_value' is invalid"; let error++; }
|
|
## forward
|
|
if [ "$forward" = 1 ]; then
|
|
[ "$forward_mode" = "dnat" -a "$family" = "ipv6" ] && >&2 echo "$(basename $0): section $1 option forward_mode 'dnat' not support under IPv6" && let error++
|
|
[ "$forward_mode" != "dnat" ] && natloopback=0
|
|
if [ -n "$forward_target" ]; then
|
|
if [ -n "$(hostname_match "$forward_target")" -a -z "$(ip_match ipv4 "$forward_target")" ]; then
|
|
# hostname
|
|
[ "$forward_mode" = "dnat" ] && >&2 echo "$(basename $0): section $1 option forward_target '$forward_target', hostname not support under DNAT mode" && let error++
|
|
else
|
|
# ipaddr(1)
|
|
[ -n "$(grep -E "^127(\.\d+){3}" <<< $forward_target)" -o "$forward_target" = "::1" ] && forward_target="$lan_addr"
|
|
[ -n "$(grep -E "^0(\.\d+){3}" <<< $forward_target)" -o "$forward_target" = "::" ] && forward_target="$bind_ip"
|
|
if [ -n "$forward_target" ]; then
|
|
[ -z "$(ip_match "$family" "$forward_target")" ] && >&2 echo "$(basename $0): section $1 option forward_target '$forward_target' not a $family address" && let error++
|
|
else
|
|
>&2 echo "$(basename $0): section $1 option forward_target parsing failed, there may be no $family network connection"; let error++
|
|
fi
|
|
fi
|
|
else
|
|
err_msg__empty $1 forward_target; let error++
|
|
fi
|
|
[ -z "$forward_port" ] && err_msg__empty $1 forward_port && let error++
|
|
elif [ "$forward" = 0 ]; then
|
|
natloopback=0
|
|
unset forward_target forward_port
|
|
fi
|
|
## refresh
|
|
if [ "$refresh" = 1 ]; then
|
|
[ -x "$clt_script" ] || { >&2 echo "$(basename $0): section $1 option clt_script '$clt_script' is empty or non-executable"; let error++; }
|
|
[ -z "$clt_web_port" ] && err_msg__empty $1 clt_web_port && let error++
|
|
fi
|
|
## notify
|
|
if [ "$notify_enable" = 1 ]; then
|
|
[ -x "$notify_script" ] || { >&2 echo "$(basename $0): section $1 option notify_script '$notify_script' is empty or non-executable"; let error++; }
|
|
fi
|
|
## ddns
|
|
if [ "$ddns_enable" = 1 ]; then
|
|
[ -x "$ddns_script" ] || { >&2 echo "$(basename $0): section $1 option ddns_script '$ddns_script' is empty or non-executable"; let error++; }
|
|
if [ -n "$ddns_srv" ]; then
|
|
[ -z "$ddns_srv_serv" ] && err_msg__empty $1 ddns_srv_serv && let error++
|
|
local ddns_srv_target=${ddns_srv_target:-$ddns_srv}
|
|
fi
|
|
fi
|
|
|
|
# review
|
|
[ -n "$NATMAP_DEBUG" ] && {
|
|
local v ucivv="enable interval stun_cycle stun_server http_server fwmark_value comment udp_mode proto family bind_ifname ifname bind_ip lan_addr port port_pointer forward forward_mode natloopback forward_congestion forward_timeout forward_target forward_port refresh clt_script clt_scheme clt_web_port clt_username clt_password notify_enable notify_script notify_tokens notify_custom_domain notify_text ddns_enable ddns_script ddns_tokens ddns_a ddns_aaaa ddns_srv ddns_srv_serv ddns_srv_proto ddns_srv_target ddns_srv_priority ddns_srv_weight ddns_https ddns_https_target ddns_https_svcparams ddns_https_priority custom_script"
|
|
for v in $ucivv; do eval "echo $1 $v=\'\$$v\'"; done # ash not support ${!v}
|
|
}
|
|
[ "$error" -gt 0 ] && return 1
|
|
|
|
natmap_instance "$1"
|
|
}
|
|
|
|
service_triggers() {
|
|
procd_add_reload_trigger "$NAME" 'network'
|
|
|
|
local interfaces
|
|
|
|
config_foreach load_interfaces $NATMAPSECTION
|
|
[ -n "$interfaces" ] && {
|
|
for n in $interfaces; do
|
|
procd_add_reload_interface_trigger $n
|
|
done
|
|
} || {
|
|
for n in $DEF_WAN $DEF_WAN6; do
|
|
procd_add_reload_interface_trigger $n
|
|
done
|
|
}
|
|
|
|
interfaces=$(uci show network|grep "device='br-lan'"|cut -f2 -d'.')
|
|
[ -n "$interfaces" ] && {
|
|
for n in $interfaces; do
|
|
procd_add_reload_interface_trigger $n
|
|
done
|
|
}
|
|
|
|
procd_add_validation validate_section_natmap
|
|
}
|
|
|
|
start_service() {
|
|
local sid="$1"
|
|
|
|
config_foreach validate_section_global "$GLOBALSECTION" define_global || return $?
|
|
[ "${GLOBAL_enable:=0}" == "0" ] && return 1
|
|
|
|
if [ -n "$sid" ]; then
|
|
clear_status_files "$sid"
|
|
validate_section_natmap "$sid" launcher
|
|
else
|
|
clear_status_files
|
|
config_foreach validate_section_natmap "$NATMAPSECTION" launcher
|
|
fi
|
|
}
|
|
|
|
stop_service() {
|
|
local sid="$1"
|
|
clear_status_files "$sid"
|
|
}
|
|
|
|
service_stopped() {
|
|
sleep 1s # Wait for procd_kill complete
|
|
|
|
ps | grep natmap | awk '{print $1}' | xargs kill -9
|
|
}
|
|
|
|
reload_service() {
|
|
stop "$@"
|
|
start "$@"
|
|
}
|
|
|
|
service_started() { procd_set_config_changed firewall; }
|
|
|
|
service_stopped() { procd_set_config_changed firewall; }
|