Class: StunClient::RFC3489NATDiscovery

Inherits:
Object
  • Object
show all
Defined in:
lib/stun-client/rfc3489/discovery/nat.rb

Overview

Helper, which should determine the NAT type.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host = nil, port = nil, protocol = :IPv4) ⇒ RFC3489NATDiscovery

Creates a new object for NAT discovery.

Parameters:

  • host (String) (defaults to: nil)

    The host of the STUN server to be used for NAT discovery. By default, the default server of the STUN client is used.

  • port (Integer) (defaults to: nil)

    The port of the STUN server.

  • protocol (Symbol) (defaults to: :IPv4)

    Defines whether the STUN server should be reached via IPv4 or IPv6. Accordingly like a NATv4 or NATv6 detected. Possible values are ‘:IPv4` or `:IPv6`.

Raises:

  • (ArgumentError)


27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/stun-client/rfc3489/discovery/nat.rb', line 27

def initialize host = nil, port = nil, protocol = :IPv4
  raise ArgumentError, 'Host must be a string' if host && (! host.is_a? String)
  raise ArgumentError, 'Port must be a integer' if port && (! port.is_a? Integer)

  case protocol
  when :IPv6
    inet = Socket::AF_INET6
    addr = '::'
  when :IPv4
    inet = Socket::AF_INET
    addr = '0.0.0.0'
  else
    raise ArgumentError, 'Unknown protocol'
  end

  @host = host
  @port = port

  @socket = UDPSocket.new inet
  @socket.do_not_reverse_lookup = true
  @socket.bind addr, 0
  @source_port = @socket.addr[1]
end

Class Method Details

.get_local_addressesArray

Returns an array with the IP addresses of the local device. rubocop:disable Naming/AccessorMethodName

Returns:

  • (Array)


12
13
14
# File 'lib/stun-client/rfc3489/discovery/nat.rb', line 12

def self.get_local_addresses
  return Socket.ip_address_list.map(&:ip_address)
end

Instance Method Details

#detect_natArray

Checks the NAT Type and returns an array with a corresponding description. In the worst case, it can take up to 38 seconds for the function to finish.

Important: Keep in mind that RFC3489 is outdated and that measurements are not always accurate.

Returns:

  • (Array)

    :preserves_ports The local ports are mapped to the outside. Example: If a socket is created locally with port 4000, this is also the source port when a server is requested. :random_port The opposite of ‘:preserves_ports`.

    :nat The device is located behind a NAT. :no_nat The device is not located behind a NAT.

    :open_internet The device is openly located on the Internet. No filtering is performed. :symmetric_udp_firewall The device is located behind a firewall that filters by source port and source address.

    :full_cone_nat The device is located behind a Full Cone NAT. The NAT does not filter the source port or source address. :restricted_nat The device is behind a restrictive NAT. The NAT filters only by source address, but not by source port. :port_restricted_nat The device is located behind a port-restricted NAT. The NAT filters by source port and source address.

    :udp_blocked The STUN server could not be reached. Therefore, the measurement could not be executed. :udp_sometimes_blocked This case should never leak. It means that an inconsistent NAT/firewall is used. One of the requests to the STUN server was blocked. Therefore, the measurement could not be completed.



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/stun-client/rfc3489/discovery/nat.rb', line 84

def detect_nat
  # rubocop:disable Style/ConditionalAssignment
  flags = []

  test1v1 = do_query @host, @port

  if test1v1
    if test1v1[:port] == @source_port
      flags << :preserves_ports
    else
      flags << :random_port
    end

    if RFC3489NATDiscovery.get_local_addresses.include?(test1v1[:host]) &&
       test1v1[:port] == @source_port

      flags << :no_nat

      test2 = do_query @host, @port, change_ip: true, change_port: true
      if test2
        flags << :open_internet
      else
        flags << :symmetric_udp_firewall
      end
    else
      flags << :nat

      test2 = do_query @host, @port, change_ip: true, change_port: true
      if test2
        flags << :full_cone_nat
      else
        test1v2 = do_query test1v1[:changed_host], test1v1[:changed_port].to_i
        if test1v2
          if test1v1[:host] == test1v2[:host] &&
             test1v1[:port] == test1v2[:port]
            test3 = do_query @host, @port, change_port: true
            if test3
              flags << :restricted_nat
            else
              flags << :port_restricted_nat
            end
          else
            flags << :symmetric_nat
          end
        else
          flags << :udp_sometimes_blocked
        end
      end
    end

  else
    flags << :udp_blocked
  end

  # flags.sort!
  return flags

  # rubocop:enable Style/ConditionalAssignment
end