Class: StunClient::GenericClient

Inherits:
Object
  • Object
show all
Defined in:
lib/stun-client/generic/client.rb

Overview

Generic STUN client. This contains functions which are used by all STUN clients independent of the RFC.

Direct Known Subclasses

RFC3489Client

Instance Method Summary collapse

Constructor Details

#initialize(parameters) ⇒ GenericClient

Creates a new generic client.

Parameters:

  • parameters (Hash)

    :host (optional) The STUN server. By default ‘stun.1and1.de` (German provider). :port (optional) The port of the STUN server. By default 3478. :socket A UDPSocket through which the requests are sent to the server.

Raises:

  • (ArgumentError)


14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/stun-client/generic/client.rb', line 14

def initialize parameters
  raise ArgumentError, 'Missing socket' if ! parameters.has_key? :socket
  raise ArgumentError, 'Socket must be a UDPSocket' if ! parameters[:socket].is_a? UDPSocket

  if parameters.has_key?(:host) && parameters[:host]
    raise ArgumentError, 'Host must be a string' if ! parameters[:host].is_a? String
  else
    parameters[:host] = 'stun.1und1.de'
  end

  if parameters.has_key?(:port) && parameters[:port]
    raise ArgumentError, 'Port must be a integer' if ! parameters[:port].is_a? Integer
  else
    parameters[:port] = 3478
  end

  @socket = parameters[:socket]
  @host = parameters[:host]
  @port = parameters[:port]
end

Instance Method Details

#parse_mapped_address(value, prefix = nil) ⇒ Hash

Parses an IP port pair and returns a hash with the information.

It throws the exception ‘UnableToInterpretResponse` if the IP version is unknown. Known are IPv4 and IPv6.

The ‘UnableToInterpretResponse` is thrown if the IP address is not encoded correctly.

Parameters:

  • value (Hash)

    A hash containing the values of a MappedAddress attribute.

  • prefix (String) (defaults to: nil)

    Prefix which is prefixed to each key of returned.

Returns:

  • (Hash)


46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/stun-client/generic/client.rb', line 46

def parse_mapped_address value, prefix = nil
  res = {}
  family_sym = "#{prefix}family".to_sym
  host_sym = "#{prefix}host".to_sym
  port_sym = "#{prefix}port".to_sym

  case value[:family]
  when 1
    res[family_sym] = :IPv4
  when 2
    res[family_sym] = :IPv6
  else
    raise UnableToInterpretResponse, "Unknown IP family. Family is #{value[family_sym].inspect}"
  end

  begin
    res[host_sym] = IPAddr.ntop value[:address]
  rescue IPAddr::AddressFamilyError
    raise UnableToInterpretResponse, 'Failed to parse ip address'
  end

  res[port_sym] = value[:port]

  return res
end

#receive_messageObject

Attempts to receive a response from the server. If there is no response, ‘false` is returned.



177
178
179
180
181
182
183
# File 'lib/stun-client/generic/client.rb', line 177

def receive_message
  begin
    @socket.recvfrom_nonblock(1024)
  rescue IO::WaitReadable
    return false
  end
end

#send_binary(parameters, request, new_interval, attempts_default, interval_default) ⇒ Array

Sends a request in binary form to a server.

‘ArgumentError` is thrown if one of the arguments is not valid. `TimeoutError` is thrown if the server does not respond.

Parameters:

  • parameters (Hash)

    Contains ‘:attempts` for the number of attempts to send the message to the server.

  • request (String)

    A binary string to be sent to the server.

  • new_interval (Proc)

    A lambda function. This is called after each failed attempt to get a response from the server. As argument it gets the current interval in which the message is tried to be sent to the server. As return value a new interval is expected, which should be waited for. The result is rounded to three decimal places outside the function. The interval is specified in seconds.

  • attempts_default (Integer)

    The default number of attempts to send the message. Can be overridden by ‘parameters`.

  • interval_default (Integer)

    The default initial interval. Can be overwritten by ‘parameters`.

Returns:

  • (Array)

    Response from the server. See UDPSocket#recvfrom_nonblock

Raises:



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/stun-client/generic/client.rb', line 126

def send_binary parameters, request, new_interval, attempts_default, interval_default
  attempts = attempts_default
  if parameters.has_key?(:attempts) && parameters[:attempts]
    attempts = parameters[:attempts]
    raise ArgumentError, 'Attepts must be a integer' if ! attempts.is_a? Integer
  end

  interval = interval_default
  if parameters.has_key?(:interval) && parameters[:interval]
    interval = parameters[:interval]
    raise ArgumentError, 'Interval must be a integer' if ! (interval.is_a?(Integer) || interval.is_a?(Float))
  end
  interval = interval.to_f

  response = try_sending request, attempts, interval, new_interval

  raise TimeoutError, 'Server failed to answer' if ! response

  return response
end

#try_sending(request, attempts, interval, new_interval) ⇒ Object

Attempts to send a message ‘request` `attempts` times to the server. The meaning of the intervals can be taken from the `send_binary` function.

Parameters:

  • request (String)
  • attempts (Integer)
  • interval (Integer)
  • new_interval (Proc)

Returns:

  • Returns either a response (see UDPSocket#recvfrom_nonblock) or false if the server did not respond.



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/stun-client/generic/client.rb', line 156

def try_sending request, attempts, interval, new_interval
  attempts.times do |index|
    @socket.send(request, 0, @host, @port)

    # Waiting for answer. No remote server should be able
    # to respond **immediately**.
    sleep interval

    resp = receive_message
    return resp if resp

    if index != (attempts - 1)
      interval = new_interval.call(interval).round(3)
    end
  end

  return false
end

#validate_and_get_change(parameters) ⇒ Array

The function validates the Change IP and Change Port parameters and returns those. If no parameters are specified, the default value false is used.

Parameters:

  • parameters (Hash)

    Hash containing ‘:change_ip` and `:change_port`. These values should be either `true`, `false` or `nil`.

Returns:

  • (Array)

    Returns an array with two integers. If Change Port is true, the first integer is 1, otherwise 0. The same is applies for Change IP and the second integer.



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/stun-client/generic/client.rb', line 82

def validate_and_get_change parameters
  change_port = 0
  change_ip = 0

  if parameters.has_key?(:change_port) && parameters[:change_port]
    if ! (parameters[:change_port].is_a?(TrueClass) || parameters[:change_port].is_a?(FalseClass))
      raise ArgumentError, 'Change port must be a boolean'
    end

    change_port = ( parameters[:change_port] ? 1 : 0 )
  end

  if parameters.has_key?(:change_ip) && parameters[:change_ip]
    if ! (parameters[:change_ip].is_a?(TrueClass) || parameters[:change_ip].is_a?(FalseClass))
      raise ArgumentError, 'Change IP must be a boolean'
    end

    change_ip = ( parameters[:change_ip] ? 1 : 0 )
  end

  return change_port, change_ip
end