#!/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_match() { [ "$#" -ge 1 ] || return 1 local _hostname="$1" echo "$_hostname" | grep -E "^[[:alnum:]][-[:alnum:]]{0,62}(\.[[:alnum:]][-[:alnum:]]{0,62})*\.?$" } # ip_match 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_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 `_` 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 `_` 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