Files
op-packages/miniupnpd/files/miniupnpd.init
T
github-actions[bot] 846b79eedc
Merge-upstream / merge (push) Canceled after 0s
🎄 Sync 2026-08-17 19:39:03
2026-08-17 19:39:03 +08:00

281 lines
12 KiB
Bash

#!/bin/sh /etc/rc.common
# Copyright (C) 2006-2014 OpenWrt.org
START=94
STOP=15
USE_PROCD=1
PROG=/usr/sbin/miniupnpd
[ -x "$(command -v nft)" ] && FW="fw4" || FW="fw3"
start_service() {
config_load "upnpd"
local enabled config_file log_output conf
config_get enabled settings enabled 0
config_get config_file settings config_file
config_get log_output settings log_output
if [ "$enabled" != "1" ]; then
log "Service disabled, enabled UCI option not set"
return 1
fi
if [ -n "$config_file" ]; then
conf="$config_file"
else
local tmpconf="/var/etc/miniupnpd.conf"
conf="$tmpconf"
mkdir -p /var/etc
upnpd_generate_config "$tmpconf" || return 1
fi
if [ "$FW" = "fw4" ]; then
nft -s -t -n list chain inet fw4 upnp_forward >/dev/null 2>&1 || fw4 reload
else
iptables -L MINIUPNPD >/dev/null 2>&1 || fw3 reload
fi
procd_open_instance
procd_set_param file "$conf"
procd_set_param command "$PROG"
procd_append_param command -f "$conf"
[ "$log_output" = "info" ] && procd_append_param command -v
[ "$log_output" = "debug" ] && procd_append_param command -v -v
procd_close_instance
}
stop_service() {
if [ "$FW" = "fw3" ]; then
iptables -t filter -F MINIUPNPD 2>/dev/null
[ -x /usr/sbin/ip6tables ] && ip6tables -t filter -F MINIUPNPD 2>/dev/null
iptables -t nat -F MINIUPNPD 2>/dev/null
iptables -t nat -F MINIUPNPD-POSTROUTING 2>/dev/null
else
nft flush chain inet fw4 upnp_forward 2>/dev/null
nft flush chain inet fw4 upnp_prerouting 2>/dev/null
#nft flush chain inet fw4 upnp_postrouting 2>/dev/null # Not used with nftables
fi
}
service_triggers() {
procd_add_reload_trigger "upnpd" "firewall"
}
upnpd_generate_config() {
# Daemon
local enable_protocols allow_cgnat stun_host allow_third_party_mapping system_uptime lease_file
config_get enable_protocols settings enable_protocols all
config_get allow_cgnat settings allow_cgnat 0
config_get stun_host settings stun_host stun.nextcloud.com
config_get allow_third_party_mapping settings allow_third_party_mapping 0
config_get system_uptime settings system_uptime 1
config_get lease_file settings lease_file /var/run/miniupnpd.leases
# Access control
local access_defaults accept_ports reject_ports check_acl ipv6_disable
config_get access_defaults settings access_defaults none
config_get accept_ports settings accept_ports
config_get reject_ports settings reject_ports "21 23 135 137-139 445 3389"
config_get check_acl settings check_acl 1
config_get ipv6_disable settings ipv6_disable 0
# UPnP IGD
local upnp_igd_compat download_kbps upload_kbps friendly_name model_number serial_number presentation_url uuid http_port notify_interval
config_get upnp_igd_compat settings upnp_igd_compat igdv1
config_get download_kbps settings download_kbps
config_get upload_kbps settings upload_kbps
config_get friendly_name settings friendly_name "OpenWrt UPnP IGD & PCP"
config_get model_number settings model_number
config_get serial_number settings serial_number
config_get presentation_url settings presentation_url
config_get uuid settings uuid
config_get http_port settings http_port 5000
config_get notify_interval settings notify_interval
# Network interfaces
local external_iface external_iface6 external_zone external_ip internal_iface
config_get external_iface settings external_iface
config_get external_iface6 settings external_iface6
config_get external_zone settings external_zone
config_get external_ip settings external_ip
config_get internal_iface settings internal_iface lan
local ifname ifname6
. /lib/functions/network.sh
if [ -n "$external_iface" ]; then
network_get_device ifname "$external_iface"
elif [ -n "$external_zone" ]; then
ifname=$($FW -q zone "$external_zone" 2>/dev/null | head -1)
else
network_find_wan external_iface && network_get_device ifname "$external_iface"
fi
if [ -n "$external_iface6" ]; then
network_get_device ifname6 "$external_iface6"
elif [ -n "$external_zone" ]; then
ifname6=$($FW -q zone "$external_zone" 2>/dev/null | head -1)
else
network_find_wan6 external_iface6 && network_get_device ifname6 "$external_iface6"
fi
if [ "$ifname" = "" ]; then
log "No external network interface found, not starting" daemon.err
return 1
fi
# Workaround for daemon bug with UPnP IGDv2 if IPv6 is not ready at start
if [ "$ipv6_disable" = "0" ] && [ "$(uci -q get firewall.@defaults[0].disable_ipv6)" != "1" ] &&
[ "$(uci -q get network.wan6.disabled)" != "1" ]; then
local pass=0
while ! ip -6 addr show dev "${ifname6:-$ifname}" | grep -q "inet6 [23]"; do
log "IPv6 not ready yet; delay start"
sleep 5
pass=$((pass + 1))
[ "$pass" = "4" ] && log "IPv6 GUA not yet available, UPnP IGD mapping not possible" && break
done
fi
# Only perform an STUN CGNAT test if necessary, with a private/CGNAT-reserved external IPv4
local extipv4 extipv4private
extipv4="$(ip -4 addr show dev "$ifname" | grep inet | head -1 | sed -E "s/.*inet ([0-9.]+).*/\1/")"
case "$extipv4" in
10.* | 100.6[4-9].* | 100.[7-9][0-9].* | 100.1[0-1][0-9].* | 100.12[0-7].* | 172.1[6-9].* | \
172.2[0-9].* | 172.3[0-1].* | 192.0.0.[1-6] | 192.168.* | 198.1[89].*) extipv4private=1 ;;
esac
[ "$extipv4private" = "1" ] && log "Private/CGNAT-reserved external IPv4 detected ($extipv4)"
{
echo "# Daemon"
[ "$enable_protocols" = "all" ] && echo "enable_upnp=yes" && echo "enable_pcp_pmp=yes"
[ "$enable_protocols" = "upnp-igd" ] && echo "enable_upnp=yes" && echo "enable_pcp_pmp=no"
[ "$enable_protocols" = "pcp+nat-pmp" ] && echo "enable_upnp=no" && echo "enable_pcp_pmp=yes"
if [ "$extipv4private" = "1" ] && [ "$allow_cgnat" != "0" ]; then
[ "$allow_cgnat" = "1" ] && echo "ext_perform_stun=yes"
[ "$allow_cgnat" = "allow-filtered" ] && echo "ext_perform_stun=allow-filtered"
# Avoid next option, as no STUN public IPv4 detection, required by clients (PCP/NAT-PMP among others)
[ "$allow_cgnat" = "report-private-ipv4" ] && echo "ext_allow_private_ipv4=yes"
echo "ext_stun_host=${stun_host%%:*}"
[ "${stun_host%%:*}" != "${stun_host##*:}" ] && echo "ext_stun_port=${stun_host##*:}"
fi
[ "$allow_third_party_mapping" = "0" ] && echo "secure_mode=yes" && echo "pcp_allow_thirdparty=no"
[ "$allow_third_party_mapping" = "1" ] && echo "secure_mode=no" && echo "pcp_allow_thirdparty=yes"
[ "$allow_third_party_mapping" = "upnp-igd" ] && echo "secure_mode=no" && echo "pcp_allow_thirdparty=no"
[ "$allow_third_party_mapping" = "pcp" ] && echo "secure_mode=yes" && echo "pcp_allow_thirdparty=yes"
[ "$system_uptime" = "0" ] && echo "system_uptime=no" || echo "system_uptime=yes"
touch "$lease_file" && echo "lease_file=$lease_file"
[ "$ipv6_disable" = "0" ] && touch "${lease_file}-ipv6" && echo "lease_file6=${lease_file}-ipv6"
if [ "$enable_protocols" = "upnp-igd" ] || [ "$enable_protocols" = "all" ]; then
echo "# UPnP IGD"
[ "$upnp_igd_compat" = "igdv1" ] && echo "force_igd_desc_v1=yes" || echo "force_igd_desc_v1=no"
[ -n "$download_kbps" ] && echo "bitrate_down=$((download_kbps * 1000))"
[ -n "$upload_kbps" ] && echo "bitrate_up=$((upload_kbps * 1000))"
[ -n "$friendly_name" ] && echo "friendly_name=$(xml_encode "$friendly_name")"
[ -n "$model_number" ] && echo "model_number=$(xml_encode "$model_number")" || echo "model_number="
[ -n "$serial_number" ] && echo "serial=$(xml_encode "$serial_number")" || echo "serial="
[ -n "$presentation_url" ] && echo "presentation_url=$presentation_url"
[ -z "$uuid" ] && {
log "Generate UPnP IGD UUID"
uuid="$(cat /proc/sys/kernel/random/uuid)"
uci set upnpd.settings.uuid="$uuid"
uci commit upnpd
}
[ "$uuid" != "nocli" ] && echo "uuid=$uuid" || log "uuid=nocli deprecated, set to 00000000-0000-0000-0000-000000000000 instead"
echo "http_port=$http_port"
[ -n "$notify_interval" ] && echo "notify_interval=$notify_interval"
fi
if [ "$FW" = "fw4" ]; then
echo "# Firewall backend"
echo "upnp_table_name=fw4"
echo "upnp_nat_table_name=fw4"
echo "upnp_forward_chain=upnp_forward"
echo "upnp_nat_chain=upnp_prerouting"
#echo "upnp_nat_postrouting_chain=upnp_postrouting" # Not used with nftables
fi
echo "# External network interface"
echo "ext_ifname=$ifname"
echo "ext_ifname6=${ifname6:-$ifname}"
[ -n "$external_ip" ] && echo "ext_ip=$external_ip"
echo "# Enable internal networks / access control"
[ "$ipv6_disable" = "0" ] && echo "ipv6_disable=no" || echo "ipv6_disable=yes"
local rejectport
for rejectport in $reject_ports; do
is_port_or_range "$rejectport" allowport0 && echo "deny 1-65535 0.0.0.0/0 $rejectport # Reject port $rejectport" &&
echo "deny $rejectport 0.0.0.0/0 1-65535 # Reject external port $rejectport" ||
log "Invalid port or port range ($rejectport) in reject_ports ignored" daemon.warn
done
[ "$check_acl" = "1" ] && echo "# Access control list" && config_foreach upnpd_add_acl_entry acl_entry
local iface
for iface in $internal_iface; do
local device subnet accessdefaultsports acceptport
network_get_device device "$iface"
network_get_subnet subnet "$iface"
[ "$subnet" = "" ] && log "Cannot get IPv4 subnet for network $iface, network ignored" daemon.warn && continue
echo "# Enable internal network $iface ($device) with access defaults $access_defaults and check ACL $check_acl"
echo "listening_ip=$device"
if [ "$access_defaults" = "accept-high-ports" ]; then
accessdefaultsports="1024-65535"
elif [ "$access_defaults" = "accept-web+high-ports" ]; then
accessdefaultsports="80 443 1024-65535"
elif [ "$access_defaults" = "accept-web-ports" ]; then
accessdefaultsports="80 443"
elif [ "$access_defaults" = "accept-all-ports" ]; then
accessdefaultsports="1-65535"
elif [ "$access_defaults" != "none" ]; then
log "Invalid access_defaults ($access_defaults) ignored" daemon.warn
fi
for acceptport in $accessdefaultsports $accept_ports; do
is_port_or_range "$acceptport" && echo "allow $acceptport $subnet $acceptport # Accept port $acceptport on $iface" ||
log "Invalid port or port range ($acceptport) in accept_ports ignored" daemon.warn
done
done
echo "deny 1-65535 0.0.0.0/0 1-65535 # Reject ACL by default"
} >"$1"
}
log() {
logger -s -p "${2:-daemon.notice}" -t "miniupnpd-init" "$1" || echo "miniupnpd-init: $1" >&2
}
xml_encode() {
# Encode required XML entities of text UPnP IGD config options until the daemon does so
echo "$1" | sed "s/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g"
}
is_port_or_range() {
[ "$1" = "0" ] && [ "$2" != "allowport0" ] && return 1
[ "$1" -ge "1" ] 2>/dev/null && [ "$1" -le "65535" ] 2>/dev/null && return 0
[ "$2" = "allowport0" ] && local minport=0 || local minport=1
[ "${1%%-*}" -ge "$minport" ] 2>/dev/null && [ "${1%%-*}" -le "65535" ] 2>/dev/null &&
[ "${1##*-}" -ge "$minport" ] 2>/dev/null && [ "${1##*-}" -le "65535" ] 2>/dev/null &&
[ "${1##*-}" -ge "${1%%-*}" ] 2>/dev/null && return 0 || return 1
}
upnpd_add_acl_entry() {
local cfg="$1"
local comment int_addr int_port ext_port descr_filter action
config_get comment "$cfg" comment "unspecified" # comment
config_get int_addr "$cfg" int_addr "0.0.0.0/0" # IPv4 address or address and netmask (internal)
config_get int_port "$cfg" int_port "1-65535" # internal port or range: x or x-y
config_get ext_port "$cfg" ext_port "1-65535" # external port or range: x or x-y
config_get descr_filter "$cfg" descr_filter # description regex filter (must be built in)
config_get action "$cfg" action # accept/reject/disabled
! is_port_or_range "$int_port" allowport0 &&
log "ACL entry: Invalid port or port range ($int_port) in int_port ignored" daemon.warn && int_port=1-65535
! is_port_or_range "$ext_port" allowport0 &&
log "ACL entry: Invalid port or port range ($ext_port) in ext_port ignored" daemon.warn && ext_port=1-65535
[ "$descr_filter" != "" ] && descr_filter=" \"$descr_filter\""
if [ "$action" = "accept" ]; then
action=allow
elif [ "$action" = "reject" ]; then
action=deny
elif [ "$action" != "disabled" ]; then
log "ACL entry: Entry with invalid action ($action) ignored" daemon.warn
action=disabled
fi
[ "$action" = "disabled" ] && return 0
echo "$action $ext_port $int_addr $int_port${descr_filter} # $comment"
}
reload_service() {
restart
}