diff --git a/net/miniupnpd/Makefile b/net/miniupnpd/Makefile index 8fa98ebde426c..7afbaf10b630f 100644 --- a/net/miniupnpd/Makefile +++ b/net/miniupnpd/Makefile @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=miniupnpd PKG_VERSION:=2.3.9 -PKG_RELEASE:=2 +PKG_RELEASE:=3 PKG_SOURCE_URL:=https://github.com/miniupnp/miniupnp/releases/download/miniupnpd_$(subst .,_,$(PKG_VERSION)) PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz @@ -98,8 +98,10 @@ define Package/miniupnpd/install/Default $(INSTALL_DIR) $(1)/etc/init.d $(INSTALL_DIR) $(1)/etc/config $(INSTALL_DIR) $(1)/etc/hotplug.d/iface + $(INSTALL_DIR) $(1)/etc/uci-defaults $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/miniupnpd $(1)/usr/sbin/miniupnpd $(INSTALL_BIN) ./files/miniupnpd.init $(1)/etc/init.d/miniupnpd + $(INSTALL_BIN) ./files/upnpd-migration.uci-defaults $(1)/etc/uci-defaults/99-miniupnpd-upnpd-migration $(INSTALL_CONF) ./files/upnpd.config $(1)/etc/config/upnpd $(INSTALL_DATA) ./files/miniupnpd.hotplug $(1)/etc/hotplug.d/iface/50-miniupnpd endef diff --git a/net/miniupnpd/files/firewall3.include b/net/miniupnpd/files/firewall3.include index 4fd483974306c..8fc2144db3193 100644 --- a/net/miniupnpd/files/firewall3.include +++ b/net/miniupnpd/files/firewall3.include @@ -20,39 +20,39 @@ iptables_prepend_rule() { local chain="$3" local target="$4" - $iptables "$IPTARGS" -t "$table" -I "$chain" $($iptables "$IPTARGS" -t "$table" --line-numbers -nL "$chain" | \ + $iptables "$IPTARGS" -t "$table" -I "$chain" $($iptables "$IPTARGS" -t "$table" --line-numbers -nL "$chain" | sed -ne '$s/[^0-9].*//p') -j "$target" } ADDED=0 add_extzone_rules() { - local ext_zone="$1" - - [ -z "$ext_zone" ] && return - - # IPv4 - due to NAT, need to add both to nat and filter table - # need to insert as penultimate rule for input & forward & postrouting since final rule might be a fw3 REJECT - iptables_prepend_rule "$IPTABLES" filter "zone_${ext_zone}_input" MINIUPNPD - iptables_prepend_rule "$IPTABLES" filter "zone_${ext_zone}_forward" MINIUPNPD - $IPTABLES -t nat -A "zone_${ext_zone}_prerouting" -j MINIUPNPD - iptables_prepend_rule "$IPTABLES" nat "zone_${ext_zone}_postrouting" MINIUPNPD-POSTROUTING - - # IPv6 if available - filter only - [ -x $IP6TABLES ] && { - iptables_prepend_rule "$IP6TABLES" filter "zone_${ext_zone}_input" MINIUPNPD - iptables_prepend_rule "$IP6TABLES" filter "zone_${ext_zone}_forward" MINIUPNPD - } - ADDED=$(($ADDED + 1)) + local ext_zone="$1" + + [ -z "$ext_zone" ] && return + + # IPv4 - due to NAT, need to add both to nat and filter table + # need to insert as penultimate rule for input & forward & postrouting since final rule might be a fw3 REJECT + iptables_prepend_rule "$IPTABLES" filter "zone_${ext_zone}_input" MINIUPNPD + iptables_prepend_rule "$IPTABLES" filter "zone_${ext_zone}_forward" MINIUPNPD + $IPTABLES -t nat -A "zone_${ext_zone}_prerouting" -j MINIUPNPD + iptables_prepend_rule "$IPTABLES" nat "zone_${ext_zone}_postrouting" MINIUPNPD-POSTROUTING + + # IPv6 if available - filter only + [ -x $IP6TABLES ] && { + iptables_prepend_rule "$IP6TABLES" filter "zone_${ext_zone}_input" MINIUPNPD + iptables_prepend_rule "$IP6TABLES" filter "zone_${ext_zone}_forward" MINIUPNPD + } + ADDED=$(($ADDED + 1)) } # By default, user configuration is king. -for ext_iface in $(uci -q get upnpd.config.external_iface); do - add_extzone_rules $(fw3 -q network "$ext_iface") +for ext_iface in $(uci -q get upnpd.settings.external_iface); do + add_extzone_rules $(fw3 -q network "$ext_iface") done -add_extzone_rules $(uci -q get upnpd.config.external_zone) +add_extzone_rules $(uci -q get upnpd.settings.external_zone) [ "$ADDED" -ne 0 ] && exit 0 @@ -66,7 +66,7 @@ network_find_wan wan_iface network_find_wan6 wan6_iface for ext_iface in $wan_iface $wan6_iface; do - # fw3 -q network fails on sub-interfaces => map to device first - network_get_device ext_device $ext_iface - add_extzone_rules $(fw3 -q device "$ext_device") + # fw3 -q network fails on sub-interfaces => map to device first + network_get_device ext_device $ext_iface + add_extzone_rules $(fw3 -q device "$ext_device") done diff --git a/net/miniupnpd/files/miniupnpd.hotplug b/net/miniupnpd/files/miniupnpd.hotplug index 607a32bdc6885..e86097e86010a 100644 --- a/net/miniupnpd/files/miniupnpd.hotplug +++ b/net/miniupnpd/files/miniupnpd.hotplug @@ -1,45 +1,36 @@ +#!/bin/sh /etc/init.d/miniupnpd enabled || exit 0 -# If miniupnpd is not running: -# - check on _any_ event (event updates may contribute to network_find_wan*) - -# If miniupnpd _is_ running: -# - check only on ifup (otherwise lease updates etc would cause -# miniupnpd state loss) +# If daemon is: +# - not running: check on any event (event updates may contribute to network_find_wan*) +# - running: check only on ifup (otherwise lease updates etc. would cause daemon state loss) [ "$ACTION" != "ifup" ] && /etc/init.d/miniupnpd running && exit 0 tmpconf="/var/etc/miniupnpd.conf" -external_iface=$(uci -q get upnpd.config.external_iface) -external_iface6=$(uci -q get upnpd.config.external_iface6) -external_zone=$(uci -q get upnpd.config.external_zone) +external_iface=$(uci -q get upnpd.settings.external_iface) +external_iface6=$(uci -q get upnpd.settings.external_iface6) +external_zone=$(uci -q get upnpd.settings.external_zone) [ -x "$(command -v nft)" ] && FW="fw4" || FW="fw3" . /lib/functions/network.sh - -if [ -n "$external_iface" ] ; then +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 - if [ -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 + network_find_wan external_iface && network_get_device ifname "$external_iface" fi -if [ -n "$external_iface6" ] ; then +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 - if [ -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 + network_find_wan6 external_iface6 && network_get_device ifname6 "$external_iface6" fi [ "$DEVICE" != "$ifname" ] && [ "$DEVICE" != "$ifname6" ] && exit 0 -grep -qs "^ext_ifname=$ifname" "$tmpconf" && grep -qs "^ext_ifname6=$ifname6" "$tmpconf" && exit 0 +grep -qs "^ext_ifname=$ifname" "$tmpconf" && grep -qs "^ext_ifname6=${ifname6:-$ifname}" "$tmpconf" && exit 0 /etc/init.d/miniupnpd restart diff --git a/net/miniupnpd/files/miniupnpd.init b/net/miniupnpd/files/miniupnpd.init index bb15b15cb98f3..940b75c792a77 100644 --- a/net/miniupnpd/files/miniupnpd.init +++ b/net/miniupnpd/files/miniupnpd.init @@ -7,170 +7,175 @@ USE_PROCD=1 PROG=/usr/sbin/miniupnpd [ -x "$(command -v nft)" ] && FW="fw4" || FW="fw3" -upnpd_get_port_range() { - local var="$1"; shift - local val - - config_get val "$@" - - case "$val" in - [0-9]*[:-][0-9]*) - export -n -- "${var}_start=${val%%[:-]*}" - export -n -- "${var}_end=${val##*[:-]}" - ;; - [0-9]*) - export -n -- "${var}_start=$val" - export -n -- "${var}_end=" - ;; - esac -} - -conf_rule_add() { - local cfg="$1" - local action int_addr - local ext_start ext_end int_start int_end comment +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 - config_get action "$cfg" action "deny" # allow or deny - upnpd_get_port_range "ext" "$cfg" ext_ports "0-65535" # external ports: x, x-y, x:y - config_get int_addr "$cfg" int_addr "0.0.0.0/0" # ip or network and subnet mask (internal) - upnpd_get_port_range "int" "$cfg" int_ports "0-65535" # internal ports: x, x-y, x:y or range - config_get comment "$cfg" comment "ACL" # comment + 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 - # Make a single IP IP/32 so that miniupnpd.conf can use it. - [ "${int_addr%/*}" = "$int_addr" ] && int_addr="$int_addr/32" + 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 - echo "$action $ext_start${ext_end:+-}$ext_end $int_addr $int_start${int_end:+-}$int_end #$comment" + 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 -vv + procd_close_instance } -upnpd_write_bool() { - local opt="$1" - local def="${2:-0}" - local alt="${3:-$opt}" - local val - - config_get_bool val config "$opt" "$def" - if [ "$val" -eq 0 ]; then - echo "$alt=no" +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 - echo "$alt=yes" + 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 fi } -upnpd() { - config_load "upnpd" - local external_iface external_iface6 external_zone external_ip internal_iface - local upload download log_output port config_file serial_number model_number - local use_stun stun_host stun_port uuid notify_interval presentation_url - local upnp_lease_file upnp_lease_file6 ipv6_disable - - local enabled - config_get_bool enabled config enabled 1 - [ "$enabled" -eq 0 ] && return 1 - - config_get external_iface config external_iface - config_get external_iface6 config external_iface6 - config_get external_zone config external_zone - config_get external_ip config external_ip - config_get internal_iface config internal_iface - config_get port config port 5000 - config_get upload config upload - config_get download config download - config_get_bool log_output config log_output 0 - config_get config_file config config_file - config_get serial_number config serial_number - config_get model_number config model_number - config_get uuid config uuid - config_get use_stun config use_stun 0 - config_get stun_host config stun_host - config_get stun_port config stun_port - config_get notify_interval config notify_interval - config_get presentation_url config presentation_url - config_get upnp_lease_file config upnp_lease_file - config_get upnp_lease_file6 config upnp_lease_file6 - config_get ipv6_disable config ipv6_disable 0 - - local conf ifname ifname6 +service_triggers() { + procd_add_reload_trigger "upnpd" "firewall" +} +upnpd_generate_config() { + # Daemon + local enable_protocols allow_cgnat stun_host allow_third_party_mapping ipv6_disable 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 ipv6_disable settings ipv6_disable 0 + config_get system_uptime settings system_uptime 1 + config_get lease_file settings lease_file /var/run/miniupnpd.leases + + # 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 + + # External network interface + local external_iface external_iface6 external_zone external_ip + 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 + + local ifname ifname6 . /lib/functions/network.sh - - if [ -n "$external_iface" ] ; then + 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 - if [ -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 + network_find_wan external_iface && network_get_device ifname "$external_iface" fi - if [ -n "$external_iface6" ] ; then + 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 - if [ -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 + network_find_wan6 external_iface6 && network_get_device ifname6 "$external_iface6" fi - if [ -n "$config_file" ]; then - conf="$config_file" - else - local tmpconf="/var/etc/miniupnpd.conf" - conf="$tmpconf" - mkdir -p /var/etc - - { - echo "ext_ifname=$ifname" - echo "ext_ifname6=$ifname6" - [ -n "$external_ip" ] && echo "ext_ip=$external_ip" - - local iface - for iface in ${internal_iface:-lan}; do - local device - network_get_device device "$iface" && echo "listening_ip=$device" + 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" ]; then + local pass=0 + while ! ip addr show dev "${ifname6:-$ifname}" | grep -q "inet6 [23]"; do + log "IPv6 not ready yet; delay start" + sleep 3 + pass=$((pass + 1)) + [ "$pass" = "5" ] && log "IPv6 GUA not yet available, disable IPv6 mapping" && break done + fi + if ! uci -q get upnpd.@internal_network[0].interface >/dev/null; then + log "No internal networks configured, not starting" daemon.err + return 1 + fi + # Only perform an STUN CGNAT test if necessary, with a private/CGNAT external IPv4 + local extipv4 extipv4private + network_get_ipaddr extipv4 "$external_iface" # Todo: Handling external_zone + case "$extipv4" in + 10.* | 172.1[6-9].* | 172.2[0-9].* | 172.3[0-1].* | 192.168.* | 100.6[4-9].* | 100.[7-9][0-9].* | 100.1[0-1][0-9].* | 100.12[0-7].*) extipv4private=1 ;; + esac - config_load "upnpd" - upnpd_write_bool enable_natpmp 1 - upnpd_write_bool enable_upnp 1 - upnpd_write_bool secure_mode 1 - upnpd_write_bool system_uptime 1 - upnpd_write_bool igdv1 0 force_igd_desc_v1 - upnpd_write_bool use_stun 0 ext_perform_stun - upnpd_write_bool ipv6_disable $ipv6_disable - - [ "$use_stun" -eq 0 ] || { - [ -n "$stun_host" ] && echo "ext_stun_host=$stun_host" - [ -n "$stun_port" ] && echo "ext_stun_port=$stun_port" - } - - [ -n "$upload" ] && [ -n "$download" ] && { - echo "bitrate_down=$((download * 1024 * 8))" - echo "bitrate_up=$((upload * 1024 * 8))" - } - - [ -n "$upnp_lease_file" ] && touch "$upnp_lease_file" && echo "lease_file=$upnp_lease_file" - [ -n "$upnp_lease_file6" ] && touch "$upnp_lease_file6" && echo "lease_file6=$upnp_lease_file6" - [ -n "$presentation_url" ] && echo "presentation_url=$presentation_url" - [ -n "$notify_interval" ] && echo "notify_interval=$notify_interval" - [ -n "$serial_number" ] && echo "serial=$serial_number" - [ -n "$model_number" ] && echo "model_number=$model_number" - [ -n "$port" ] && echo "port=$port" - - [ -z "$uuid" ] && { - uuid="$(cat /proc/sys/kernel/random/uuid)" - uci set upnpd.config.uuid="$uuid" - uci commit upnpd - } - - [ "$uuid" = "nocli" ] || echo "uuid=$uuid" - - config_foreach conf_rule_add perm_rule + { + 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" ] && [ "$external_ip" = "" ]; then + [ "$allow_cgnat" = "1" ] && echo "ext_perform_stun=yes" + [ "$allow_cgnat" = "allow-filtered" ] && echo "ext_perform_stun=allow-filtered" + # Alternatively, as allow-filtered detects the public IPv4 required by various clients, e.g. PCP/NAT-PMP + [ "$allow_cgnat" = "allow-private-ext-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" + [ "$ipv6_disable" = "0" ] && echo "ipv6_disable=no" || echo "ipv6_disable=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 - #When using nftables configure miniupnpd to use its own table and chains + echo "# Firewall backend" echo "upnp_table_name=fw4" echo "upnp_nat_table_name=fw4" echo "upnp_forward_chain=upnp_forward" @@ -178,46 +183,105 @@ upnpd() { echo "upnp_nat_postrouting_chain=upnp_postrouting" fi - } > "$tmpconf" - fi + echo "# External network interface" + echo "ext_ifname=$ifname" + echo "ext_ifname6=${ifname6:-$ifname}" + [ -n "$external_ip" ] && echo "ext_ip=$external_ip" - if [ -n "$ifname" ]; then - # start firewall - 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 - else - logger -t "upnp daemon" "external interface not found, not starting" - fi + echo "# Enable internal networks / access control" + config_foreach upnpd_add_int_network_and_preset internal_network pre-acl + config_foreach upnpd_add_acl_entry acl_entry + config_foreach upnpd_add_int_network_and_preset internal_network post-acl + echo "deny 1-65535 0.0.0.0/0 1-65535 # Reject ACL by default" - procd_open_instance - procd_set_param file "$conf" "/etc/config/firewall" - procd_set_param command "$PROG" - procd_append_param command -f "$conf" - [ "$log_output" = "1" ] && procd_append_param command -d - procd_close_instance + } >"$1" } -stop_service() { - if [ "$FW" = "fw3" ]; then - iptables -t nat -F MINIUPNPD 2>/dev/null - iptables -t nat -F MINIUPNPD-POSTROUTING 2>/dev/null - iptables -t filter -F MINIUPNPD 2>/dev/null - [ -x /usr/sbin/ip6tables ] && ip6tables -t filter -F MINIUPNPD 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 - fi +log() { + logger -s -p "${2:-daemon.notice}" -t "miniupnpd-init" "$1" || echo "miniupnpd-init: $1" >&2 } -start_service() { - config_load "upnpd" - config_foreach upnpd "upnpd" +xml_encode() { + # Encode required XML entities of text UPnP IGD config options until the daemon does so + echo "$1" | sed "s/&/\&/g; s//\>/g" } -service_triggers() { - procd_add_reload_trigger "upnpd" +is_port_or_range() { + [ "$1" = "0" ] && return 1 + [ "$1" -ge "1" ] 2>/dev/null && [ "$1" -le "65535" ] 2>/dev/null && return 0 + [ "$2" = "port0inrange" ] && 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_int_network_and_preset() { + local cfg="$1" + local interface access_preset accept_ports reject_ports ignore_acl + config_get interface "$cfg" interface + config_get access_preset "$cfg" access_preset none + config_get accept_ports "$cfg" accept_ports + config_get reject_ports "$cfg" reject_ports "21 23 135 137-139 445 3389" + config_get ignore_acl "$cfg" ignore_acl 0 + local device subnet rejectport acceptpresetports acceptport + network_get_device device "$interface" + network_get_subnet subnet "$interface" + if [ "$2" = "pre-acl" ]; then + echo "# Enable internal network $interface ($device) with preset $access_preset and ignore ACL ${ignore_acl}" + echo "listening_ip=$device" + fi + [ "$subnet" = "" ] && log "Cannot get IPv4 subnet for network $interface, access_preset ignored" daemon.warn && return 0 + if [ "$2" = "pre-acl" ]; then + for rejectport in $reject_ports; do + is_port_or_range "$rejectport" && echo "deny $rejectport $subnet $rejectport # Reject port $rejectport on $interface" || + log "Invalid port or port range ($rejectport) in reject_ports ignored" daemon.warn + done + fi + if { [ "$2" = "post-acl" ] && [ "$ignore_acl" = "0" ]; } || + { [ "$2" = "pre-acl" ] && [ "$ignore_acl" = "1" ]; }; then + if [ "$access_preset" = "accept-high-ports" ]; then + acceptpresetports="1024-65535" + elif [ "$access_preset" = "accept-web+high-ports" ]; then + acceptpresetports="80 443 1024-65535" + elif [ "$access_preset" = "accept-web-ports" ]; then + acceptpresetports="80 443" + elif [ "$access_preset" = "accept-all-ports" ]; then + acceptpresetports="1-65535" + elif [ "$access_preset" != "none" ]; then + log "Invalid access_preset ($access_preset) ignored" daemon.warn + fi + for acceptport in $acceptpresetports $accept_ports; do + is_port_or_range "$acceptport" && echo "allow $acceptport $subnet $acceptport # Accept port $acceptport on $interface" || + log "Invalid port or port range ($acceptport) in accept_ports ignored" daemon.warn + done + fi + if [ "$2" = "pre-acl" ] && [ "$ignore_acl" = "1" ]; then + echo "deny 1-65535 $subnet 1-65535 # Reject ACL by default on $interface" + fi +} + +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 or network and subnet mask (internal) + config_get int_port "$cfg" int_port "1-65535" # internal port/range: x or x-y + config_get ext_port "$cfg" ext_port "1-65535" # external port/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/ignore + ! is_port_or_range "$int_port" port0inrange && + 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" port0inrange && + 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" != "ignore" ]; then + log "ACL entry: Entry with invalid action ($action) ignored" daemon.warn + action=ignore + fi + [ "$action" = "ignore" ] && return 0 + echo "$action $ext_port $int_addr $int_port${descr_filter} # $comment" } diff --git a/net/miniupnpd/files/upnpd-migration.uci-defaults b/net/miniupnpd/files/upnpd-migration.uci-defaults new file mode 100644 index 0000000000000..a8589c244e1aa --- /dev/null +++ b/net/miniupnpd/files/upnpd-migration.uci-defaults @@ -0,0 +1,256 @@ +#!/bin/sh + +log() { + logger -s -p "${2:-daemon.notice}" -t "upnpd" "$1" || echo "upnpd: $1" >&2 +} + +# Skip migration with existing settings (v2.0) or with no config (v1.0) UCI section +# Enables the creation of a merged v1.0/v2.0 config file +{ uci -q get upnpd.settings >/dev/null || ! uci -q get upnpd.config >/dev/null; } && exit 0 + +log "Check UCI options in /etc/config/upnpd to be migrated to v2.0" +cp /etc/config/upnpd /tmp + +# Set missing enabled option to fix previously different defaults in LuCI/config (0) and init UCI (1) +if ! uci -q get upnpd.config.enabled >/dev/null; then + uci -q set upnpd.config.enabled="1" +fi + +# Migrate boolean options to only use 0/1 for LuCI support +for option in enabled ipv6_disable system_uptime; do + if uci -q get upnpd.config.$option >/dev/null; then + uci get upnpd.config.$option | grep -q -E -x "0|off|false|no|disabled" && uci set upnpd.config.$option="0" + uci get upnpd.config.$option | grep -q -E -x "1|on|true|yes|enabled" && uci set upnpd.config.$option="1" + fi +done + +# Migrate enable_upnp/enable_natpmp -> enable_protocols: Combined option +if uci -q get upnpd.config.enable_upnp >/dev/null || uci -q get upnpd.config.enable_natpmp >/dev/null; then + log "enable_upnp/enable_natpmp -> enable_protocols: Combined option" + if ! uci -q get upnpd.config.enable_upnp | grep -q -E -x "0|off|false|no|disabled"; then + uci -q get upnpd.config.enable_natpmp | grep -q -E -x "0|off|false|no|disabled" && + uci set upnpd.config.enable_protocols="upnp-igd" || + uci set upnpd.config.enable_protocols="all" + elif ! uci -q get upnpd.config.enable_natpmp | grep -q -E -x "0|off|false|no|disabled"; then + uci set upnpd.config.enable_protocols="pcp+nat-pmp" + else + uci set upnpd.config.enable_protocols="all" + uci set upnpd.config.enabled="0" + fi + uci -q delete upnpd.config.enable_upnp + uci -q delete upnpd.config.enable_natpmp +fi + +# Rename use_stun -> allow_cgnat +if uci -q get upnpd.config.use_stun >/dev/null; then + log "use_stun -> allow_cgnat" + uci rename upnpd.config.use_stun="allow_cgnat" +fi + +# Migrate force_forwarding=1 (in X-Wrt since 2021) to new similar option allow_cgnat=allow-filtered for cross-upgrades +if uci -q get upnpd.config.force_forwarding >/dev/null; then + log "force_forwarding=1 -> allow_cgnat=allow-filtered: New daemon option" + uci get upnpd.config.force_forwarding | grep -q -E -x "1|on|true|yes|enabled" && + uci set upnpd.config.allow_cgnat="allow-filtered" + uci delete upnpd.config.force_forwarding +fi + +# Remove known incompatible (not CGNAT filtering test capable) STUN servers and include stun_port in stun_host +if uci -q get upnpd.config.stun_host | grep -q -E "stun[0-9]?.l.google.com|stun.cloudflare.com"; then + log "stun_host: Incompatible STUN server ($(uci -q get upnpd.config.stun_host)) found, remove to set default" + uci delete upnpd.config.stun_host + uci -q delete upnpd.config.stun_port +elif uci -q get upnpd.config.stun_port >/dev/null; then + uci -q get upnpd.config.stun_host >/dev/null && [ "$(uci -q get upnpd.config.stun_port)" != "3478" ] && + log "stun_port: Include stun_port in stun_host, and remove option" && + uci set upnpd.config.stun_host="$(uci -q get upnpd.config.stun_host | cut -d ":" -f 1):$(uci -q get upnpd.config.stun_port)" + uci delete upnpd.config.stun_port +fi + +# Migrate secure_mode=1/0 -> allow_third_party_mapping=0/upnp-igd: Invert/extend to PCP +if uci -q get upnpd.config.secure_mode >/dev/null; then + log "secure_mode=1/0 -> allow_third_party_mapping=0/upnp-igd: Invert/ex PCP" + uci get upnpd.config.secure_mode | grep -q -E -x "0|off|false|no|disabled" && + uci set upnpd.config.allow_third_party_mapping="upnp-igd" || + uci set upnpd.config.allow_third_party_mapping="0" + uci delete upnpd.config.secure_mode +fi + +# Migrate log_output=0/1 -> log_output=default/debug: Now info also allowed +if uci -q get upnpd.config.log_output >/dev/null; then + log "log_output=0/1 -> log_output=default/debug: Now info also allowed" + uci get upnpd.config.log_output | grep -q -E -x "1|on|true|yes|enabled" && + uci set upnpd.config.log_output="debug" + uci get upnpd.config.log_output | grep -q -E -x "0|off|false|no|disabled" && + uci set upnpd.config.log_output="default" +fi + +# Rename upnp_lease_file -> lease_file: To original daemon name, and remove if UCI default set +if uci -q get upnpd.config.upnp_lease_file >/dev/null; then + if [ "$(uci -q get upnpd.config.upnp_lease_file)" = "/var/run/miniupnpd.leases" ]; then + log "upnp_lease_file -> lease_file: Remove option as UCI default set" + uci delete upnpd.config.upnp_lease_file + else + log "upnp_lease_file -> lease_file" + uci rename upnpd.config.upnp_lease_file="lease_file" + fi +fi +if uci -q get upnpd.config.upnp_lease_file6 >/dev/null; then + uci delete upnpd.config.upnp_lease_file6 +fi + +# Migrate igdv1=1/0 -> upnp_igd_compat=igdv1/igdv2: Extensible/clearer +if uci -q get upnpd.config.igdv1 >/dev/null; then + log "igdv1=1/0 -> upnp_igd_compat=igdv1/igdv2" + uci get upnpd.config.igdv1 | grep -q -E -x "1|on|true|yes|enabled" && + uci set upnpd.config.upnp_igd_compat="igdv1" || + uci set upnpd.config.upnp_igd_compat="igdv2" + uci delete upnpd.config.igdv1 +fi + +# Migrate download/upload -> download_kbps/upload_kbps: Convert to kbit/s +if uci -q get upnpd.config.download >/dev/null; then + download="$(uci -q get upnpd.config.download)" + if [ "$download" != "1024" ] && [ "$download" -ge "1" ] 2>/dev/null; then + log "download -> download_kbps: Convert to kbit/s" + download_kbps="$((download * 8 * 1000 / 1024))" + uci set upnpd.config.download_kbps="$download_kbps" + fi + uci delete upnpd.config.download +fi +if uci -q get upnpd.config.upload >/dev/null; then + upload="$(uci -q get upnpd.config.upload)" + if [ "$upload" != "512" ] && [ "$upload" -ge "1" ] 2>/dev/null; then + log "upload -> upload_kbps: Convert to kbit/s" + upload_kbps="$((upload * 8 * 1000 / 1024))" + uci set upnpd.config.upload_kbps="$upload_kbps" + fi + uci delete upnpd.config.upload +fi + +# Rename port -> http_port: Remove if UCI default set +if uci -q get upnpd.config.port >/dev/null; then + if [ "$(uci -q get upnpd.config.port)" = "5000" ]; then + log "port -> http_port: Remove option as UCI default set" + uci delete upnpd.config.port + else + log "port -> http_port" + uci rename upnpd.config.port="http_port" + fi +fi + +# Migrate notify_interval <= 900 s: Remove to set minimum of 900 (default) +if [ "$(uci -q get upnpd.config.notify_interval)" -le "900" ] 2>/dev/null; then + log "notify_interval <= 900 s: Remove to set minimum of 900 (default)" + uci delete upnpd.config.notify_interval +fi + +# Migrate ACL to new section, note that an empty ACL is now rejected alone +# a) Empty/unmodified ACL: Enable appropriate preset, add/update template entries +# b) Modified ACL: +# - Add missing entry action to avoid adding inverted actions when changing via LuCI +# - Update entry action allow/deny -> accept/reject +# - Update entry port options to only use the LuCI (and daemon) supported hyphen (-) as port range separator +# - Not using a preset, add template entries +if uci -q get upnpd.@acl_entry[0] >/dev/null; then + log "Error migrating ACL, as the new UCI section already exists" daemon.err +elif ! uci -q get upnpd.@perm_rule[0] >/dev/null; then + log "Empty ACL: Enable preset, add templates, empty ACL now rejected alone" + access_preset=accept-all-ports + ignore_acl=1 + addtemplateentries=1 +elif ! uci -q get upnpd.@perm_rule[2] >/dev/null && + [ "$(uci -q get upnpd.@perm_rule[0].int_addr)" = "0.0.0.0/0" ] && + [ "$(uci -q get upnpd.@perm_rule[0].int_ports)" = "1024-65535" ] && + [ "$(uci -q get upnpd.@perm_rule[0].ext_ports)" = "1024-65535" ] && + [ "$(uci -q get upnpd.@perm_rule[0].action)" = "allow" ] && + [ "$(uci -q get upnpd.@perm_rule[1].int_addr)" = "0.0.0.0/0" ] && + [ "$(uci -q get upnpd.@perm_rule[1].int_ports)" = "0-65535" ] && + [ "$(uci -q get upnpd.@perm_rule[1].ext_ports)" = "0-65535" ] && + [ "$(uci -q get upnpd.@perm_rule[1].action)" = "deny" ]; then + log "Unmodified ACL: Enable preset, update templates, empty ACL rejected" + access_preset=accept-high-ports + ignore_acl=1 + addtemplateentries=1 + uci delete upnpd.@perm_rule[-1] + uci delete upnpd.@perm_rule[-1] +else + log "Modified ACL: Migrate entries/section, empty ACL now rejected alone" + addtemplateentries=1 + entrynr=0 + while uci -q get upnpd.@perm_rule[$entrynr] >/dev/null; do + comment="$(uci -q get upnpd.@perm_rule[$entrynr].comment)" + int_addr="$(uci -q get upnpd.@perm_rule[$entrynr].int_addr)" + int_port="$(uci -q get upnpd.@perm_rule[$entrynr].int_ports)" + ext_port="$(uci -q get upnpd.@perm_rule[$entrynr].ext_ports)" + action="$(uci -q get upnpd.@perm_rule[$entrynr].action)" + echo "$int_port" | grep -q ":" && + log "ACL entry: Update int_port to use hyphen (-) as port range separator" && + int_port="$(echo "$int_port" | tr ":" "-")" + echo "$ext_port" | grep -q ":" && + log "ACL entry: Update ext_port to use hyphen (-) as port range separator" && + ext_port="$(echo "$ext_port" | tr ":" "-")" + [ "$action" = "" ] && log "ACL entry: Add missing action option" && action=reject + [ "$action" = "allow" ] && action=accept + [ "$action" = "deny" ] && action=reject + uci batch >/dev/null <<-EOF + add upnpd acl_entry + set upnpd.@acl_entry[-1].comment="${comment:-unspecified}" + set upnpd.@acl_entry[-1].int_addr="${int_addr:-0.0.0.0/0}" + set upnpd.@acl_entry[-1].int_port="$int_port" + set upnpd.@acl_entry[-1].ext_port="$ext_port" + set upnpd.@acl_entry[-1].action="$action" + EOF + entrynr=$((entrynr + 1)) + done + if [ "${int_addr:-0.0.0.0/0}" = "0.0.0.0/0" ] && [ "${int_port:-0-65535}" = "0-65535" ] && + [ "${ext_port:-0-65535}" = "0-65535" ] && [ "$action" = "reject" ]; then + log "ACL entry: Remove no longer useful reject by default entry" + uci delete upnpd.@acl_entry[-1] + fi + while uci -q delete upnpd.@perm_rule[-1]; do :; done +fi +if [ "$addtemplateentries" = "1" ]; then + uci batch >/dev/null <<-EOF + add upnpd acl_entry + add upnpd acl_entry + set upnpd.@acl_entry[-2].comment="High ports" + set upnpd.@acl_entry[-2].int_addr="0.0.0.0/0" + set upnpd.@acl_entry[-2].int_port="1024-65535" + set upnpd.@acl_entry[-2].ext_port="1024-65535" + set upnpd.@acl_entry[-2].action="ignore" + set upnpd.@acl_entry[-1].comment="Low/system ports" + set upnpd.@acl_entry[-1].int_addr="0.0.0.0/0" + set upnpd.@acl_entry[-1].int_port="1-1023" + set upnpd.@acl_entry[-1].ext_port="1-1023" + set upnpd.@acl_entry[-1].action="ignore" + EOF + uci -q get upnpd.@acl_entry[-3] >/dev/null && + uci reorder upnpd.@acl_entry[-2]=0 && uci reorder upnpd.@acl_entry[-1]=1 +fi + +# Migrate internal_iface option to new internal_network section +if ! uci -q get upnpd.@internal_network[0] >/dev/null; then + ifnr=0 + for interface in $(uci -q get upnpd.config.internal_iface || echo lan); do + log "Create new internal_network section for $interface" + uci add upnpd internal_network >/dev/null + uci set upnpd.@internal_network[$ifnr].interface="$interface" + [ "$access_preset" != "" ] && uci set upnpd.@internal_network[$ifnr].access_preset="$access_preset" + [ "$ignore_acl" = "1" ] && uci set upnpd.@internal_network[$ifnr].ignore_acl="1" + ifnr=$((ifnr + 1)) + done + uci -q delete upnpd.config.internal_iface +fi + +# Rename UCI section config -> settings (v2.0) +if uci -q get upnpd.config >/dev/null; then + log "Rename UCI section config -> settings (v2.0)" && uci rename upnpd.config="settings" || + log "Error renaming the UCI section" daemon.err +fi + +uci commit upnpd >/dev/null + +log "Previous v1.0 config file was copied to /tmp/upnpd (until next reboot)" + +exit 0 diff --git a/net/miniupnpd/files/upnpd.config b/net/miniupnpd/files/upnpd.config index c4546e018f5de..c7b96de11df1c 100644 --- a/net/miniupnpd/files/upnpd.config +++ b/net/miniupnpd/files/upnpd.config @@ -1,29 +1,36 @@ -config upnpd config - option enabled 0 - option enable_natpmp 1 - option enable_upnp 1 - option secure_mode 1 - option log_output 0 - option download 1024 - option upload 512 -#by default, looked up dynamically from ubus -# option external_iface wan - option internal_iface lan - option port 5000 - option upnp_lease_file /var/run/miniupnpd.leases - option upnp_lease_file6 /var/run/miniupnpd.leases6 - option igdv1 1 +# UPnP IGD & PCP/NAT-PMP Service Settings (v2.0) -config perm_rule - option action allow - option ext_ports 1024-65535 - option int_addr 0.0.0.0/0 # Does not override secure_mode - option int_ports 1024-65535 - option comment "Allow high ports" +config upnpd 'settings' + option enabled '0' + # Can be set to all/upnp-igd/pcp+nat-pmp + option enable_protocols 'all' + option allow_third_party_mapping '0' + # Extra logging by setting to info or debug (previously 1) + option log_output 'default' + option upnp_igd_compat 'igdv1' -config perm_rule - option action deny - option ext_ports 0-65535 - option int_addr 0.0.0.0/0 - option int_ports 0-65535 - option comment "Default deny" +# Enable Networks / Access Control + +config internal_network + option interface 'lan' + option access_preset 'accept-high-ports' + #option accept_ports + #option reject_ports '21 23 135 137-139 445 3389' + option ignore_acl '1' + +# Access Control List +# Empty ACL rejected alone, IPv6 accepted unless disabled, action: accept/reject + +config acl_entry + option comment 'High ports' + option int_addr '0.0.0.0/0' + option int_port '1024-65535' + option ext_port '1024-65535' + option action 'ignore' + +config acl_entry + option comment 'Low/system ports' + option int_addr '0.0.0.0/0' + option int_port '1-1023' + option ext_port '1-1023' + option action 'ignore' diff --git a/net/miniupnpd/patches/010-upnp-igdv2-compat.patch b/net/miniupnpd/patches/010-upnp-igdv2-compat.patch new file mode 100644 index 0000000000000..b3cb0ceb321db --- /dev/null +++ b/net/miniupnpd/patches/010-upnp-igdv2-compat.patch @@ -0,0 +1,75 @@ +From a5fa10bbe0dbb69bbfc47f5865238f352fa71675 Mon Sep 17 00:00:00 2001 +From: Self-Hosting-Group + <155233284+Self-Hosting-Group@users.noreply.github.com> +Date: Thu, 27 Nov 2025 00:00:00 +0000 +Subject: [PATCH] miniupnpd: UPnP IGDv2 Microsoft/Apple compatibility + +* Add workaround to list port maps with the Windows IGDv2-incompatible + client by returning an infinite (0) lease duration. To fix listing and + editing via GUI (Explorer/Network), if daemon was compiled with IGDv2 +* Extend detection to older versions of Windows and add Xbox +* Detect Apple IGDv2-incompatible clients and apply existing workaround, + that only caused problems if PCP/NAT-PMP (prioritised) was disabled + +Link: https://github.com/Self-Hosting-Group/miniupnp/tree/upnp-igdv2-compat + +--- a/upnphttp.c ++++ b/upnphttp.c +@@ -303,9 +303,22 @@ ParseHttpHeaders(struct upnphttp * h) + } + else if(strncasecmp(line, "user-agent:", 11) == 0) + { +- /* - User-Agent: Microsoft-Windows/10.0 UPnP/1.0 +- * - User-Agent: FDSSDP */ +- if(strcasestr(line + 11, "microsoft") != NULL || strstr(line + 11, "FDSSDP") != NULL) { ++ /* Detect Microsoft UPnP IGDv2-incompatible clients that only support IGDv1 routers, ++ * and Windows requires extra UDA 1.x (Win XP 1.0), via User-Agent SOAP/HTTP header: ++ * - Microsoft-Windows/10.0 UPnP/1.0 (Win >=10) ++ * - Microsoft-Windows/6.1 UPnP/1.0 (Win 7) ++ * - FDSSDP (Win >=Vista for GET) ++ * - Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1) (Win XP/Vista for GET) ++ * - Mozilla/4.0 (compatible; UPnP/1.0; Windows 9x) (Win XP/Vista for POST) ++ * - Xbox/2.0.17559.0 UPnP/1.0 Xbox/2.0.17559.0 ++ * Detect Apple UPnP IGDv2-incompatible clients that only support IGDv1 routers: ++ * - Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1) (for GET) ++ * - Mozilla/4.0 (compatible; UPnP/1.0; Windows 9x) (for POST) */ ++ if (((strstr(line + 11, "Microsoft-Windows/") != NULL || ++ strstr(line + 11, "Xbox/") != NULL) && ++ strstr(line + 11, " UPnP/1.0") != NULL) || ++ strstr(line + 11, "FDSSDP") != NULL || ++ strstr(line + 11, "Mozilla/4.0 (compatible; UPnP/1.0; Windows") != NULL) { + h->respflags |= FLAG_MS_CLIENT; + } + } +--- a/upnpsoap.c ++++ b/upnpsoap.c +@@ -854,6 +854,14 @@ GetSpecificPortMappingEntry(struct upnph + #ifdef ENABLE_PCP + hide_pcp_nonce(desc); + #endif ++#ifdef IGD_V2 ++ /* Workaround to list port maps with the Windows IGDv2-incompatible client ++ * by returning an infinite (0) lease duration. To fix listing and editing ++ * via GUI (Explorer/Network), if daemon was compiled with IGDv2 */ ++ if (h->respflags & FLAG_MS_CLIENT) { ++ leaseduration = 0; ++ } ++#endif + bodylen = snprintf(body, sizeof(body), resp, + action, ns/*SERVICE_TYPE_WANIPC*/, + (unsigned int)iport, int_ip, desc, leaseduration, +@@ -1124,6 +1132,14 @@ GetGenericPortMappingEntry(struct upnpht + #ifdef ENABLE_PCP + hide_pcp_nonce(desc); + #endif ++#ifdef IGD_V2 ++ /* Workaround to list port maps with the Windows IGDv2-incompatible client ++ * by returning an infinite (0) lease duration. To fix listing and editing ++ * via GUI (Explorer/Network), if daemon was compiled with IGDv2 */ ++ if (h->respflags & FLAG_MS_CLIENT) { ++ leaseduration = 0; ++ } ++#endif + bodylen = snprintf(body, sizeof(body), resp, + action, ns, /*SERVICE_TYPE_WANIPC,*/ rhost, + (unsigned int)eport, protocol, (unsigned int)iport, iaddr, desc, diff --git a/net/miniupnpd/patches/020-descr-filter-fix.patch b/net/miniupnpd/patches/020-descr-filter-fix.patch new file mode 100644 index 0000000000000..9d657d15345f0 --- /dev/null +++ b/net/miniupnpd/patches/020-descr-filter-fix.patch @@ -0,0 +1,511 @@ +From 54ecff0ae4452598773a020e8798ff61ccf3c966 Mon Sep 17 00:00:00 2001 +From: yangfl +Date: Thu, 31 Jul 2025 10:35:50 +0800 +Subject: [PATCH] miniupnpd: Rewrite permission line parser + +Permission line parser rewrites input buffer in-place for parsing, +which causes several problems: + +* It unnecessarily invalidates input buffer for caller. Actually, you + might see the following error message when parsing fails: + miniupnpd[1234]: parsing error file /etc/miniupnpd.conf line 16 : allow 1024 + since the hyphen after 1024 is erased by a '\0'. + +* It fails to validate token separators. For example, the following + line will be accepted: + allow 1024-65535X0.0.0.0/0 1024-65535 all + ^ could be any character + and even a potential buffer over-read if the character is '\0', since + the parser blindlessly skips it. + +* The fifth token is never parsed since it gets a previously written + '\0'. + +Instead of fixing them case-by-case, we rewrite it with DFS in mind, +which is much simpler and less error-prone. + +--- a/upnppermissions.c ++++ b/upnppermissions.c +@@ -29,6 +29,12 @@ isodigit(char c) + return '0' <= c && c >= '7'; + } + ++static int ++iseol(char c) ++{ ++ return c == '\0' || c == '\n' || c == '\r'; ++} ++ + static char + hex2chr(char c) + { +@@ -91,6 +97,84 @@ unescape_char(const char * s, int * seql + return c; + } + ++/* greedy parser: try to match the longest sequence and do not ++ * check for terminators */ ++ ++static char * ++get_sep(const char * s) ++{ ++ if(!isspace(*s)) ++ return NULL; ++ do ++ s++; ++ while(isspace(*s)); ++ return (char *) s; ++} ++ ++static char * ++get_ushort(const char * s, u_short * val) ++{ ++ char * end; ++ unsigned long val_ul; ++ ++ if(!isdigit(*s)) ++ return NULL; ++ val_ul = strtoul(s, &end, 10); ++ if(val_ul > 65535) ++ return NULL; ++ *val = (u_short)val_ul; ++ ++ return end; ++} ++ ++static char * ++get_range(const char * s, u_short * begin, u_short * end) ++{ ++ s = get_ushort(s, begin); ++ if(!s) ++ return NULL; ++ ++ if(*s!='-') ++ *end = *begin; ++ else ++ { ++ s++; ++ s = get_ushort(s, end); ++ if(!s) ++ return NULL; ++ if(*begin > *end) ++ return NULL; ++ } ++ return (char *) s; ++} ++ ++static char * ++get_addr(const char * s, struct in_addr * addr, unsigned int * dot_cnt) ++{ ++ size_t i; ++ char buf[64]; ++ ++ if(!isdigit(*s)) ++ return NULL; ++ ++ *dot_cnt = 0; ++ for(i = 0; isdigit(s[i]) || s[i] == '.';) ++ { ++ if(s[i] == '.') ++ (*dot_cnt)++; ++ buf[i] = s[i]; ++ i++; ++ if (i > sizeof(buf) - 1) ++ return NULL; ++ } ++ ++ buf[i] = '\0'; ++ if(!inet_aton(buf, addr)) ++ return NULL; ++ ++ return (char *) s + i; ++} ++ + /* get_next_token(s, &token, raw) + * put the unquoted/unescaped token in token and returns + * a pointer to the begining of the next token +@@ -99,18 +183,8 @@ static char * + get_next_token(const char * s, char ** token, int raw) + { + char deli; +- const char * end; ++ size_t len; + +- /* skip any whitespace */ +- for(; isspace(*s); s++) +- if(*s == '\0' || *s == '\n') +- { +- if(token) +- *token = NULL; +- return (char *) s; +- } +- +- /* find the start */ + if(*s == '"' || *s == '\'') + { + deli = *s; +@@ -119,85 +193,90 @@ get_next_token(const char * s, char ** t + else + deli = 0; + /* find the end */ +- end = s; +- for(; *end != '\0' && *end != '\n' && (deli ? *end != deli : !isspace(*end)); +- end++) +- if(*end == '\\') ++ for(len = 0; !iseol(s[len]) && (deli ? s[len] != deli : !isspace(s[len])); ++ len++) ++ if(s[len] == '\\') + { +- end++; +- if(*end == '\0') ++ len++; ++ if(iseol(s[len])) + break; + } + + /* save the token */ + if(token) + { +- unsigned int token_len; +- unsigned int i; +- +- token_len = end - s; +- *token = strndup(s, token_len); +- if(!*token) +- return NULL; +- +- for(i = 0; (*token)[i] != '\0'; i++) +- { +- int sequence_len; +- +- if((*token)[i] != '\\') +- continue; +- +- if(raw && deli && (*token)[i + 1] != deli) +- continue; +- (*token)[i] = unescape_char(*token + i, &sequence_len); +- memmove(*token + i + 1, *token + i + sequence_len, +- token_len - i - sequence_len); +- } +- if (i == 0) +- { +- /* behavior of realloc(p, 0) is implementation-defined, so better set it to NULL. +- * https://github.com/miniupnp/miniupnp/issues/652#issuecomment-1518922139 */ +- free(*token); ++ if(len == 0) + *token = NULL; +- } + else + { +- char * tmp = realloc(*token, i); +- if (tmp != NULL) +- *token = tmp; ++ unsigned int i; ++ unsigned int j; ++ ++ char * tmp; ++ char * t; ++ ++ t = malloc(len + 1); ++ if(!t) ++ return NULL; ++ ++ if (raw) ++ { ++ memcpy(t, s, len); ++ j = len; ++ } + else +- syslog(LOG_ERR, "%s: failed to reallocate to %u bytes", +- "get_next_token()", i); ++ { ++ for(i = 0, j = 0; i < len; j++) ++ if(s[i] != '\\') ++ { ++ t[j] = s[i]; ++ i++; ++ } ++ else ++ { ++ int seqlen; ++ t[j] = unescape_char(s + i, &seqlen); ++ i += seqlen; ++ if (i > len) ++ break; ++ } ++ ++ tmp = realloc(*token, j + 1); ++ if (tmp != NULL) ++ t = tmp; ++ else ++ syslog(LOG_ERR, "%s: failed to reallocate to %u bytes", ++ "get_next_token()", j + 1); ++ } ++ t[j] = '\0'; ++ *token = t; + } + } + +- /* return the beginning of the next token */ +- if(deli && *end == deli) +- end++; +- while(isspace(*end)) +- end++; +- return (char *) end; ++ s += len; ++ if(deli && *s == deli) ++ s++; ++ return (char *) s; + } + + /* read_permission_line() + * parse the a permission line which format is : +- * (deny|allow) [0-9]+(-[0-9]+) ip/mask [0-9]+(-[0-9]+) regex ++ * (deny|allow) [0-9]+(-[0-9]+)? ip(/mask)? [0-9]+(-[0-9]+)? (regex)? + * ip/mask is either 192.168.1.1/24 or 192.168.1.1/255.255.255.0 + */ + int + read_permission_line(struct upnpperm * perm, +- char * p) ++ const char * p) + { +- char * q; +- int n_bits; +- int i; ++ unsigned int dot_cnt; + + /* zero memory : see https://github.com/miniupnp/miniupnp/issues/652 */ + memset(perm, 0, sizeof(struct upnpperm)); + +- /* first token: (allow|deny) */ + while(isspace(*p)) + p++; ++ ++ /* first token: (allow|deny) */ + if(0 == memcmp(p, "allow", 5)) + { + perm->type = UPNPPERM_ALLOW; +@@ -212,133 +291,61 @@ read_permission_line(struct upnpperm * p + { + return -1; + } +- while(isspace(*p)) +- p++; ++ ++ p = get_sep(p); ++ if(!p) ++ return -1; + + /* second token: eport or eport_min-eport_max */ +- if(!isdigit(*p)) ++ p = get_range(p, &perm->eport_min, &perm->eport_max); ++ if(!p) + return -1; +- for(q = p; isdigit(*q); q++); +- if(*q=='-') +- { +- *q = '\0'; +- i = atoi(p); +- if(i > 65535) +- return -1; +- perm->eport_min = (u_short)i; +- q++; +- p = q; +- while(isdigit(*q)) +- q++; +- *q = '\0'; +- i = atoi(p); +- if(i > 65535) +- return -1; +- perm->eport_max = (u_short)i; +- if(perm->eport_min > perm->eport_max) +- return -1; +- } +- else if(isspace(*q)) +- { +- *q = '\0'; +- i = atoi(p); +- if(i > 65535) +- return -1; +- perm->eport_min = perm->eport_max = (u_short)i; +- } +- else +- { ++ ++ p = get_sep(p); ++ if(!p) + return -1; +- } +- p = q + 1; +- while(isspace(*p)) +- p++; + +- /* third token: ip/mask */ +- if(!isdigit(*p)) ++ /* third token: ip/mask */ ++ p = get_addr(p, &perm->address, &dot_cnt); ++ if(!p) + return -1; +- for(q = p; isdigit(*q) || (*q == '.'); q++); +- if(*q=='/') ++ ++ if(*p!='/') ++ perm->mask.s_addr = 0xffffffffu; ++ else + { +- *q = '\0'; +- if(!inet_aton(p, &perm->address)) +- return -1; +- q++; +- p = q; +- while(isdigit(*q)) +- q++; +- if(*q == '.') +- { +- while(*q == '.' || isdigit(*q)) +- q++; +- if(!isspace(*q)) +- return -1; +- *q = '\0'; +- if(!inet_aton(p, &perm->mask)) +- return -1; +- } +- else if(!isspace(*q)) ++ p++; ++ p = get_addr(p, &perm->mask, &dot_cnt); ++ if(!p) + return -1; +- else ++ /* inet_aton(): When only one part is given, the value is stored ++ * directly in the network address without any byte ++ * rearrangement. */ ++ if(!dot_cnt) + { +- *q = '\0'; +- n_bits = atoi(p); ++ unsigned int n_bits = ntohl(perm->mask.s_addr); + if(n_bits > 32) + return -1; +- perm->mask.s_addr = htonl(n_bits ? (0xffffffffu << (32 - n_bits)) : 0); ++ perm->mask.s_addr = !n_bits ? 0 : htonl(0xffffffffu << (32 - n_bits)); + } + } +- else if(isspace(*q)) +- { +- *q = '\0'; +- if(!inet_aton(p, &perm->address)) +- return -1; +- perm->mask.s_addr = 0xffffffffu; +- } +- else +- { ++ ++ p = get_sep(p); ++ if(!p) + return -1; +- } +- p = q + 1; + + /* fourth token: iport or iport_min-iport_max */ +- while(isspace(*p)) +- p++; +- if(!isdigit(*p)) ++ p = get_range(p, &perm->iport_min, &perm->iport_max); ++ if(!p) + return -1; +- for(q = p; isdigit(*q); q++); +- if(*q=='-') +- { +- *q = '\0'; +- i = atoi(p); +- if(i > 65535) +- return -1; +- perm->iport_min = (u_short)i; +- q++; +- p = q; +- while(isdigit(*q)) +- q++; +- *q = '\0'; +- i = atoi(p); +- if(i > 65535) +- return -1; +- perm->iport_max = (u_short)i; +- if(perm->iport_min > perm->iport_max) +- return -1; +- } +- else if(isspace(*q) || *q == '\0') +- { +- *q = '\0'; +- i = atoi(p); +- if(i > 65535) +- return -1; +- perm->iport_min = perm->iport_max = (u_short)i; +- } +- else +- { ++ ++ if(iseol(*p) || *p == '#') ++ goto end; ++ p = get_sep(p); ++ if(!p) + return -1; +- } +- p = q; ++ if(iseol(*p) || *p == '#') ++ goto end; + + /* fifth token: (optional) regex */ + p = get_next_token(p, &perm->re, 1); +@@ -381,6 +388,7 @@ read_permission_line(struct upnpperm * p + } + } + ++end: + #ifdef DEBUG + printf("perm rule added : %s %hu-%hu %08x/%08x %hu-%hu %s\n", + (perm->type==UPNPPERM_ALLOW) ? "allow" : "deny", +@@ -427,8 +435,26 @@ write_permlist(int fd, const struct upnp + write(fd, buf, l); + if(perm->re) + { +- write(fd, " ", 1); +- write(fd, perm->re, strlen(perm->re)); ++ const char * p; ++ write(fd, " \"", 2); ++ for(p = perm->re; *p != '\0'; p++) ++ { ++ if(*p == '"') ++ { ++ write(fd, "\\\"", 2); ++ continue; ++ } ++ ++ if(*p == '\\') ++ { ++ write(fd, p, 1); ++ p++; ++ if(*p == '\0') ++ break; ++ } ++ write(fd, p, 1); ++ } ++ write(fd, "\"", 1); + } + write(fd, "\n", 1); + } +--- a/upnppermissions.h ++++ b/upnppermissions.h +@@ -43,7 +43,7 @@ struct upnpperm { + * deny 0-65535 0.0.0.0/0 0-65535 */ + int + read_permission_line(struct upnpperm * perm, +- char * p); ++ const char * p); + + void + free_permission_line(struct upnpperm * perm); +@@ -72,4 +72,3 @@ write_permlist(int fd, const struct upnp + #endif + + #endif +- diff --git a/net/miniupnpd/patches/030-allow-private-fix.patch b/net/miniupnpd/patches/030-allow-private-fix.patch new file mode 100644 index 0000000000000..b8a42b2b567f2 --- /dev/null +++ b/net/miniupnpd/patches/030-allow-private-fix.patch @@ -0,0 +1,18 @@ +From 9ff3c71bfb6c7f7bf525d6bda447387de7257aff Mon Sep 17 00:00:00 2001 +From: Vlad Starodubtsev +Date: Wed, 6 Aug 2025 19:04:18 +0300 +Subject: [PATCH] miniupnpd: fix ALLOWPRIVATEIPV4MASK condition + +Fixed usage of private/reserved WAN addresses if they are allowed in the configuration. + +--- a/upnpsoap.c ++++ b/upnpsoap.c +@@ -370,7 +370,7 @@ GetExternalIPAddress(struct upnphttp * h + ext_if_name); + ext_ip_addr[0] = '\0'; + } else if (addr_is_reserved(&addr)) { +- if (!GETFLAG(ALLOWPRIVATEIPV4MASK)) { ++ if (GETFLAG(ALLOWPRIVATEIPV4MASK)) { + syslog(LOG_WARNING, "IGNORED : private/reserved address %s is not suitable for external IP", ext_ip_addr); + } else { + syslog(LOG_NOTICE, "private/reserved address %s is not suitable for external IP", ext_ip_addr); diff --git a/net/miniupnpd/patches/040-improve-logging.patch b/net/miniupnpd/patches/040-improve-logging.patch new file mode 100644 index 0000000000000..998df1cb4e2f1 --- /dev/null +++ b/net/miniupnpd/patches/040-improve-logging.patch @@ -0,0 +1,450 @@ +--- a/minissdp.c ++++ b/minissdp.c +@@ -1274,7 +1274,7 @@ ProcessSSDPData(int s, const char *bufr, + else + snprintf(ver_str, sizeof(ver_str), "%d", known_service_types[i].version); + #endif +- syslog(LOG_INFO, "Single search found"); ++ syslog(LOG_DEBUG, "Single search found"); + #ifdef DELAY_MSEARCH_RESPONSE + delay = random() / (1 + RAND_MAX / (1000 * mx_value)); + #ifdef DEBUG +@@ -1303,7 +1303,7 @@ ProcessSSDPData(int s, const char *bufr, + #ifdef DELAY_MSEARCH_RESPONSE + unsigned int delay_increment = (mx_value * 1000) / 15; + #endif +- syslog(LOG_INFO, "ssdp:all found"); ++ syslog(LOG_DEBUG, "ssdp:all found"); + for(i=0; known_service_types[i].s; i++) + { + #ifdef DELAY_MSEARCH_RESPONSE +@@ -1361,7 +1361,7 @@ ProcessSSDPData(int s, const char *bufr, + #endif + if(0 == memcmp(st, uuidvalue_igd, l)) + { +- syslog(LOG_INFO, "ssdp:uuid (IGD) found"); ++ syslog(LOG_DEBUG, "ssdp:uuid (IGD) found"); + SendSSDPResponse(s, sender, st, st_len, "", + announced_host, http_port, + #ifdef ENABLE_HTTPS +@@ -1371,7 +1371,7 @@ ProcessSSDPData(int s, const char *bufr, + } + else if(0 == memcmp(st, uuidvalue_wan, l)) + { +- syslog(LOG_INFO, "ssdp:uuid (WAN) found"); ++ syslog(LOG_DEBUG, "ssdp:uuid (WAN) found"); + SendSSDPResponse(s, sender, st, st_len, "", + announced_host, http_port, + #ifdef ENABLE_HTTPS +@@ -1381,7 +1381,7 @@ ProcessSSDPData(int s, const char *bufr, + } + else if(0 == memcmp(st, uuidvalue_wcd, l)) + { +- syslog(LOG_INFO, "ssdp:uuid (WCD) found"); ++ syslog(LOG_DEBUG, "ssdp:uuid (WCD) found"); + SendSSDPResponse(s, sender, st, st_len, "", + announced_host, http_port, + #ifdef ENABLE_HTTPS +@@ -1646,4 +1646,3 @@ SubmitServicesToMiniSSDPD(const char * h + syslog(LOG_DEBUG, "%d service submitted to MiniSSDPd", i); + return 0; + } +- +--- a/miniupnpd.8 ++++ b/miniupnpd.8 +@@ -42,10 +42,10 @@ and do not filter out low priority messa + report system uptime instead of daemon uptime to clients. + .TP + .B \-S0 +-disable "secure" mode so clients can only add mappings to other IPs ++disable UPnP IGD secure mode, allow adding port maps for non-requesting IP addresses + .TP + .B \-N +-enable NAT-PMP and PCP functionality. ++enable PCP/NAT-PMP protocols. + .TP + .BI \-u " uuid" + set the uuid of the UPnP Internet Gateway Device. +@@ -83,7 +83,7 @@ examples : + sets the value of BOOTID.UPNP.ORG SSDP header + .TP + .B \-1 +-force reporting IGDv1 in rootDesc when compiled as IGDv2 *use with care* ++force reporting IGDv1 in rootDesc when compiled as IGDv2 + .SH "SEE ALSO" + minissdpd(1) miniupnpc(3) + .SH BUGS +--- a/miniupnpd.c ++++ b/miniupnpd.c +@@ -859,7 +859,7 @@ set_startup_time(void) + } + else + { +- syslog(LOG_INFO, "system uptime is %lu seconds", uptime); ++ syslog(LOG_DEBUG, "system uptime is %lu seconds", uptime); + } + fclose(f); + startup_time -= uptime; +@@ -2098,6 +2098,22 @@ init(int argc, char * * argv, struct run + pidfilename = NULL; + #endif + ++syslog(LOG_NOTICE, "MiniUPnP daemon version " MINIUPNPD_VERSION " starting, enabled protocols: %s%s%s, ext_ifname=%s BOOTID=%u", ++ GETFLAG(ENABLEUPNPMASK) ? "UPnP IGD" : "", ++#ifdef ENABLE_NATPMP ++ GETFLAG(ENABLEUPNPMASK) && GETFLAG(ENABLENATPMPMASK) ? " & " : "", ++#ifdef ENABLE_PCP ++ GETFLAG(ENABLENATPMPMASK) ? "PCP/NAT-PMP" : "", ++#else ++ GETFLAG(ENABLENATPMPMASK) ? "NAT-PMP" : "", ++#endif ++#else ++ "", "", ++#endif ++ ext_if_name, upnp_bootid); ++syslog(LOG_INFO, "More information at https://miniupnp.tuxfamily.org/ or http://miniupnp.free.fr/"); ++syslog(LOG_NOTICE, "Extra logging by setting log level to info (-v) or debug (-vv)"); ++ + #ifdef USE_SYSTEMD + if (systemd_flag) { + int r = sd_notify(0, +@@ -2190,14 +2206,14 @@ print_usage: + #if defined(USE_PF) || defined(USE_IPF) + "\t-L sets packet log in pf and ipf on.\n" + #endif +- "\t-S0 disable \"secure\" mode so clients can add mappings to other ips\n" ++ "\t-S0 disable UPnP IGD secure mode, allow adding port maps for non-requesting IP addresses\n" + "\t-U causes miniupnpd to report system uptime instead " + "of daemon uptime.\n" + #ifdef ENABLE_NATPMP + #ifdef ENABLE_PCP +- "\t-N enables NAT-PMP and PCP functionality.\n" ++ "\t-N enable PCP/NAT-PMP protocols.\n" + #else +- "\t-N enables NAT-PMP functionality.\n" ++ "\t-N enable NAT-PMP protocol.\n" + #endif + #endif + "\t-B sets bitrates reported by daemon in bits per second.\n" +@@ -2217,7 +2233,7 @@ print_usage: + "\t \"deny 0-65535 0.0.0.0/0 0-65535\"\n" + "\t-b sets the value of BOOTID.UPNP.ORG SSDP header\n" + #ifdef IGD_V2 +- "\t-1 force reporting IGDv1 in rootDesc *use with care*\n" ++ "\t-1 force reporting IGDv1 in rootDesc\n" + #endif + "\t-v enables LOG_INFO messages, -vv LOG_DEBUG as well (default with -d)\n" + "\t-h / --help prints this help and quits.\n" +@@ -2398,21 +2414,9 @@ main(int argc, char * * argv) + return 0; + } + +- syslog(LOG_INFO, "version " MINIUPNPD_VERSION " starting%s%sext if %s BOOTID=%u", +-#ifdef ENABLE_NATPMP +-#ifdef ENABLE_PCP +- GETFLAG(ENABLENATPMPMASK) ? " NAT-PMP/PCP " : " ", +-#else +- GETFLAG(ENABLENATPMPMASK) ? " NAT-PMP " : " ", +-#endif +-#else +- " ", +-#endif +- GETFLAG(ENABLEUPNPMASK) ? "UPnP-IGD " : "", +- ext_if_name, upnp_bootid); + #ifdef ENABLE_IPV6 + if (strcmp(ext_if_name6, ext_if_name) != 0) { +- syslog(LOG_INFO, "specific IPv6 ext if %s", ext_if_name6); ++ syslog(LOG_NOTICE, "ext_ifname6=%s", ext_if_name6); + } + #endif + +@@ -2456,7 +2460,7 @@ main(int argc, char * * argv) + return 1; + } + v.port = listen_port; +- syslog(LOG_NOTICE, "HTTP listening on port %d", v.port); ++ syslog(LOG_NOTICE, "Listening for UPnP IGD (SOAP/HTTP) traffic on port %d/TCP", v.port); + #if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6) + if(!GETFLAG(IPV6DISABLEDMASK)) + { +@@ -2482,7 +2486,7 @@ main(int argc, char * * argv) + return 1; + } + v.https_port = listen_port; +- syslog(LOG_NOTICE, "HTTPS listening on port %d", v.https_port); ++ syslog(LOG_NOTICE, "Listening for UPnP IGD (SOAP/HTTPS) traffic on port %d/TCP", v.https_port); + #if defined(V6SOCKETS_ARE_V6ONLY) && defined(ENABLE_IPV6) + shttpsl_v4 = OpenAndConfHTTPSocket(&listen_port, 0); + if(shttpsl_v4 < 0) +@@ -2496,11 +2500,11 @@ main(int argc, char * * argv) + if(!GETFLAG(IPV6DISABLEDMASK)) { + if(find_ipv6_addr(lan_addrs.lh_first ? lan_addrs.lh_first->ifname : NULL, + ipv6_addr_for_http_with_brackets, sizeof(ipv6_addr_for_http_with_brackets)) > 0) { +- syslog(LOG_NOTICE, "HTTP IPv6 address given to control points : %s", ++ syslog(LOG_NOTICE, "IPv6 enabled with address: %s", + ipv6_addr_for_http_with_brackets); + } else { + memcpy(ipv6_addr_for_http_with_brackets, "[::1]", 6); +- syslog(LOG_WARNING, "no HTTP IPv6 address, disabling IPv6"); ++ syslog(LOG_WARNING, "No IPv6 address, disable support"); + SETFLAG(IPV6DISABLEDMASK); + } + } +@@ -2571,22 +2575,22 @@ main(int argc, char * * argv) + } + + #ifdef ENABLE_NATPMP +- /* open socket for NAT PMP traffic */ ++ /* open socket for NAT-PMP traffic */ + if(GETFLAG(ENABLENATPMPMASK)) + { + if(OpenAndConfNATPMPSockets(snatpmp) < 0) + #ifdef ENABLE_PCP + { +- syslog(LOG_ERR, "Failed to open sockets for NAT-PMP/PCP."); ++ syslog(LOG_ERR, "Failed to open sockets for PCP/NAT-PMP."); + } else { +- syslog(LOG_NOTICE, "Listening for NAT-PMP/PCP traffic on port %u", ++ syslog(LOG_NOTICE, "Listening for PCP/NAT-PMP traffic on port %u/UDP", + NATPMP_PORT); + } + #else + { +- syslog(LOG_ERR, "Failed to open sockets for NAT PMP."); ++ syslog(LOG_ERR, "Failed to open sockets for NAT-PMP."); + } else { +- syslog(LOG_NOTICE, "Listening for NAT-PMP traffic on port %u", ++ syslog(LOG_NOTICE, "Listening for NAT-PMP traffic on port %u/UDP", + NATPMP_PORT); + } + #endif +@@ -2695,6 +2699,24 @@ main(int argc, char * * argv) + } + #endif /* HAS_LIBCAP_NG */ + ++if (GETFLAG(ENABLEUPNPMASK) && !GETFLAG(SECUREMODEMASK)) ++ syslog(LOG_WARNING, "WARNING: secure_mode=no, allow adding port maps for non-requesting IP addresses via UPnP IGD"); ++#ifdef ENABLE_PCP ++if (GETFLAG(ENABLENATPMPMASK) && GETFLAG(PCP_ALLOWTHIRDPARTYMASK)) ++ syslog(LOG_WARNING, "WARNING: pcp_allow_thirdparty=yes, allow adding port maps for non-requesting IP addresses via PCP"); ++#endif ++if (GETFLAG(ENABLEUPNPMASK)) { ++#ifdef IGD_V2 ++ if (GETFLAG(FORCEIGDDESCV1MASK)) { ++ syslog(LOG_NOTICE, "UPnP IGD compatiblity mode set to IGDv1 (IPv4 only)"); ++ } else { ++ syslog(LOG_NOTICE, "UPnP IGD compatiblity mode set to IGDv2"); ++ } ++#else ++ syslog(LOG_NOTICE, "UPnP IGD compatiblity mode set to IGDv1 (IPv4 only)"); ++#endif ++} ++ + #ifdef USE_SYSTEMD + if (v.systemd_notify) { + upnp_update_status(); +@@ -3162,7 +3184,7 @@ main(int argc, char * * argv) + /* process SSDP packets */ + if(sudp >= 0 && FD_ISSET(sudp, &readset)) + { +- /*syslog(LOG_INFO, "Received UDP Packet");*/ ++ /*syslog(LOG_DEBUG, "Received UDP Packet");*/ + #ifdef ENABLE_HTTPS + ProcessSSDPRequest(sudp, (unsigned short)v.port, (unsigned short)v.https_port); + #else +@@ -3172,7 +3194,7 @@ main(int argc, char * * argv) + #ifdef ENABLE_IPV6 + if(sudpv6 >= 0 && FD_ISSET(sudpv6, &readset)) + { +- syslog(LOG_INFO, "Received UDP Packet (IPv6)"); ++ /*syslog(LOG_DEBUG, "Received UDP Packet (IPv6)");*/ + #ifdef ENABLE_HTTPS + ProcessSSDPRequest(sudpv6, (unsigned short)v.port, (unsigned short)v.https_port); + #else +--- a/miniupnpd.conf ++++ b/miniupnpd.conf +@@ -88,7 +88,8 @@ + # 120s and 86400s (24h) are suggested values from PCP-base + #min_lifetime=120 + #max_lifetime=86400 +-# allow THIRD_PARTY Option for MAP and PEER Opcodes (default is no) ++# PCP allow third-party mapping option (default no) ++# Allow adding port maps for non-requesting IP addresses + #pcp_allow_thirdparty=yes + + # table names for netfilter nft. Default is "filter" for both +@@ -131,8 +132,8 @@ + #bitrate_up=1000000 + #bitrate_down=10000000 + +-# Secure Mode, UPnP clients can only add mappings to their own IP +-# Enabled by default ++# UPnP IGD secure mode (default yes) ++# Allow adding port maps for requesting IP addresses only + #secure_mode=no + + # Default presentation URL is HTTP address on port 80 +--- a/natpmp.c ++++ b/natpmp.c +@@ -231,7 +231,7 @@ void ProcessIncomingNATPMPPacket(int s, + syslog(LOG_ERR, "inet_ntop(natpmp): %m"); + } + +- syslog(LOG_INFO, "NAT-PMP request received from %s:%hu %dbytes", ++ syslog(LOG_DEBUG, "NAT-PMP request received from %s:%hu %d bytes", + senderaddrstr, ntohs(senderaddr->sin_port), n); + + if(n<2 || ((((req[1]-1)&~1)==0) && n<12)) { +@@ -260,7 +260,7 @@ void ProcessIncomingNATPMPPacket(int s, + resp[3] = 1; /* unsupported version */ + } else switch(req[1]) { + case 0: /* Public address request */ +- syslog(LOG_INFO, "NAT-PMP public address request"); ++ syslog(LOG_DEBUG, "NAT-PMP public address request"); + FillPublicAddressResponse(resp, senderaddr->sin_addr.s_addr); + resplen = 12; + break; +@@ -340,7 +340,7 @@ void ProcessIncomingNATPMPPacket(int s, + eport_first = eport; + } else if(eport == eport_first) { /* no eport available */ + if(any_eport_allowed == 0) { /* all eports rejected by permissions */ +- syslog(LOG_ERR, "No allowed eport for NAT-PMP %hu %s->%s:%hu", ++ syslog(LOG_INFO, "No allowed eport for NAT-PMP %hu %s->%s:%hu", + eport, proto_itoa(proto), senderaddrstr, iport); + resp[3] = 2; /* Not Authorized/Refused */ + } else { /* at least one eport allowed (but none available) */ +--- a/netfilter_nft/nftnlrdr_misc.c ++++ b/netfilter_nft/nftnlrdr_misc.c +@@ -104,7 +104,7 @@ nft_mnl_connect(void) + return -1; + } + mnl_portid = mnl_socket_get_portid(mnl_sock); +- syslog(LOG_INFO, "mnl_socket bound, port_id=%u", mnl_portid); ++ syslog(LOG_DEBUG, "mnl_socket bound, port_id=%u", mnl_portid); + return 0; + } + +--- a/netfilter_nft/nftpinhole.c ++++ b/netfilter_nft/nftpinhole.c +@@ -159,7 +159,6 @@ find_pinhole(const char * ifname, + (0 == memcmp(&daddr, &p->daddr6, sizeof(struct in6_addr)))) { + + if (sscanf(p->desc, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) { +- syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", p->desc); + continue; + } + +@@ -397,7 +396,6 @@ get_pinhole_info(unsigned short uid, + if (timestamp) { + int uid_temp; + if (sscanf(p->desc, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid_temp, &ts) != 2) { +- syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", p->desc); + continue; + } + +@@ -461,7 +459,6 @@ clean_pinhole_list(unsigned int * next_t + continue; + + if (sscanf(p->desc, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) { +- syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", p->desc); + continue; + } + +--- a/pcpserver.c ++++ b/pcpserver.c +@@ -1087,7 +1087,7 @@ static void CreatePCPMap(pcp_info_t *pcp + else + r = CreatePCPMap_NAT(pcp_msg_info); + pcp_msg_info->result_code = r; +- syslog(r == PCP_SUCCESS ? LOG_INFO : LOG_ERR, ++ syslog(LOG_INFO, + "PCP MAP: %s mapping %s %hu->%s:%hu '%s'", + r == PCP_SUCCESS ? "added" : "failed to add", + proto_itoa(pcp_msg_info->protocol), +--- a/pf/pfpinhole.c ++++ b/pf/pfpinhole.c +@@ -246,12 +246,10 @@ int find_pinhole(const char * ifname, + (0 == memcmp(&daddr, &RULE.dst.addr.v.a.addr.v6, sizeof(struct in6_addr)))) { + #ifdef USE_LIBPFCTL + if(sscanf(RULE.label[0], PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) { +- syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", RULE.label[0]); + continue; + } + #else /* USE_LIBPFCTL */ + if(sscanf(RULE.label, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) { +- syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", RULE.label); + continue; + } + #endif /* USE_LIBPFCTL */ +@@ -563,7 +561,6 @@ int clean_pinhole_list(unsigned int * ne + return -1; + } + if(sscanf(RULE.label[0], PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) { +- syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", RULE.label[0]); + continue; + } + #else /* USE_LIBPFCTL */ +@@ -574,7 +571,6 @@ int clean_pinhole_list(unsigned int * ne + return -1; + } + if(sscanf(RULE.label, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) { +- syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", RULE.label); + continue; + } + #endif /* USE_LIBPFCTL */ +--- a/upnphttp.c ++++ b/upnphttp.c +@@ -513,7 +513,7 @@ ProcessHTTPPOST_upnphttp(struct upnphttp + if(h->req_soapActionOff > 0) + { + /* we can process the request */ +- syslog(LOG_INFO, "SOAPAction: %.*s", ++ syslog(LOG_DEBUG, "SOAPAction: %.*s", + h->req_soapActionLen, h->req_buf + h->req_soapActionOff); + ExecuteSoapAction(h, + h->req_buf + h->req_soapActionOff, +@@ -818,7 +818,7 @@ ProcessHttpQuery_upnphttp(struct upnphtt + for(i = 0; i<15 && *p != '\r'; i++) + HttpVer[i] = *(p++); + HttpVer[i] = '\0'; +- syslog(LOG_INFO, "HTTP REQUEST from %s : %s %s (%s)", ++ syslog(LOG_DEBUG, "HTTP REQUEST from %s : %s %s (%s)", + h->clientaddr_str, HttpCommand, HttpUrl, HttpVer); + ParseHttpHeaders(h); + if(h->req_HostOff > 0 && h->req_HostLen > 0) { +--- a/upnpredirect.c ++++ b/upnpredirect.c +@@ -641,7 +641,7 @@ get_upnp_rules_state_list(int max_rules_ + { + if(tmp->to_remove) + { +- syslog(LOG_NOTICE, "remove port mapping %hu %s because it has expired", ++ syslog(LOG_INFO, "remove port mapping %hu %s because it has expired", + tmp->eport, proto_itoa(tmp->proto)); + _upnp_delete_redir(tmp->eport, tmp->proto); + *p = tmp->next; +--- a/upnpsoap.c ++++ b/upnpsoap.c +@@ -847,7 +847,7 @@ GetSpecificPortMappingEntry(struct upnph + } + else + { +- syslog(LOG_INFO, "%s: rhost='%s' %s %s found => %s:%u desc='%s' duration=%u", ++ syslog(LOG_DEBUG, "%s: rhost='%s' %s %s found => %s:%u desc='%s' duration=%u", + action, + r_host ? r_host : "NULL", ext_port, protocol, int_ip, + (unsigned int)iport, desc, leaseduration); +@@ -1112,7 +1112,7 @@ GetGenericPortMappingEntry(struct upnpht + return; + } + +- syslog(LOG_INFO, "%s: index=%d", action, (int)index); ++ syslog(LOG_DEBUG, "%s: index=%d", action, (int)index); + + rhost[0] = '\0'; + r = upnp_get_redirection_infos_by_index((int)index, &eport, protocol, &iport, +@@ -2486,4 +2486,3 @@ SoapError(struct upnphttp * h, int errCo + BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen); + SendRespAndClose_upnphttp(h); + } +-