Module: RightSupport::Net::AddressHelper

Included in:
RightSupport::Net
Defined in:
lib/right_support/net/address_helper.rb

Overview

A helper module that provides some useful methods for querying the local machine about its network addresses.

This module is automatically included into the eigenclass of RightSupport::Net for convenience. Any of the methods available in this module can be called as RightSupport::Net.foo without needing to include this module.

Defined Under Namespace

Classes: NoSuitableInterface

Constant Summary collapse

PRIVATE_IP_REGEX =
/^(10\.|192\.168\.|172\.(1[6789]|2[0-9]|3[01]))/
LOOPBACK_IP_REGEX =
/^(127\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/

Instance Method Summary collapse

Instance Method Details

#local_hostname_addresses(address_family) ⇒ Object

Determine all network addresses of the local machine that are resolvable using either the machine’s hostname or “localhost”. Typically this allows us to discover the local IP addresses of “interesting” network interfaces without relying on OS-specific tools such as ifconfig/ipconfig.

Parameters

address_family(Integer)

Socket::AF_INET or Socket::AF_INET6

Return

addresses(Array)

a list of IP addresses in dotted-quad notation



84
85
86
87
88
89
90
91
92
93
# File 'lib/right_support/net/address_helper.rb', line 84

def local_hostname_addresses(address_family)
  loopback = Socket.getaddrinfo('localhost', 1,
               address_family, Socket::SOCK_STREAM,
               nil, nil).collect { |x| x[3] }

  real = Socket.getaddrinfo(Socket.gethostname, 1,
           address_family, Socket::SOCK_STREAM,
           nil, nil).collect { |x| x[3] }
  (loopback + real).uniq
end

#local_routable_address(address_family) ⇒ Object

Determine the network address of some local interface that has a route to the public Internet.

On some systems, Socket.getaddrinfo(Socket.gethostname, …) does not return any IP addresses, for instance because the local hostname cannot be resolved by DNS. This method can be used to detect “my IP address” in such cases.

This code does NOT make a connection or send any packets (to 64.233.187.99 which is google). Since UDP is a stateless protocol, connect() merely makes a system call which figures out how to route the packets based on the address and what interface (and therefore IP address) it should bind to. addr() returns an array containing the family (AF_INET), local port, and local address (which is what we want) of the socket.

Parameters

address_family(Integer)

Socket::AF_INET or Socket::AF_INET6

Return

address(String)

a single IP address in dotted-quad notation



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/right_support/net/address_helper.rb', line 55

def local_routable_address(address_family)
  case address_family
    when Socket::AF_INET
      remote_address = '64.233.187.99'
    when Socket::AF_INET6
      remote_address = '2607:f8b0:4003:c00::68'
    else
      raise ArgumentError, "Routable address discovery only works for AF_INET or AF_INET6"
  end

  # turn off reverse DNS resolution temporarily
  orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
  UDPSocket.open(address_family) do |s|
    s.connect remote_address, 1
    s.addr.last
  end
ensure
  Socket.do_not_reverse_lookup = orig
end

#my_ipv4_address(flavor = :private) ⇒ Object

Determine an IPv4 address of the local machine that falls into the given range of IP address space (public, private or loopback). If multiple suitable addresses are found, the same address will be consistently returned but there is no way to influence which address that will be.

Parameters

flavor(Symbol)

One of :public, :private or :loopback

Return

addresses(Array)

a list of IP addresses in dotted-quad notation



129
130
131
132
133
134
135
# File 'lib/right_support/net/address_helper.rb', line 129

def my_ipv4_address(flavor=:private)
  candidates = my_ipv4_addresses(flavor)
  raise NoSuitableInterface, "No interface had a #{flavor} IPv4 address" if candidates.empty?

  #Ensure we consistently the same interface by doing a lexical sort
  return candidates.sort.first
end

#my_ipv4_addresses(flavor = :private) ⇒ Object

Determine all IPv4 addresses of the local machine that fall into the given range of IP address space (public, private or loopback).

Parameters

flavor(Symbol)

One of :public, :private or :loopback

Return

addresses(Array)

a list of IP addresses in dotted-quad notation



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/right_support/net/address_helper.rb', line 103

def my_ipv4_addresses(flavor=:private)
  all = local_hostname_addresses(Socket::AF_INET)
  all << local_routable_address(Socket::AF_INET)
  all.uniq!

  case flavor
    when :public
      return all.select { |ip| ip !~ PRIVATE_IP_REGEX && ip !~ LOOPBACK_IP_REGEX }
    when :private
      return all.select { |ip| ip =~ PRIVATE_IP_REGEX }
    when :loopback
      return all.select { |ip| ip =~ LOOPBACK_IP_REGEX }
    else
      raise ArgumentError, "flavor must be :public, :private or :loopback"
  end
end