ruby sockets openssl bidirectional two-way

¿Cómo puedo crear un socket SSL bidireccional en Ruby?



sockets openssl (1)

No estoy familiarizado con Ruby en sí mismo, pero con los problemas de usar select con sockets basados ​​en SSL. Los sockets SSL se comportan de manera diferente a los sockets TCP ya que los datos SSL se transfieren en marcos y no como una secuencia de datos, pero sin embargo la semántica de flujo se aplica a la interfaz del socket.

Vamos a explicar esto con un ejemplo, primero usando una conexión TCP simple:

  • El servidor envía 1000 bytes en una sola escritura.
  • Los datos se entregarán al cliente y se colocarán en el búfer del zócalo interno. Por lo tanto, seleccionar devolverá que los datos están disponibles.
  • La aplicación cliente solo lee primero 100 bytes.
  • Los otros 900 bytes se mantendrán en el búfer de socket interno. La próxima llamada de select devolverá nuevamente que los datos están disponibles, ya que select mira el buffer del socket en el kernel.

Con SSL esto es diferente:

  • El servidor envía 1000 bytes en una sola escritura. La pila SSL encriptará estos 1000 bytes en un único marco SSL y enviará este marco al cliente.
  • Este marco SSL se entregará al cliente y se colocará en el búfer de socket interno. Por lo tanto, seleccionar devolverá que los datos están disponibles.
  • Ahora la aplicación cliente lee solo 100 bytes. Como la trama SSL contiene 1000 bytes y como necesita el fotograma completo para descifrar los datos, la pila SSL leerá el fotograma completo desde el zócalo, sin dejar nada en el búfer del zócalo interno. Descifrará el cuadro y devolverá los 100 bytes solicitados a la aplicación. El resto de 900 bytes descifrados se mantendrá en la pila SSL en el espacio de usuario.
  • Dado que el búfer de socket del kernel está vacío, la siguiente llamada de select no devolverá que los datos estén disponibles. Por lo tanto, si la aplicación solo se ocupa de seleccionar, ahora no habrá datos no leídos, es decir, los 900 bytes guardados en la pila SSL.

Cómo lidiar con esta diferencia:

  • Una forma es intentar siempre leer al menos 32768 datos, porque este es el tamaño máximo de un marco SSL. De esta manera, uno puede estar seguro de que aún no se conservan datos en la pila SSL (la lectura SSL no leerá los límites del marco SSL).
  • Otra forma es verificar la pila SSL para datos ya descifrados antes de llamar a seleccionar. Solo si no se guardan datos en la pila SSL, debe seleccionarse. Para verificar tales "datos pendientes", use el método pendiente .
  • Intente leer más datos del socket sin bloqueo hasta que no haya más datos disponibles. De esta forma, puede estar seguro de que aún no hay datos pendientes en la pila SSL. Pero tenga en cuenta que esto también hará lecturas en el zócalo TCP subyacente y, por lo tanto, podría preferir los datos en el zócalo SSL en comparación con los datos en otros zócalos (que solo se leen luego de una selección exitosa). Esta es la recomendación que ha citado, pero preferiría las otras.

Estoy construyendo una biblioteca cliente de Ruby que se conecta a un servidor y espera datos, pero también permite a los usuarios enviar datos llamando a un método.

El mecanismo que uso es tener una clase que inicialice un par de socket, así:

def initialize @pipe_r, @pipe_w = Socket.pair(:UNIX, :STREAM, 0) end

El método que les permito a los desarrolladores llamar para enviar datos al servidor se ve así:

def send(data) @pipe_w.write(data) @pipe_w.flush end

Luego tengo un ciclo en un hilo separado, donde selecciono desde un socket conectado al servidor y desde @pipe_r :

def socket_loop Thread.new do socket = TCPSocket.new(host, port) loop do ready = IO.select([socket, @pipe_r]) if ready[0].include?(@pipe_r) data_to_send = @pipe_r.read_nonblock(1024) socket.write(data_to_send) end if ready[0].include?(socket) data_received = socket.read_nonblock(1024) h2 << data_received break if socket.nil? || socket.closed? || socket.eof? end end end end

Esto funciona maravillosamente, pero solo con un TCPSocket normal como el ejemplo. Necesito usar un OpenSSL::SSL::SSLSocket en OpenSSL::SSL::SSLSocket lugar, sin embargo, según el IO.select docs :

La mejor forma de utilizar IO.select es invocándolo después de métodos que no son de bloqueo como read_nonblock, write_nonblock, etc.

[...]

Especialmente, la combinación de métodos no bloqueantes y IO.select es preferida para objetos similares a IO como OpenSSL :: SSL :: SSLSocket.

De acuerdo con esto, necesito llamar a IO.select después de métodos nonblocking, mientras que en mi ciclo lo uso antes, así que puedo seleccionar entre 2 objetos IO diferentes.

El ejemplo dado sobre cómo usar IO.select con un socket SSL es:

begin result = socket.read_nonblock(1024) rescue IO::WaitReadable IO.select([socket]) retry rescue IO::WaitWritable IO.select(nil, [socket]) retry end

Sin embargo, esto solo funciona si IO.select se usa con un único objeto IO.

Mi pregunta es: ¿cómo puedo hacer que mi ejemplo anterior funcione con un socket SSL, dado que necesito seleccionar tanto el @pipe_r como los objetos del socket ?

EDITAR: He intentado lo que sugirió @ steffen-ullrich, pero fue en vano. Pude hacer que mis exámenes pasaran usando lo siguiente:

loop do begin data_to_send = @pipe_r.read_nonblock(1024) socket.write(data_to_send) rescue IO::WaitReadable, IO::WaitWritable end begin data_received = socket.read_nonblock(1024) h2 << data_received break if socket.nil? || socket.closed? || socket.eof? rescue IO::WaitReadable IO.select([socket, @pipe_r]) rescue IO::WaitWritable IO.select([@pipe_r], [socket]) end end

Esto no se ve tan mal, pero cualquier entrada es bienvenida.