Version 1 of Public IP

Updated 2016-07-06 19:00:19 by dbohdan

dbohdan 2015-03-18: Here is my solution for how to get your globally routable IP address. It is based on this helpful answer on ServerFault and uses the DNS protocol. One upside of the approach is that the DNS servers used are more likely to last and have a good uptime than your average free your-IP-over-a-REST-API service.

Download with wiki-reaper: wiki-reaper -x 44451 0 > publicip-0.2.0.tm

package require Tcl 8.5

namespace eval ::public-ip {
    package require dns
    variable services {
        ipv4 {
            google {{o-o.myaddr.l.google.com} -server ns1.google.com -type TXT}
            akamai {whoami.akamai.net -server ns1-1.akamaitech.net -type A}
            opendns {myip.opendns.com -server resolver1.opendns.com -type A}
        }
    }
    variable ready 0
}

# Resolve the public IP address using one service.
# Usage: query google|akamai|opendns
proc ::public-ip::query {service {protocol ipv4}} {
    set id [::dns::resolve \
            {*}[dict get ${::public-ip::services} $protocol $service]\
            -command ::public-ip::query-ready]
    vwait ::public-ip::ready
    if {[::dns::status $id] ne "ok"} {
        error [::dns::error $id]
    }
    set result [dict get [lindex [::dns::result $id] 0] rdata]
    ::dns::cleanup $id
    return $result
}

# Used internally by [query].
proc ::public-ip::query-ready {token} {
    set ::public-ip::ready 1
}

# Resolve the public IP address using all of the available services for the
# selected protocol. If one of the services returns a different address than the
# others generate an error.
proc ::public-ip::query-all {{protocol ipv4}} {
    set ip ""
    foreach service \
            [dict keys [dict get ${::public-ip::services} $protocol]] {
        set resolved [query $service $protocol]
        if {$ip eq ""} {
            set ip $resolved
            set ipResolver $service
        } else {
            if {$ip ne $resolved} {
                error "got $protocol address $ip from $ipResolver but\
                        $resolved from $service"
            }
        }
    }
    return $ip
}

proc ::public-ip::test {{protocol ipv4}} {
    puts [query-all $protocol]
}

# If this is the main script...
if {[info exists argv0] && ([file tail [info script]] eq [file tail $argv0])} {
    ::public-ip::test
}