Stunning: Determining Your Public IP

May 17, 2014

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