Public IP

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.4.0.tm

See also

Code

#! /usr/bin/env tclsh
# Public IP library for Tcl, version 0.4.0.
# Copyright (c) 2015-2016, 2020-2021 D. Bohdan.
# License: MIT.

package require Tcl 8.5-10

namespace eval public-ip {
    package require dns

    variable ready 0
    variable services {
        ipv4 {
            akamai {
                whoami.akamai.net
                -server ns1-1.akamaitech.net
                -type A
            }
            google {
                o-o.myaddr.l.google.com
                -server ns1.google.com
                -type TXT
            }
            opendns {
                myip.opendns.com
                -server resolver1.opendns.com
                -type A
            }
            ultradns {
                whoami.ultradns.net
                -server pdns1.ultradns.net
                -type A
            }
        }
    }
}


# Resolve the public IP address using one service.
# Usage: query ?akamai|google|opendns|ultradns?
proc public-ip::query {{service google} {protocol ipv4}} {
    variable services

    set service [dict get $services $protocol $service]
    set id [dns::resolve \
        {*}$service \
        -command [namespace current]::query-ready \
    ]

    vwait [namespace current]::ready

    if {[dns::status $id] ne {ok}} {
        set error [dns::error $id]
        dns::cleanup $id
        error $error
    }

    set rdata [dict get [lindex [dns::result $id] 0] rdata]
    dns::cleanup $id
    return $rdata
}


# Used internally by [query].
proc public-ip::query-ready token {
    set [namespace  current]::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}} {
    variable services

    set ip {}

    foreach service [dict keys [dict get $services $protocol]] {
        set resolved [query $service $protocol]

        if {$ip eq {}} {
            set ip $resolved
            set ipResolver $service
        } elseif {$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
}