Dávid Halász
dhalasz@redhat.com
Twitter: halaszdavid
GitHub: skateman
Browser
VM
Browser
Hypervisor
VM
VNC
Browser
Proxy
Hypervisor
VM
VNC
WS
Browser
Proxy
Hypervisor
VM
VNC
WS
Browser
Proxy
Hypervisor
VM
Translate & Transmit
A
B
A->B
B->A
Source: pngarts.com
An abstraction around the networking stack in UNIX systems, they behave like file descriptors so you can read/write them similarly to files.
require "socket"
sock = TCPSocket.new("www.manageiq.org", 80)
sock.write("GET / HTTP/1.1\r\n");
sock.write("Host: www.manageiq.org\r\n")
sock.write("\r\n")
puts sock.read(314)
"HTTP/1.1 200 OK
Server: nginx
Vary: Accept-Encoding
Content-Type: text/html
Date: Mon, 28 Jan 2019 13:18:14 GMT
Accept-Ranges: bytes
Connection: keep-alive
Last-Modified: Fri, 18 Jan 2019 13:20:10 GMT
X-UA-Comp"
Reading is just reversed: the OS puts data in the buffer and the read(n) waits until the buffer has the requested amount of data.
IO.select(reads, writes, errors, timeout)
A
B
A->B
B->A
require "socket"
sock_a = TCPSocket.new(...)
sock_b = TCPSocket.new(...)
pairing = {
sock_a => sock_b,
sock_b => sock_a
}
loop do
reads, writes, _ = IO.select(pairing.keys, pairing.keys, [], 1)
reads.each do |r|
w = pairing[r]
# Skip the transmission if w is not writable
next unless writes.include?(w)
# Read from r, translate it and write it to w
w.write_nonblock(translate(r.read_nonblock(4096)))
end
end
require "socket"
sock_a = TCPSocket.new(...)
sock_b = TCPSocket.new(...)
pairing = {
sock_a => sock_b,
sock_b => sock_a
}
loop do
reads, writes, _ = IO.select(pairing.keys, pairing.keys, [], 1)
reads.each do |r|
w = pairing[r]
# Skip the transmission if w is not writable
next unless writes.include?(w)
# Read from r, translate it and write it to w
w.write_nonblock(translate(r.read_nonblock(4096)))
end
end
Threads with blocking IO
EventMachine
Celluloid
Async
!Ruby
Quit my job
def select(timeout)
read, write, _ = IO.select(@to_read, @to_write, [], timeout)
@to_read -= read
@to_write -= write
end
def each_ready
ready_pairs.each do |rd, wr|
yield(rd, wr)
@to_read.push(rd)
@to_write.push(wr)
end
end
# ...
loop do
select(1)
each_ready do |rd, wr|
data = rd.read_nonblock(4096)
wr.write_nonblock(translate(data))
end
end
def select(timeout)
read, write, _ = IO.select(@to_read, @to_write, [], timeout)
@to_read -= read
@to_write -= write
end
def each_ready
ready_pairs.each do |rd, wr|
yield(rd, wr)
@to_read.push(rd)
@to_write.push(wr)
end
end
# ...
loop do
select(1)
each_ready do |rd, wr|
data = rd.read_nonblock(4096)
wr.write_nonblock(translate(data))
end
end
https://github.com/skateman/surro-gate
Using HTTP for bidirectional data transfer instead of the request-response model
Webserver
Browser
Webserver
Browser
GET /websocket HTTP/1.1 Host: localhost Connection: Upgrade Upgrade: WebSocket
Webserver
Browser
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade
Webserver
Browser
WebSocket frames
in both directions
Source: pngarts.com
require 'rack'
# app can be anything that responds to #call
app = Proc.new do |env|
[
'200', # status
{'Content-Type' => 'text/html'}, # headers
['A barebones rack app.'] # 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::WEBrick.run app
Kindly asking the server to give us the underlying socket of an HTTP connection
class WebsocketServer
def initialize
@proxy = Proxy.new
end
def call(env)
return not_found unless http_upgrade?
sock_ws = env['rack.hijack'].call
address, port = magic(env)
sock_tcp = TCP.new(address, port)
@proxy.push(sock_ws, sock_tcp)
[-1, {}, []]
end
end
VNC
WS
Browser
Proxy
Hypervisor
VM
GET /websocket HTTP/1.1
Host: localhost
Connection: Upgrade
Upgrade: WebSocket
GET /websocket HTTP/1.1
Host: localhost
Connection: Upgrade
Upgrade: PurrSocket
VNC
Purr
Server Proxy
Hypervisor
VM
Client Proxy
VNC Client
VNC
Webserver
Proxy
Hypervisor
Browser
VM 123
Webserver
Proxy
Hypervisor
Browser
Talk to /purr?id=123
VM 123
Webserver
Proxy
Hypervisor
Browser
Talk to /purr?id=123
Plugin
/purr?id=123
VM 123
Webserver
Proxy
Hypervisor
Browser
Plugin
listening on
localhost:3333
VM 123
Webserver
Proxy
Hypervisor
Browser
Plugin
Displays
localhost:3333
VM 123
Webserver
VNC Client
VNC to localhost:3333
Proxy
Hypervisor
VM 123
Browser
Plugin
Webserver
Plugin
VNC Client
Proxy
Hypervisor
Browser
GET /purr?id=123 (UPGRADE)
VNC
VM 123
Webserver
Plugin
VNC Client
Proxy
Hypervisor
Browser
GET /purr?id=123 (UPGRADE)
VNC
VM 123
Webserver
Plugin
VNC Client
Proxy
Hypervisor
Browser
GET /purr?id=123 (UPGRADE)
VNC
VM 123
Webserver
Plugin
VNC Client
Proxy
Hypervisor
Browser
HTTP 101
VNC
VM 123
Webserver
Plugin
VNC Client
Proxy
Hypervisor
Browser
Purr
VNC
VM 123
Webserver
Plugin
VNC Client
Proxy
Hypervisor
Browser
Purr
VNC
VNC
VM 123
Webserver
Plugin
VNC Client
Proxy
Hypervisor
Browser
Purr
VNC
VNC
VM 123
Webserver
Plugin
VNC Client
Proxy
Hypervisor
Browser
Purr
VNC
VNC
VM 123
# purr.ru
require 'purr'
use Rack::Logger
app = Purr.server do |env|
# Just implement your own magic method
host, port = magic(env)
[host, port]
end
run app
# puma purr.ru
Native remote connections
SSH
VNC
etc
VPN
<Your idea here>
Dávid Halász
dhalasz@redhat.com
Twitter: halaszdavid
GitHub: skateman
If you can read this, you're awesome
github.com/skateman/purr
github.com/skateman/surro-gate
www.manageiq.org
www.skateman.eu
Made with FontAwesome icons