Access to SSL context in faye-websocket+eventmachine connection - ruby

I would like to get a wire dump of a secure websocket connection where I am the client.
I am using the faye-websocket gem in ruby to connect to a secure websocket service. This works well. To understand a specific issue, I need to get a wire dump of the communication. I typically use wireshark for this (running on the same machine as the client). To decrypt the SSL connection, I need to extract the master key to pass it to wireshark. I know how to extract the master key if I have direct access to the socket, but I fail to get access to it when using the faye-websocket gem.
The code to run faye-websocket is pretty standard:
EM.run {
ws = Faye::WebSocket::Client.new('wss://...')
ws.on :open do |event|
p [:open]
### authentication
end
ws.on :message do |event|
p [:message, event.data]
### message - response loop here
end
ws.on :close do |event|
p [:close, event.code, event.reason]
ws = nil
end
}
Inspecting the content of ws, it has a #socket member, but I fail to receive it (get_instance_var returns nil).
For the record, once I have the SSLcontext, I would use the code from
https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/how-to-decrypt-ruby-ssl-communications-with-wireshark/
to extract the master key and pass it to wireshark:
ssl_socket.session.to_text.each_line do |line|
if match = line.match(/Session-ID\s*: (?<session_id>.*)/)
session_id = match[:session_id]
end
if match = line.match(/Master-Key\s*: (?<master_key>.*)/)
master_key = match[:master_key]
end
end
Does someone have a solution to get access to the underlying socket and the SSL context?

Related

web server in ruby and connection keep-alive

