Good luck, have fun!
Dávid Halász
WrocLove RB '19
@halaszdavid
Like remote desktop sessions, but in a HTML5 <canvas>
Browser
Hypervisor
VM
Proxy
WS
VNC
WS
VNC
require "socket"
sock = TCPSocket.new("wrocloverb.com", 80)
sock.write("GET / HTTP/1.1\r\n");
sock.write("Host: wrocloverb.com\r\n")
sock.write("\r\n")
puts sock.read(358)
sock.close
"HTTP/1.1 301 Moved Permanently
Cache-Control: public, max-age=0, must-revalidate
Content-Length: 39
Content-Type: text/plain
Date: Thu, 07 Mar 2019 20:54:19 GMT
Location: https://wrocloverb.com/
Age: 72524
Connection: keep-alive
Server: Netlify
Redirecting to https://wrocloverb.com/"
HEADERS
PAYLOAD
HTTP
# sock_a <- websocket
# sock_b <- vncsocket
Thread.new do
loop do
ws_data = sock_a.read(1024)
vnc_data = ws_to_vnc(ws_data)
sock_b.write(vnc_data)
end
end
loop do
vnc_data = sock_b.read(1024)
ws_data = vnc_to_ws(vnc_data)
sock_a.write(ws_data)
end
class Reader
def initialize
@sockets = []
Thread.new { event_loop }
end
def register(socket)
@sockets.push(socket)
end
private
def event_loop
loop do
# Wait for events
to_read, _, _ = IO.select(@sockets, [], [], 1)
# Handle the events
to_read.each do |socket|
puts socket.read_nonblock(4096)
end
end
end
end
pairing = {
sock_a => [sock_b, method(:ws_to_vnc)],
sock_b => [sock_a, method(:vnc_to_ws)]
}
loop do
rds, wrs, _ = IO.select(pairing.keys, pairing.keys, [], 1)
# Iterate through all the readable sockets
rds.each do |read|
# Retrieve the right endpoint and translation method
write, translate = pairing[read]
# Don't do anything if write is not writable - OOPSIE
next unless wrs.include?(write)
# Read, translate and write
data = read.read_nonblock(4096)
translated = translate.call(data)
write.write_nonblock(translated)
end
end
😭
😭 😭
👍
🤩 🤩
# sock_a <- websocket
# sock_b <- vncsocket
fibers = []
fibers << AutoFiber.new do
loop do
ws_data = sock_a.read(1024)
vnc_data = ws_to_vnc(ws_data)
sock_b.write(vnc_data)
end
end
fibers << AutoFiber.new do
loop do
vnc_data = sock_b.read_nonblock(1024)
ws_data = vnc_to_ws(vnc_data)
sock_a.write_nonblock(ws_data)
end
end
# Not sure about the syntax, sorry
loop { fibers.each(&:call) }
✨✨ MAGIC ✨✨
pairing = {
sock_a => [sock_b, method(:ws_to_vnc)],
sock_b => [sock_a, method(:vnc_to_ws)]
}
@to_read = pairing.keys
@to_write = pairing.keys
loop do
rds, wrs, _ = IO.select(@to_read, @to_write, [], 1)
@to_read -= read # bounce out
@to_write -= write
# Iterate through all the readable sockets
rds.each do |read|
# Retrieve the right endpoint and translation method
write, translate = pairing[read]
# Don't do anything if write is not writable
next unless wrs.include?(write)
# Read, translate and write
data = read.read_nonblock(4096)
translated = translate.call(data)
write.write_nonblock(translated)
@to_read.push(read) # bounce back in
@to_write.push(write)
end
end
void epoll_register(int *epoll, VALUE socket) {
struct epoll_event ev;
ev.data.u64 = (uint64_t) socket;
ev.events = EPOLLONESHOT | EPOLLIN | EPOLLOUT;
epoll_ctl(*epoll, EPOLL_CTL_ADD, SOCK_PTR(socket), &ev);
}
void select(int *epoll, int timeout) {
int i = 0;
struct epoll_event events[256];
int numevents = epoll_wait(epoll, events, 256, timeout);
for (int i = 0; i < numevents; i++) {
VALUE socket = (VALUE) events[i].data.u64;
// set readiness of the socket
// in an ruby-readable structure
// that can be iterated with each
}
}
For any other platform it can fall back to IO.select
https://github.com/skateman/surro-gate
Using HTTP for bidirectional data transfer instead of the classic request-response model after a regular handshake
Browser
Webserver
GET /websocket HTTP/1.1 Host: localhost Connection: Upgrade Upgrade: WebSocket
Browser
Webserver
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade
Browser
Webserver
WebSocket frames
defined by RFC 6455
Source: pngarts.com
require 'rack'
# app can be anything that responds to #call
app = lambda do |env|
# Here you do some stuff
[
'200', # status
{'Content-Type' => 'text/html'}, # headers
['Hello world!'] # body
]
end
# Incoming HTTP requests will run app.call
# with a single hash as an argument that
# contains all the parsed data from the request
Rack::Handler::Puma.run app
require 'rack'
# app can be anything that responds to #call
app = lambda do |env|
# Here you do some stuff
[
'200', # status
{'Content-Type' => 'text/html'}, # headers
['Hello world!'] # body
]
end
# Incoming HTTP requests will run app.call
# with a single hash as an argument that
# contains all the parsed data from the request
Rack::Handler::Puma.run app
@proxy = Proxy.new # Runs in background
app = lambda do |env|
return [404, {}, []] unless http_upgrade?
sock_ws = env['rack.hijack'].call
address, port = magic(env)
sock_vnc = TCP.new(address, port)
@proxy.push(sock_ws, sock_vnc)
[-1, {}, []] # Dummy return to make Rack happy
end
Rack::Handler::Puma.run app
Browser
Hypervisor
VM
Proxy
WS
VNC
GET /websocket HTTP/1.1 Host: localhost Connection: Upgrade Upgrade: WebSocket
🐱 🐱 🐱 🐱 🐱
Hypervisor
VM
Server Proxy
VNC
Client Proxy
VNC
VNC Client
Purr
Browser
Webserver
Proxy
Hypervisor
VM
Browser
Webserver
Proxy
Hypervisor
VM
I want to purr like a cat
Browser
Webserver
Proxy
Hypervisor
VM
Go for it, talk to purr://miq
Browser
Webserver
Proxy
Hypervisor
VM
Plugin
purr://miq
Browser
Webserver
Proxy
Hypervisor
VM
Plugin
I'm listening on localhost:1234
Browser
Webserver
Proxy
Hypervisor
VM
Plugin
I'm listening on localhost:1234
Browser
Webserver
Proxy
VNC client
Plugin
Hypervisor
VM
Connecting to
localhost:1234
Browser
Webserver
Proxy
VNC client
Plugin
Hypervisor
VM
VNC
Browser
Webserver
Proxy
VNC client
Plugin
Hypervisor
VM
VNC
HTTP w/upgrade to Purr
Browser
Webserver
Proxy
VNC client
Plugin
Hypervisor
VM
VNC
HTTP w/upgrade to Purr
Browser
Webserver
Proxy
VNC client
Plugin
Hypervisor
VM
VNC
HTTP 101
Browser
Webserver
Proxy
VNC client
Plugin
Hypervisor
VM
VNC
Purr
Browser
Webserver
Proxy
VNC client
Plugin
Hypervisor
VM
VNC
Purr
VNC
Browser
Webserver
Proxy
VNC client
Plugin
Hypervisor
VM
VNC
Purr
VNC
require 'purr'
app = Purr.server do |env|
# Just implement your own magic method
# that returns with a host and a port
host, port = magic(env)
[host, port]
end
Rack::Handler::Puma.run app
This presentation is running in a browser, that is running in a container which is being accessed through VNC. The container is running in a VM, without any exposed ports. Therefore, the only way to access the container's VNC server from my machine is via a HTTP server purring on the VM.
Dávid Halász
dhalasz@redhat.com
Twitter: halaszdavid
Github: skateman
I have some stickers of George and Purr
github.com/skateman/purr
github.com/skateman/surro-gate
www.manageiq.org
www.skateman.eu
Made with FontAwesome icons