Commit 03e1b8a6 authored by Andreas Kempe's avatar Andreas Kempe
Browse files

Add local client connection support

The server can listen for local connections and pass them on. No
compression is done at this point.
parents
BSD 3-Clause License
Copyright (c) 2019, Andreas Kempe
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use "net"
interface ProxyClient
"""
This interface is used by ClientNotifier for relaying data.
The actor implementing this interface is expected to relay the
data from the client to the server.
When the local connection has been accepted, the ClientNotifier
should call accepted().
When the remote connection is established to the server, the
ClientNotifier should call connected().
"""
// Notify relay that the remote connection has been established.
be connected(conn: TCPConnection tag)
// Notify the relay that the local connection has been accepted.
be accepted(conn: TCPConnection tag)
// Kill the relay. Generally done if a connection closes.
be kill(caller: TCPConnection tag)
// Relay data to the other end-point.
be relay(from: TCPConnection tag, data: Array[U8 val] val)
class ClientAcceptor is TCPListenNotify
"""
Accepts client connections and initiates a connection to the
server for relaying.
"""
let env: Env
let auth: AmbientAuth
let remote_address: String
let remote_port: String
new create(env': Env, auth': AmbientAuth,
remote_address': String,
remote_port': String) =>
env = env'
auth = auth'
remote_address = remote_address'
remote_port = remote_port'
fun ref connected(listen: TCPListener ref): TCPConnectionNotify iso^ =>
let client = Client(env.out, auth)
/*
* At this point, the local connection is established. Before
* returning the notifier, we initialise a connection to the
* remote server and create another notifier for that remote
* connection.
*/
TCPConnection(auth, recover ClientNotifier(env.out, client) end,
remote_address, remote_port)
recover ClientNotifier(env.out, client) end
fun ref not_listening(listen: TCPListener ref) =>
None
class ClientNotifier is TCPConnectionNotify
"""
This class passes data and events from the TCPConnection to the
ProxyClient actor that handles relaying data between connections.
"""
let out: OutStream
let client: ProxyClient tag
new create(out': OutStream, client': ProxyClient tag) =>
out = out'
client = client'
fun ref accepted(conn: TCPConnection ref) =>
client.accepted(conn)
fun ref connected(conn: TCPConnection ref) =>
client.connected(conn)
fun ref closed(conn: TCPConnection ref) =>
client.kill(conn)
fun ref received(conn: TCPConnection ref,
data: Array[U8] iso,
times: USize) : Bool =>
client.relay(conn, consume data)
true
fun ref connect_failed(conn: TCPConnection ref) =>
client.kill(conn)
actor Client is ProxyClient
"""
This class simply shuffles data from the locally connected client
to the remote server. The connections are established by the TCP
handling logic and the active connections are passed to this actor
via connected() for the remote connection and accepted() for the
local connection. Data arriving via the relay() callback will be
passed between the two connections.
"""
let auth: AmbientAuth
let out: OutStream
var local: (TCPConnection tag | None)
var remote: (TCPConnection tag | None)
var local_buffer: Array[U8]
var remote_buffer: Array[U8]
new create(out': OutStream, auth': AmbientAuth) =>
out = out'
auth = auth'
local = None
remote = None
local_buffer = Array[U8]
remote_buffer = Array[U8]
fun get_opposite(conn: TCPConnection tag) : TCPConnection tag? =>
if local is conn then
remote as TCPConnection
else
local as TCPConnection
end
be accepted(conn: TCPConnection tag) =>
local = conn
if remote_buffer.size() > 0 then
send_buffer(conn, remote_buffer)
end
be connected(conn: TCPConnection tag) =>
remote = conn
if local_buffer.size() > 0 then
send_buffer(conn, local_buffer)
end
fun send_buffer(conn: TCPConnection tag, buffer: Array[U8]) =>
let bs = buffer.size()
let data : Array[U8] iso = recover Array[U8](bs) end
for d in buffer.values() do
data.push(d)
end
buffer.truncate(0)
conn.write(consume data)
be kill(caller: TCPConnection tag) =>
try
// If this fails, the other connection was never opened.
// Ignore the error.
get_opposite(caller)?.dispose()
end
be relay(from: TCPConnection tag, data: Array[U8 val] val) =>
try
get_opposite(from)?.write(data)
else
/*
* If we couldn't get the opposite connection, that means
* one side still hasn't connected. We queue the data and
* send it when the connection is made.
*/
if from is local then
local_buffer.concat(data.values())
elseif from is remote then
remote_buffer.concat(data.values())
else
out.print("ERROR: Received data from connection that was neither local nor remote")
end
end
use "cli"
use "logger"
use "net"
actor Main
new create(env: Env) =>
try
var client: Bool
let client_spec =
CommandSpec.leaf("client",
"""
Run as a client and connect to a socker server
available at address:port. The client will open a tcp
port at localhost:local_port that acts as a SOCKS5
proxy compressing and relaying data to the socker
server.
"""
, [
OptionSpec.string("address", "Listen address"
where short' = 'a', default' = "localhost")
OptionSpec.string("port", "Listen port"
where short' = 'p', default' = "8081")
OptionSpec.string("remote_address", "Server address"
where short' = 'r', default' = "localhost")
OptionSpec.string("remote_port", "Server port"
where short' = 's', default' = "8080")
], [])?
let server_spec =
CommandSpec.leaf("server",
"""
Run in server mode and listen for incoming connections
at addr:port. Will act as a SOCKS5 proxy for incoming
traffic from a socker client.
"""
, [
OptionSpec.string("address", "Listen address"
where short' = 'a', default' = "localhost")
OptionSpec.string("port", "Listen port"
where short' = 'p', default' = "8081")
], [])?
let cs =
CommandSpec.parent("socker", "A compressing SOCKS5 proxy",
[], [server_spec; client_spec])?
.> add_help()?
var cmd =
match CommandParser(cs).parse(env.args, env.vars)
| let c: Command => c
| let ch: CommandHelp =>
ch.print_help(env.out)
error
| let se: SyntaxError =>
env.out.print(se.string())
error
end
if cmd.fullname() == "socker/client" then
client = true
else
client = false
end
let address = cmd.option("address").string()
let port = cmd.option("port").string()
var acceptor =
if client then
let remote_address = cmd.option("remote_address").string()
let remote_port = cmd.option("remote_port").string()
env.out.print("Running as client")
recover iso
ClientAcceptor(env,
env.root as AmbientAuth,
remote_address,
remote_port)
end
else
env.out.print("Running as server")
recover iso ProxyAcceptor(env) end
end
TCPListener(env.root as AmbientAuth,
consume acceptor,
address, port)
else
env.out.print("General Error")
env.exitcode(1)
return
end
use "net"
class ProxyAcceptor is TCPListenNotify
let env: Env
new create(env': Env) =>
env = env'
fun ref connected(listen: TCPListener ref): TCPConnectionNotify iso^ =>
recover Proxy(env) end
fun ref not_listening(listen: TCPListener ref) =>
None
class Proxy is TCPConnectionNotify
let env: Env
new create(env': Env) =>
env = env'
fun ref received(conn: TCPConnection ref,
data: Array[U8] iso,
times: USize) : Bool =>
conn.write(String.from_array(consume data))
true
fun ref connect_failed(conn: TCPConnection ref) =>
None
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment