172 lines
3.6 KiB
Ruby
Executable File
172 lines
3.6 KiB
Ruby
Executable File
#!/usr/bin/env ruby
|
|
|
|
# A WebSocket to TCP socket proxy with support for "wss://" encryption.
|
|
# Copyright 2011 Joel Martin
|
|
# Licensed under LGPL version 3 (see docs/LICENSE.LGPL-3)
|
|
|
|
require 'socket'
|
|
$: << "other"
|
|
$: << "../other"
|
|
require 'websocket'
|
|
require 'optparse'
|
|
|
|
# Proxy traffic to and from a WebSockets client to a normal TCP
|
|
# socket server target. All traffic to/from the client is base64
|
|
# encoded/decoded to allow binary data to be sent/received to/from
|
|
# the target.
|
|
class WebSocketProxy < WebSocketServer
|
|
|
|
@@Traffic_legend = "
|
|
Traffic Legend:
|
|
} - Client receive
|
|
}. - Client receive partial
|
|
{ - Target receive
|
|
|
|
> - Target send
|
|
>. - Target send partial
|
|
< - Client send
|
|
<. - Client send partial
|
|
"
|
|
|
|
|
|
def initialize(opts)
|
|
vmsg "in WebSocketProxy.initialize"
|
|
|
|
super(opts)
|
|
|
|
@target_host = opts["target_host"]
|
|
@target_port = opts["target_port"]
|
|
end
|
|
|
|
# Echo back whatever is received
|
|
def new_client(client)
|
|
|
|
msg "connecting to: %s:%s" % [@target_host, @target_port]
|
|
tsock = TCPSocket.open(@target_host, @target_port)
|
|
|
|
if @verbose then puts @@Traffic_legend end
|
|
|
|
begin
|
|
do_proxy(client, tsock)
|
|
rescue
|
|
tsock.shutdown(Socket::SHUT_RDWR)
|
|
tsock.close
|
|
raise
|
|
end
|
|
end
|
|
|
|
# Proxy client WebSocket to normal target socket.
|
|
def do_proxy(client, target)
|
|
cqueue = []
|
|
c_pend = 0
|
|
tqueue = []
|
|
rlist = [client, target]
|
|
|
|
loop do
|
|
wlist = []
|
|
|
|
if tqueue.length > 0
|
|
wlist << target
|
|
end
|
|
if cqueue.length > 0 || c_pend > 0
|
|
wlist << client
|
|
end
|
|
|
|
ins, outs, excepts = IO.select(rlist, wlist, nil, 0.001)
|
|
if excepts && excepts.length > 0
|
|
raise Exception, "Socket exception"
|
|
end
|
|
|
|
# Send queued client data to the target
|
|
if outs && outs.include?(target)
|
|
dat = tqueue.shift
|
|
sent = target.send(dat, 0)
|
|
if sent == dat.length
|
|
traffic ">"
|
|
else
|
|
tqueue.unshift(dat[sent...dat.length])
|
|
traffic ".>"
|
|
end
|
|
end
|
|
|
|
# Receive target data and queue for the client
|
|
if ins && ins.include?(target)
|
|
buf = target.recv(@@Buffer_size)
|
|
if buf.length == 0:
|
|
raise EClose, "Target closed"
|
|
end
|
|
|
|
cqueue << buf
|
|
traffic "{"
|
|
end
|
|
|
|
# Encode and send queued data to the client
|
|
if outs && outs.include?(client)
|
|
c_pend = send_frames(cqueue)
|
|
cqueue = []
|
|
end
|
|
|
|
# Receive client data, decode it, and send it back
|
|
if ins && ins.include?(client)
|
|
frames, closed = recv_frames
|
|
tqueue += frames
|
|
|
|
if closed
|
|
send_close
|
|
raise EClose, closed
|
|
end
|
|
end
|
|
|
|
end # loop
|
|
end
|
|
end
|
|
|
|
# Parse parameters
|
|
opts = {}
|
|
parser = OptionParser.new do |o|
|
|
o.on('--verbose', '-v') { |b| opts['verbose'] = b }
|
|
o.parse!
|
|
end
|
|
|
|
if ARGV.length < 2:
|
|
puts "Too few arguments"
|
|
exit 2
|
|
end
|
|
|
|
# Parse host:port and convert ports to numbers
|
|
if ARGV[0].count(":") > 0
|
|
opts['listen_host'], _, opts['listen_port'] = ARGV[0].rpartition(':')
|
|
else
|
|
opts['listen_host'], opts['listen_port'] = nil, ARGV[0]
|
|
end
|
|
|
|
begin
|
|
opts['listen_port'] = opts['listen_port'].to_i
|
|
rescue
|
|
puts "Error parsing listen port"
|
|
exit 2
|
|
end
|
|
|
|
if ARGV[1].count(":") > 0
|
|
opts['target_host'], _, opts['target_port'] = ARGV[1].rpartition(':')
|
|
else
|
|
puts "Error parsing target"
|
|
exit 2
|
|
end
|
|
|
|
begin
|
|
opts['target_port'] = opts['target_port'].to_i
|
|
rescue
|
|
puts "Error parsing target port"
|
|
exit 2
|
|
end
|
|
|
|
puts "Starting server on #{opts['listen_host']}:#{opts['listen_port']}"
|
|
server = WebSocketProxy.new(opts)
|
|
server.start(100)
|
|
server.join
|
|
|
|
puts "Server has been terminated"
|
|
|
|
# vim: sw=2
|