Skip to content

ylxdzsw/sopipe

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

76 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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 one and only one argument: the input script. The behaviour of sopipe is controlled solely by the script. No commandline options are provided and no environment variables are read.

Shell tips: use single quote for the script so you don't need to escape the quotes and !! operations. For 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 builder at https://blog.ylxdzsw.com/sopipe/. Note that the builder is unfinished and not all scripts can be built using the builder (e.g. currently it does not support multiple inputs or outputs).

screenshot of the builder

Modules

Currently the following components are avaliable. More to come™.

Endpoints

  • tcp: Listen to a tcp port or send to a (remote) tcp port. If the stream is directed (e.g. produced by socks5_server), the output tcp node don't need arguments about destination.
  • udp: Similar to tcp but for UDP.
  • stdio: Read or write to STDIN / STDOUT.

Proxying

Authentication

  • auth: A simple authentication components based on preshared keys and MAC. It has two methods: time (default) and challenge. In the time method, the client sends the current timestamp and MAC for verification. In the challenge method, the server actively sends a nounce and the client replies with MAC.

Encryption

  • xor: Not really encrypt, but xor the stream with a fixed key.
  • aead: Various AEAD cyphers using ring.

Compression

Scripting / Debugging

  • exec: Spawn an external process and connect to its STDIN / STDOUT. This allows integrating virtually anything with substantial performance penalty.
  • throttle: Limit the flow rate like packets per second, byte per second, or randomly drop packets.
  • tee: Broadcast to all outputs.
  • balance: Choose one output for each stream ("anycast").
  • drop: Discard whatever received.
  • echo: Reply whatever received.

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
Direct 32.5 Gbits/sec
Socat 16.1 Gbits/sec
Sopipe 12.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
XOR 677 Mbits/sec
AEAD 3.35 Gbits/sec
MINIZ 812 Mbits/sec

Testing HTTP performance via https://www.speedtest.net/

$ 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")'
Ping Download Upload
direct 189ms 20.02 Mbps 6.17 Mbps
socks5 194ms 20.29 Mbps 7.31 Mbps
socks5 + auth + aead 187ms 13.89 Mbps 8.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")'