#!/bin/sh [ -r /mod/etc/conf/mod.cfg ] && . /mod/etc/conf/mod.cfg # Global result IP="" helpmsg() { cat << EOF get_ip - determine external IP address Usage: $0 [option] -a, --all - use all methods (order: stun, route, dsld, webcm) [recommended] -d, --dsld - use showdsldstat (since firmware 04.86) -r, --route - use routing table -s, --stun - use STUN/VoIP (server: $MOD_GET_IP_STUN) -w, --webcm - use webcm CGI handler / ctlmgr_ctl -?, --help - print this help message Current default method: $MOD_GET_IP_METHOD Note: If environment variable IPADDR is set and contains a public IP (not from a private or link-local network), its value will be returned. EOF } # Detect private (RFC 1918) or link-local (RFC 5735) IPs # Returns 0 for public IP, 1 for private IP and 2 if IP # is empty or has invalid format ip_public() { # format check (not checking >255) local ip="$(echo "$1" | sed -rn '/^([0-9]{1,3}\.){3}[0-9]{1,3}$/p')" [ -z "$ip" ] && return 2 # 10.0.0.0/8 (private), 192.168.0.0/16 (private), 169.254.0.0/16 (link-local) ( [ "$ip" != "${ip#10.}" ] || [ "$ip" != "${ip#192.168.}" ] || [ "$ip" != "${ip#169.254.}" ] ) && return 1 # 172.16.0.0/12 (private) [ "$ip" == "${ip#172.}" ] && return 0 ip=$(echo $ip | cut -d '.' -f 2) [ $ip -ge 16 ] && [ $ip -le 31 ] && return 1 || return 0 } via_dsld() { # Firmware ca. 04.68 and newer should be safe, but some boxes had it since 04.31 IP="$(/sbin/showdsldstat 2>/dev/null | sed -nr 's/0:.*ip ([0-9.]+).*/\1/p')" ip_public "$IP" || return 1 } # AVM's home-brew NAT does not expose the external IP on network interface # "dsl", but sets a route to it. This method should work on most DSL boxes, # with two known exceptions: # a) DNS servers are configured manually to hosts with public IP address. # In this case multiple candidate IPs would be found. Thus we return an # error rather than trying to guess which IP might be correct. # b) If the box does not connect to DSL via PPPoE but uses the DSL modem as a # bridge to e.g. a public /21 network, the external IP is not listed in # the routing table at all, but the /21 network instead. # There might be more exceptions in case of routing table manipulation (maybe # for VPNs or if for any reason a single external host IP is explicitly added # to the routing table). via_route() { local candidate_count=0 for ip in $(route -n | sed -nr 's/^([1-9][0-9]*(\.[0-9]+){3}) +0(\.0){3} +255(\.255){3} +.* dsl$/\1/p'); do ip_public "$ip" && IP="$ip" && candidate_count=$((candidate_count + 1)) done [ $candidate_count -eq 1 ] && return 0 unset IP return 1 } # On IP clients or UMTS, we are mostly behind a NAT and the only way to # determine the external IP is to get the information from an external # server. The most efficient method is STUN (stun-ip applet will try 3x). # ip is cached for max 1 minute via_stun() { IP=$(find /tmp/.get_ip -mmin 0 -exec cat {} ';' 2>/dev/null) [ -n "$IP" ] && return 0 IP=$(stun-ip $MOD_GET_IP_STUN) [ $? -eq 0 -a -n "$IP" ] && echo "$IP" > /tmp/.get_ip && return 0 return 1 } via_webcm() { local queryfile="/usr/www/all/html/query.txt" local querystring="" if which ctlmgr_ctl >/dev/null; then # Firmware ca. 04.76 and newer IP=$(ctlmgr_ctl r connection0 pppoe:status/ip) else if [ "$(sed -n '/var:n\[/p' $queryfile)" ]; then # Firmware ca. 04.84 and newer (should never be used, see above) querystring="var:n[0]=connection0:pppoe:status/ip" else # Older firmware querystring="var:cnt=1&var:n0=connection0:pppoe:status/ip" fi IP="$(/usr/www/html/cgi-bin/webcm "getpage=${queryfile}&${querystring}")" fi # ctlmgr_ctl return values [box=val]: 7170=176, 7270=177, 7141=172 # Caveat: must use "-o" instead of "||", otherwise "$?" would be reset [ $? -eq 0 -o $? -ge 170 ] && ip_public "$IP" && return 0 || return 1 } # If multid (or whoever) has already determined the external IP, use it ip_public "$IPADDR" && echo "$IPADDR" && exit 0 # Set user-defined method (e.g. via web UI) if no argument is given [ $# -eq 0 ] && method="$MOD_GET_IP_METHOD" || method="$1" case $method in -a|--all|""|-e|--extquery|-o|--ostat) # for compatibility reason only, may be removed later [ "$method" ] && [ "$method" != "-a" ] && [ "$method" != "--all" ] && echo "warning: method $method is obsolete, using --all instead" >&2 # Why this order? # 1.) STUN should always work and is fast # 2.) route is fast, works in all firmwares, problematic routing configs are rare # 3.) dsld is faster than ctlmgr/webcm, but not always available # 4.) ctlmgr/webcm is slow, but should work in all firmwares on DSL boxes for mode in stun route dsld webcm; do via_$mode [ $? -eq 0 ] && break done ;; -d|--dsld) via_dsld ;; -r|--route) via_route ;; -s|--stun) via_stun ;; -w|--webcm) via_webcm ;; -?|-h|--help) helpmsg exit 0 ;; *) helpmsg >&2 exit 1 ;; esac [ $? -ne 0 ] && echo "get_ip error" >&2 && exit 1 echo "$IP"