Programmatically fetching your public IP address (aka, internet visible IP) can be tough. Most often, I’ve done something silly like fetching whatismyip.com and then parsing the page. That introduces a pretty bad dependency.

Fortunately, there’s actually a protocol for asking for your internet visible IP called Session Traversal Utilities for NAT (or STUN). It’s used by services that require peer-to-peer connection negotiation (like Skype, Google hangouts, etc).

Here’s an example Ruby script that will hit up a few public STUN servers (starting with Google’s) and return your found IP:

require 'socket'
require 'timeout'

class StunClient
  def initialize(host, port)
    @host = host
    @port = port
  end

  def get_ip
    begin
      Timeout::timeout(0.5) {
        socket = UDPSocket.new
        data = [0x0001,0].pack("nn") + Random.new.bytes(16)
        socket.send(data, 0, @host, @port)
        data, _ = socket.recvfrom(1000)
        type, length = data.unpack("nn")

        # if not a message binding response
        return nil unless type == 0x0101

        data = data[20..-1]
        while data.size > 0
          type, length = data.unpack("nn")
          # if attr type is ATTR_MAPPED_ADDRESS, return it
          if type == 0x0001
            values = data[4...4+length].unpack("CCnCCCC")
            return values[3..-1]*"."
          end
          data = data[4+length..-1]
        end

        return nil
      }
    rescue Timeout::Error
      return nil
    end
  end

  def self.get_ip
    servers = [["stun.l.google.com", 19302], ["stun.ekiga.net", 3478], ["stunserver.org", 3478]]
    servers.each do |host, port|
      ip = StunClient.new(host, port).get_ip
      return ip unless ip.nil?
    end
    nil
  end
end


puts StunClient.get_ip