2016-04-22 1 views
6

Je construis une bibliothèque client Ruby qui se connecte à un serveur et attend des données, mais permet également aux utilisateurs d'envoyer des données en appelant une méthode.Comment puis-je créer un socket SSL bidirectionnel dans Ruby

Le mécanisme que j'utilise est d'avoir une classe qui initialise une paire de socket, comme ceci:

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

La méthode que je permettre aux développeurs d'appeler pour envoyer des données au serveur ressemble à ceci:

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

Ensuite, j'ai une boucle dans un fil séparé, où je sélectionne à partir d'un socket connecté au serveur et à partir du @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 

Cela fonctionne magnifiquement, mais seulement avec un TCPSocket normal selon l'exemple. Je dois utiliser un lieu OpenSSL::SSL::SSLSocket, mais comme par the IO.select docs:

La meilleure façon d'utiliser IO.select invoque après des méthodes bloquantes telles que read_nonblock, write_nonblock, etc.

[...]

En particulier, la combinaison de méthodes non bloquantes et d'IO.select est préférée pour les objets de type IO tels que OpenSSL :: SSL :: SSLSocket.

Selon ce, je dois appeler IO.selectaprès méthodes bloquantes, alors que dans ma boucle, je l'utilise avant je peux choisir parmi 2 différents objets IO.

L'exemple donné sur la façon d'utiliser IO.select avec une prise SSL est:

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

Cependant, cela ne fonctionne que si IO.select est utilisé avec un seul objet IO.

Ma question est: comment puis-je faire fonctionner mon exemple précédent avec un socket SSL, étant donné que je dois sélectionner à la fois les objets @pipe_r et socket?

EDIT: J'ai essayé ce que @ steffen-ullrich suggéré, mais en vain. J'ai été en mesure de faire passer mes tests en utilisant ce qui suit:

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 

Cela ne semble pas si mal, mais toute entrée est la bienvenue.

Répondre

2

Je ne suis pas familier avec ruby ​​lui-même, mais avec les problèmes d'utilisation de select avec des sockets SSL. Les sockets SSL se comportent différemment des sockets TCP car les données SSL sont transférées dans des trames et non dans un flot de données, mais la sémantique des flux est néanmoins appliquée à l'interface de sockets.

Expliquons cela par un exemple, en utilisant d'abord une simple connexion TCP:

  • Le serveur envoie 1000 octets en une seule écriture.
  • Les données seront envoyées au client et placées dans le tampon socket du noyau. Ainsi, select retournera que les données sont disponibles.
  • L'application cliente lit d'abord 100 octets seulement.
  • Les 900 autres octets seront ensuite conservés dans le tampon socket du noyau. L'appel suivant de select retournera à nouveau que les données sont disponibles, puisque select regarde le tampon socket du noyau.

Avec SSL ce qui est différent:

  • Le serveur envoie 1000 octets en une seule écriture. La pile SSL va ensuite chiffrer ces 1000 octets dans une seule trame SSL et envoyer cette trame au client.
  • Ce cadre SSL sera remis au client et placé dans le tampon socket du noyau. Ainsi, select retournera que les données sont disponibles.
  • Maintenant, l'application cliente lit seulement 100 octets. Étant donné que le cadre SSL contient 1000 octets et qu'il a besoin de l'image complète pour déchiffrer les données, la pile SSL lit l'image complète à partir du socket, ne laissant rien dans le tampon socket du noyau. Il va ensuite décrypter la trame et renvoyer les 100 octets demandés à l'application. Le reste des 900 octets décryptés sera conservé dans la pile SSL dans l'espace utilisateur.
  • Étant donné que le tampon de socket du noyau est vide, l'appel suivant de select ne retournera pas les données disponibles. Ainsi, si l'application ne traite que de sélection, il n'y aura pas maintenant de données non lues, c'est-à-dire les 900 octets conservés dans la pile SSL.

Comment faire face à cette différence:

  • Une façon est de toujours essayer de lire au moins 32768 données, parce que c'est la taille maximale d'un cadre SSL. De cette façon, on peut être sûr qu'aucune donnée n'est encore conservée dans la pile SSL (la lecture SSL ne lira pas les limites de la trame SSL).
  • Une autre méthode consiste à vérifier la pile SSL pour les données déjà décryptées avant d'appeler select. Seulement si aucune donnée n'est conservée dans la pile SSL, la sélection doit être appelée. Pour vérifier ces "données en attente", utilisez the pending method.
  • Essayez de lire plus de données à partir du socket non-bloquant jusqu'à ce qu'il n'y ait plus de données disponibles. De cette façon, vous pouvez être sûr qu'aucune donnée n'est encore en attente dans la pile SSL. Mais notez que cela fera aussi des lectures sur le socket TCP sous-jacent et donc préférera les données sur le socket SSL par rapport aux données sur d'autres sockets (qui ne sont lues qu'après une sélection réussie). C'est la recommandation que vous avez citée, mais je préférerais les autres.
+0

Merci @ steffen-ullrich, ceci est une réponse très claire. Malheureusement, j'ai essayé d'utiliser la méthode en attente avant d'utiliser select et j'obtiens toujours qu'il y a 0 octets disponibles. J'ai également essayé nonblock_read (32768) et cela ne fait pas l'affaire non plus. J'essaierai un peu plus. – ostinelli

+0

Acceptera cette réponse car elle clarifie la bonne façon de résoudre ces problèmes. Merci encore! – ostinelli