Web server example:
require 'rubygems'
require 'socket'
require 'thread'
class WebServer
LINE_TERMINATOR = "\r\n".freeze
def initialize(host, port)
#server = TCPServer.new(host, port)
end
def run
response_body = 'Hello World!'.freeze
response_headers = "HTTP/1.1 200 OK#{LINE_TERMINATOR}Connection: Keep-Alive#{LINE_TERMINATOR}Content-Length: #{response_body.bytesize}#{LINE_TERMINATOR}".freeze
loop do
Thread.new(#server.accept) do |socket|
puts "request #{socket}"
sleep 3
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
socket.write(response_headers)
socket.write(LINE_TERMINATOR)
socket.write(response_body)
# socket.close # if this line is uncommented then it's work.
end
end
end
end
WebServer.new('localhost', 8888).run
if update browser without waiting for the end of the cycle then the following queries are not processed
How can handle incomming request which are persistent socket ?
You need to:
Keep around the sockets you get from the #server.accept call. Store them in an array (socket_array).
Use the IO.select call on the array of sockets to get the set of sockets that can be read:
ready = IO.select(socket_array)
readable = ready[0]
readable.each do |socket|
# Read from socket here
# Do the rest of processing here
Don't close the socket after you have sent the data.
If you need more details leave a comment - I can write more of the code.

Ruby: Connect to remote WebSocket

I'm trying to connect to remote websocket using Celluloid and Websocket client based on celluloid (gem 'celluloid-websocket-client'). The main advantage of this client for me is that I can use callbacks in the form of class methods instead of blocks.
require 'celluloid/websocket/client'
class WSConnection
include Celluloid
def initialize(url)
#ws_client = Celluloid::WebSocket::Client.new url, Celluloid::Actor.current
end
# When WebSocket is opened, register callbacks
def on_open
puts "Websocket connection opened"
end
# When raw WebSocket message is received
def on_message(msg)
puts "Received message: #{msg}"
end
# When WebSocket is closed
def on_close(code, reason)
puts "WebSocket connection closed: #{code.inspect}, #{reason.inspect}"
end
end
m = WSConnection.new('wss://foo.bar')
while true; sleep; end
The expected output is
"Websocket connection opened"
However, I don't get any output at all. What could be the problem?
I am using
gem 'celluloid-websocket-client', '0.0.2'
rails 4.2.1
ruby 2.1.3
As you noticed in the comments, the gem had no SSL support. That is the trouble. To expound on the answer, here is a resolution, and also some next steps of what to expect for the future:
[ now ] Override methods in Celluloid::WebSocket::Client::Connection
This is an example injection to provide SSL support to the current gem. Mine is actually highly modified, but this shows you the basic solution:
def initialize(url, handler=nil)
#url = url
#handler = handler || Celluloid::Actor.current
#de If you want an auto-start:
start
end
def start
uri = URI.parse(#url)
port = uri.port || (uri.scheme == "ws" ? 80 : 443)
#socket.close rescue nil
#socket = Celluloid::IO::TCPSocket.new(uri.host, port)
#socket = Celluloid::IO::SSLSocket.new(#socket) if port == 443
#socket.connect
#client = ::WebSocket::Driver.client(self)
async.run
end
The above sends ripple effects through the other methods however, for example, #handler is used to hold the calling actor, which also has the emitter methods on it. Like I said, my version is very different from the stock gem because I got fed up with it and reworked mine. But then:
[ soon ] Use Reel::IO::Client and avoid near certain brain damage.
There are exciting things going on with WebSocket support, and a gem is coming to refactor both server and client implementations of websockets. No more monkeypatches required!
All websocket functionality is being extracted from Reel and being combined with a websocket-driver abstraction, as Reel::IO... in both ::Server and ::Client varieties.
Interestingly, this is prompted by Rails which is moving away from EventMachine to Celluloid::IO for websockets:
https://github.com/rails/actioncable/issues/16
https://github.com/celluloid/reel/issues/201
https://github.com/celluloid/reel-io/issues/2
A prealpha is online for preview: https://github.com/celluloid/reel-io

Faye WebSocket, reconnect to socket after close handler gets triggered

I have a super simple script that has pretty much what's on the Faye WebSocket GitHub page for handling closed connections:
ws = Faye::WebSocket::Client.new(url, nil, :headers => headers)
ws.on :open do |event|
p [:open]
# send ping command
# send test command
#ws.send({command: 'test'}.to_json)
end
ws.on :message do |event|
# here is the entry point for data coming from the server.
p JSON.parse(event.data)
end
ws.on :close do |event|
# connection has been closed callback.
p [:close, event.code, event.reason]
ws = nil
end
Once the client is idle for 2 hours, the server closes the connection. I can't seem to find a way to reconnect to the server once ws.on :close is triggered. Is there an easy way of going about this? I just want it to trigger ws.on :open after :close goes off.
Looking for the Faye Websocket Client implementation, there is a ping option which sends some data to the server periodically, which prevents the connection to go idle.
# Send ping data each minute
ws = Faye::WebSocket::Client.new(url, nil, headers: headers, ping: 60)
However, if you don't want to rely on the server behaviour, since it can finish the connection even if you are sending some data periodically, you can just put the client setup inside a method and start all over again if the server closes the connection.
def start_connection
ws = Faye::WebSocket::Client.new(url, nil, headers: headers, ping: 60)
ws.on :open do |event|
p [:open]
end
ws.on :message do |event|
# here is the entry point for data coming from the server.
p JSON.parse(event.data)
end
ws.on :close do |event|
# connection has been closed callback.
p [:close, event.code, event.reason]
# restart the connection
start_connection
end
end

is Ruby em-websocket blocking?

I'm writing a ruby program that has 2 threads. One that listens on an incoming UDP connection and another that broadcasts on a websocket from which browsers on the client side read.I'm using the em-websocket gem. However, My UDP listener thread never gets called and it looks like the code stays within the websocket initialization code. I'm guessing because em-websocket is blocking, but I haven't been able to find any info online that suggests that. Is it an error on my side? I'm kinda new to ruby so I'm not able to figure out what I'm doing wrong.
require 'json'
require 'em-websocket'
require 'socket'
socket=nil
text="default"
$x=0
EventMachine.run do
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
ws.onopen {
ws.send "Hello Client!"
socket=ws
$x=1
}
ws.onmessage { |msg| socket.send "Pong: #{msg}" }
ws.onclose { puts "WebSocket closed" }
end
end
def listen()
puts "listening..."
s = UDPSocket.new
s.bind(nil, 3000)
while 1<2 do
text, sender = s.recvfrom(1024)
puts text
if $x==1 then
socket.send text
end
end
end
t2=Thread.new{listen()}
t2.join
em-websocket is non-blocking, however UDPSocket#recv_from is. Might be better to just use EventMachine's open_datagram_socket instead.
Another thing to note: you should not expose socket as a "global" variable. Every time somebody connects the reference to the previously connected client will be lost. Maybe make some sort of repository for socket connections, or use an observer pattern to broadcast messages when something comes in. What I would do is have a dummy object act as an observer, and whenever a socket is connected/disconnect you register/unregister from the observer:
require 'observer'
class Dummy
include Observable
def receive_data data
changed true
notify_observers data
end
end
# ... later on ...
$broadcaster = Dummy.new
class UDPHandler < EventMachine::Connection
def receive_data data
$broadcaster.receive_data data
end
end
EventMachine.run do
EM.open_datagram_socket "0.0.0.0", 3000, UDPHandler
EM::WebSocket.start :host => "0.0.0.0", :port => 8080 do |ws|
ws.onopen do
$broadcaster.add_observer ws
end
ws.onclose do
$broadcaster.delete_observer ws
end
# ...
end
end
The whole point of EventMachine is to abstract away from the basic socket and threading structure, and handle all the asynchronous bits internally. It's best not to mix the classical libraries like UDPSocket or Thread with EventMachine stuff.

How to disconnect redis client in websocket eventmachine

I'm trying to build a websocket server where each client establish its own redis connections used for publish and subscribe.
When the redis server is running I can see the two new connections being established when a client connects to the websocket server and I can also publish data to the client, but when the client drops the connection to the websocket server I also want to disconnect from Redis . How can I do this?
Maybe I'm doing it wrong, but this is my code.
#require 'redis'
require 'em-websocket'
require 'em-hiredis'
require 'json'
CLIENTS = Hash.new
class PubSub
def initialize(client)
#socket = client.ws
# These clients can only be used for pub sub commands
#publisher = EM::Hiredis.connect #Later I will like to disconnect this
#subscriber = EM::Hiredis.connect #Later I will like to disconnect this
client.connections << #publisher << #subscriber
end
def subscribe(channel)
#channel = channel
#subscriber.subscribe(channel)
#subscriber.on(:message) { |chan, message|
#socket.send message
}
end
def publish(channel,msg)
#publisher.publish(channel, msg).errback { |e|
puts [:publisherror, e]
}
end
def unsubscribe()
#subscriber.unsubscribe(#channel)
end
end
class Client
attr_accessor :connections, :ws
def initialize(ws)
#connections = []
#ws = ws
end
end
EventMachine.run do
# Creates a websocket listener
EventMachine::WebSocket.start(:host => '0.0.0.0', :port => 8081) do |ws|
ws.onopen do
# I instantiated above
puts 'CLient connected. Creating socket'
#client = Client.new(ws)
CLIENTS[ws] = #client
end
ws.onclose do
# Upon the close of the connection I remove it from my list of running sockets
puts 'Client disconnected. Closing socket'
#client.connections.each do |con|
#do something to disconnect from redis
end
CLIENTS.delete ws
end
ws.onmessage { |msg|
puts "Received message: #{msg}"
result = JSON.parse(msg)
if result.has_key? 'channel'
ps = PubSub.new(#client)
ps.subscribe(result['channel'])
elsif result.has_key? 'publish'
ps = PubSub.new(ws)
ps.publish(result['publish']['channel'],result['publish']['msg']);
end
}
end
end
This version of em-hiredis supports close connection: https://github.com/whatupdave/em-hiredis
Here is how I would (and did many times) this:
instead of always opening and closing connections for each client you can keep 1 connection open per Thread/Fiber dependeing on what you are basing your concurrency on, that way if you are using a poll of Thread/Fibers once each one of them have its connections they will keep it and reuse them.
I did not worked much with websocket until now (I was waiting for a standard implementation) but I am sure you can apply that thinking to it too.
You can also do what rails/activerecord: keeo a pool of redis connection, each time you need to use a connection you request one, use it and realease it, it could look like this:
def handle_request(request)
#redis_pool.get_connection do |c|
# [...]
end
end
before yielding the block a connection is taken from the available ones and after it the connection is marked as free.
This was added to em-hiredis: https://github.com/mloughran/em-hiredis/pull/6

Resources