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

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

  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.from_ptr(sess).addrinfo

  # 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, nil, :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



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

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

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

  cptr = Wrapper::dtls_new_context(FFI::Pointer.new(id))
  @ctx = Wrapper::DTLSContextStruct.new(cptr)

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

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

Instance Method Details

#add_client(id, key) ⇒ Object



68
69
70
# File 'lib/tinydtls/udpsocket.rb', line 68

def add_client(id, key)
  @secconf.add_client(id, key)
end

#bind(host, port) ⇒ Object



72
73
74
75
# File 'lib/tinydtls/udpsocket.rb', line 72

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

#closeObject

TODO: close_read,write



79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/tinydtls/udpsocket.rb', line 79

def close
  @sessions.destroy!
  @thread.kill unless @thread.nil?

  # DTLS free context sends messages to peers so we need to
  # call it before we actually close the underlying socket.
  Wrapper::dtls_free_context(@ctx)
  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(object_id)
end

#connect(host, port) ⇒ Object



94
95
96
97
# File 'lib/tinydtls/udpsocket.rb', line 94

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

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



99
100
101
102
# File 'lib/tinydtls/udpsocket.rb', line 99

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



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/tinydtls/udpsocket.rb', line 104

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.



128
129
130
131
# File 'lib/tinydtls/udpsocket.rb', line 128

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



133
134
135
136
# File 'lib/tinydtls/udpsocket.rb', line 133

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



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/tinydtls/udpsocket.rb', line 138

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, nil, :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` until it
  # succeeds which is suboptimal because it doesn't take into
  # account that the handshake may fail.
  until (res = dtls_send(addr, mesg)) > 0
    sleep 1
  end

  return res
end