Class: TinyDTLS::UDPSocket

Inherits:
UDPSocket
  • Object
show all
Defined in:
lib/tinydtls/udpsocket.rb

Overview

This class implements a DTLS socket on top of a ruby UDPSocket. It isn’t currently nowhere near being API compatible with the ruby UDPSocket. Being 100% backwards compatible with the ruby UDPSocket is not possible to to tinydtls internals. For instance we can’t properly implement IO#select. It should thus be considered if it is really a good idea to extend the ruby UDPSocket in the long run.

Basic send and receive methods are implemented and should work.

Constant Summary collapse

MAX_RETRY =

Maximum of times a dtls_send is retried.

5.freeze
Write =
Proc.new do |ctx, sess, buf, len|
  addrinfo = Session.addr_from_ptr(sess)

  ctxobj = TinyDTLS::Context.from_ptr(ctx)
  ctxobj.sendfn.call(buf.read_string(len),
                     Socket::MSG_DONTWAIT,
                     addrinfo.ip_address, addrinfo.ip_port)
end
Read =
Proc.new do |ctx, sess, buf, len|
  addrinfo = Session.addr_from_ptr(sess)

  # We need to perform a reverse lookup here because
  # the #recvfrom function needs to return the DNS
  # hostname.
  sender = Socket.getaddrinfo(addrinfo.ip_address,
                              addrinfo.ip_port,
                              addrinfo.afamily,
                              :DGRAM, 0, 0, true).first

  ctxobj = TinyDTLS::Context.from_ptr(ctx)
  ctxobj.queue.push([buf.read_string(len), sender])

  # It is unclear to me why this callback even needs a return value,
  # the `tests/dtls-client.c` program in the tinydtls repository
  # simply uses 0 as a return value, so let's do that as well.
  0
end

Instance Method Summary collapse

Constructor Details

#initialize(address_family = Socket::AF_INET, timeout = nil) ⇒ UDPSocket

Returns a new instance of UDPSocket.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/tinydtls/udpsocket.rb', line 43

def initialize(address_family = Socket::AF_INET, timeout = nil)
  super(address_family)
  Wrapper::dtls_init

  @timeout = timeout.freeze
  @queue   = Queue.new
  @family  = address_family
  @sendfn  = method(:send).super_method
  @secconf = SecurityConfig.new

  @context = TinyDTLS::Context.new(@sendfn, @queue, @secconf)
  CONTEXT_MAP[@context.key] = @context

  if timeout.nil?
    @sessions = SessionManager.new(@context)
  else
    @sessions = SessionManager.new(@context, timeout)
  end

  @handler = Wrapper::DTLSHandlerStruct.new
  @handler[:write] = UDPSocket::Write
  @handler[:read] = UDPSocket::Read
  @handler[:get_psk_info] = SecurityConfig::GetPSKInfo
  Wrapper::dtls_set_handler(@context.to_ffi, @handler)
end

Instance Method Details

#add_client(id, key, default = false) ⇒ Object

Adds a new identity/key pair to the underlying TinyDTLS::SessionManager. By default the first pair added to the store will be used for establishing new handshakes, this behaviour can be changed using the optional default argument.



73
74
75
76
77
78
79
80
# File 'lib/tinydtls/udpsocket.rb', line 73

def add_client(id, key, default = false)
  @secconf.add_client(id, key)

  if default
    @secconf.default_id  = id
    @secconf.default_key = key
  end
end

#bind(host, port) ⇒ Object



82
83
84
85
# File 'lib/tinydtls/udpsocket.rb', line 82

def bind(host, port)
  super(host, port)
  start_thread
end

#closeObject

TODO: close_read,write



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/tinydtls/udpsocket.rb', line 89

