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.3.1.tm See also edit
#! /usr/bin/env tclsh
# Public IP library for Tcl, version 0.3.1
# Copyright (C) 2015, 2016 dbohdan
# License: MIT
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 google} {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] eq {ok}} {
set rdata [dict get [lindex [::dns::result $id] 0] rdata]
::dns::cleanup $id
return $rdata
} else {
set error [::dns::error $id]
::dns::cleanup $id
error $error
}
}
# 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
}