Home

Awesome

Sopipe

Sopipe aims to be socat with middlewares. It can be used for secured* and accelerated data transfer with arbitrarily chained encryption, compression, authentication, and error correction (WIP).

* Sopipe has not undergone any security review. The encryption-related components should be used at own risk.

Installation

Download the latest release at the release page and drop it anywhere. Sopipe is a single static linked binary that does not read or generate any file unless explicitly scripted.

Usage

Cli

Sopipe expects only one argument: the script. The behaviour of sopipe is controlled solely by the script. No commandline options are provided and no environment variables are read. Example:

sopipe 'stdin => exec("tee", "record.txt") !! drop => stdout'

If the script is long and saved in a file, you can use some shell tricks:

sopipe "$(< script.txt)"

Run sopipe with empty argument will print the version and enabled features.

Script

Sopipe uses an extreamly simple DSL to describe the pipeline. Take a look at the examples to get a sense of it.

A "function call" defines a node, and => operators are used to connect the nodes. The arguments of a node can have three forms: key-value pair, key-only, or value-only. If no arguments are needed, the parentheses can be omitted too. !! operators can used to composite two nodes, such that the one on the left is used for forwarding and the other for backwarding.

:= operator binds a node to a name. This is necessary for feeding multiple inputs to a node. The RHS of the := operation can be a pipe =>, in which case the last node in the pipe is bound to the name.

$a.b => foo() connects $a and foo() with a specific name b. Some components use names to recognize the role of each output. Named outputs can also be specified inline. For example, foo(.b => bar()) => baz() will connect a foo component with two outputs, one is bar() with the name b, and the other is baz(). Anonymous outputs can also be inlined with only a dot. For example, stdio => tee(. => tcp("localhost:2000"), . => tcp("localhost:2001")).

All whitespaces " ", "\t", "\n" are treated equivalently. So the previous example can be written as:

stdio => tee(
    . => tcp("localhost:2000"),
    . => tcp("localhost:2001")
)

Blockly Builder

Why write scripts when you can just drag and drop some colorful blocks? Try the experimental builder at https://blog.ylxdzsw.com/sopipe/.

screenshot of the builder

Modules

Endpoints

Proxying

Authentication

Encryption

Compression

Scripting / Debugging

Performance

A micro benchmark about the local tcp port forwarding throughput using iperf3.

iperf3 -s
iperf3 -c localhost -p 2000
socat -b65536 TCP-LISTEN:2000,fork TCP:127.0.0.1:5201
sopipe 'tcp(2000) => tcp("127.0.0.1", 5201)'
Throughput
Direct32.5 Gbits/sec
Socat16.1 Gbits/sec
Sopipe12.4 Gbits/sec

Addtional benchmarks:

sopipe 'tcp(2000) => xor("a") => xor("a") => tcp("localhost:5201")'
sopipe 'tcp(2000) => aead_encode("a") => aead_decode("a") => tcp("localhost:5201")'
sopipe 'tcp(2000) => deflate => inflate => tcp("localhost:5201")'
Throughput
XOR677 Mbits/sec
AEAD3.35 Gbits/sec
MINIZ812 Mbits/sec

Testing HTTP performance using https://www.speedtest.net/ in a 4G mobile network.

$ sopipe 'tcp(2000) => socks5_server => tcp'
$ sopipe 'tcp(1080) => tcp("localhost:2000")'
$ sopipe 'tcp(2000) => auth_server("a") => aead_decode("x") => socks5_server => tcp'
$ sopipe 'tcp(1080) => aead_encode("x") => auth_client("a") => tcp("localhost:2000")'
PingDownloadUpload
direct189ms20.02 Mbps6.17 Mbps
socks5194ms20.29 Mbps7.31 Mbps
socks5 + auth + aead187ms13.89 Mbps8.81 Mbps

Building Instructions

Building static linked binary with all features (the command used in CI):

RUSTFLAGS="-C target-feature=+crt-static" cargo build --release --target x86_64-unknown-linux-gnu

Building dynamic linked binary with all features and optimized for your CPU architechture:

RUSTFLAGS="-C target-cpu=native" cargo build --release

Sopipe supports building with arbitrary selection of components. For example, a tiny build that only includes tcp and socks5:

cargo build --no-default-features --features tcp,socks5

Gallery

Port forwarding

Forward local TCP port 2000 to 22.

$ sopipe 'tcp(2000) => tcp("localhost:22")'

Make a relay that forwards TCP port 2000 to a server, but only for authenticated users. The traffic is encrypted from the user to the server and the relay cannot inspect.

(user)$ sopipe 'tcp(2000) => aead_encode("encrypt pass") => auth_client("auth pass") => tcp("relay", 2000)'
(relay)$ sopipe 'tcp(2000) => auth_server("auth pass") => tcp("server", 2000)'
(server)$ sopipe 'tcp(2000) => aead_decode("encrypt pass") => tcp("localhost:22")'

Proxying

Make a socks5 server but the traffic is compressed.

(client)$ sopipe 'tcp(8080) => deflate(level=4) => tcp("proxy:8080")'
(server)$ sopipe 'tcp(8080) => inflate => socks5_server => tcp'

Debugging

Forward UDP packets from port 2000 to port 2001, but randomly drop 20% packets.

$ sopipe 'udp(2000) => throttle(drop_rate=20) => udp("localhost:2001")'

Forward TCP traffic but limits to 100KB/s.

$ sopipe 'tcp(2000) => throttle(size=102400, interval=1000) => tcp("localhost:2001")'

Other

Forward local TCP port 2000 to 2001, record messages in a file, and drop all messages sent back.

$ sopipe 'tcp(2000) => exec("tee", "record.txt") !! drop => tcp("localhost:2001")'