def close
  # This method can't be called twice since we actually free memory
  # allocated by ruby-ffi in this function. Since we can't free it
  # twice we ensure that this function is only called once.
  return unless CONTEXT_MAP.has_key? @context.key

  @sessions.close
  unless @thread.nil?
    @thread.kill
    while @thread.alive?
      @thread.join
    end
  end

  # dtls_free_context sends messages to peers so we need to
  # explicitly free the dtls_context_t before closing the socket.
  Wrapper::dtls_free_context(@context.to_ffi)
  super

  # Assuming the @thread is already stopped at this point
  # we can safely access the CONTEXT_MAP without running
  # into any kind of concurrency problems.
  CONTEXT_MAP.delete(@context.key)
end

#connect(host, port) ⇒ Object



114
115
116
117
# File 'lib/tinydtls/udpsocket.rb', line 114

def connect(host, port)
  @defhost = host
  @defport = port
end

#recvfrom(len = -1,, flags = 0) ⇒ Object



119
120
121
122
# File 'lib/tinydtls/udpsocket.rb', line 119

def recvfrom(len = -1, flags = 0)
  ary = @queue.pop
  return [byteslice(ary.first, len), ary.last]
end

#recvfrom_nonblock(len = -1,, flag = 0, outbuf = nil, exception: true) ⇒ Object



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/tinydtls/udpsocket.rb', line 124

def recvfrom_nonblock(len = -1, flag = 0, outbuf = nil, exception: true)
  ary = nil
  begin
    ary = @queue.pop(true)
  rescue ThreadError
    if exception
      raise IO::EAGAINWaitReadable
    else
      return :wait_readable
    end
  end

  pay = byteslice(ary.first, len)
  unless outbuf.nil?
    outbuf << pay
  end

  return [pay, ary.last]
end

#recvmsg(maxmesglen = nil, flags = 0, maxcontrollen = nil, opts = {}) ⇒ Object

TODO: The recvmsg functions only implement a subset of the functionallity of the UDP socket class, e.g. they don’t return ancillary data.



148
149
150
151
# File 'lib/tinydtls/udpsocket.rb', line 148

def recvmsg(maxmesglen = nil, flags = 0, maxcontrollen = nil, opts = {})
  mesg, sender = recvfrom(maxmesglen.nil? ? -1 : maxmesglen, flags)
  return [mesg, to_addrinfo(*sender), 0, nil]
end

#recvmsg_nonblock(maxdatalen = nil, flags = 0, maxcontrollen = nil, opts = {}) ⇒ Object



153
154
155
156
# File 'lib/tinydtls/udpsocket.rb', line 153

def recvmsg_nonblock(maxdatalen = nil, flags = 0, maxcontrollen = nil, opts = {})
  mesg, sender = recvfrom_nonblock(maxdatalen.nil? ? -1 : maxdatalen, flags)
  return [mesg, to_addrinfo(*sender), 0, nil]
end

#send(mesg, flags, host = nil, port = nil) ⇒ Object

Raises:

  • (Errno::ECONNREFUSED)


158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/tinydtls/udpsocket.rb', line 158

def send(mesg, flags, host = nil, port = nil)
  start_thread

  if host.nil? and port.nil?
    if @defport.nil? or @defhost.nil?
      raise Errno::EDESTADDRREQ
    end

    host = @defhost
    port = @defport
  elsif port.nil? # host is not nil and must be a sockaddr_to
    port, host = Socket.unpack_sockaddr_in(host)
  end

  addr = Addrinfo.getaddrinfo(host, port, @family, :DGRAM).first

  # If a new thread has been started above a new handshake needs to
  # be performed by it. We need to block here until the handshake
  # was completed.
  #
  # The current approach is calling `Wrapper::dtls_write` up to
  # MAX_RETRY times. If we didn't manage to send our data to the
  # peer after MAX_RETRY times an exception is raised.
  MAX_RETRY.times do
    res = dtls_send(addr, mesg)
    if res > 0
      return res
    end

    sleep 1
  end

  raise Errno::ECONNREFUSED.new("DTLS handshake failed")
end