A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations.

Overview

onetun

A cross-platform, user-space WireGuard port-forwarder that requires no system network configurations.

Build status Latest Release

Usage

onetun opens a TCP port on your local system, from which traffic is forwarded to a TCP port on a peer in your WireGuard network. It requires no changes to your operating system's network interfaces: you don't need to have root access, or install any WireGuard tool on your local system for it to work.

The only prerequisite is to register a peer IP and public key on the remote WireGuard endpoint; those are necessary for the WireGuard endpoint to trust the onetun peer and for packets to be routed.

./onetun 
    
    
                                    \
    --endpoint-addr 
     
                       \
    --endpoint-public-key 
      
         \
    --private-key 
       
         \ --source-peer-ip 
        
          \ --keep-alive 
         
           \ --log 
          
         
        
       
      
     
    
   

Note: you can use environment variables for all of these flags. Use onetun --help for details.

Example

Suppose your WireGuard endpoint has the following configuration, and is accessible from 140.30.3.182:51820:

# /etc/wireguard/wg0.conf

[Interface]
PrivateKey = ********************************************
ListenPort = 51820
Address = 192.168.4.1

# A friendly peer that hosts the TCP service we want to reach
[Peer]
PublicKey = AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AllowedIPs = 192.168.4.2/32

# Peer assigned to onetun
[Peer]
PublicKey = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
AllowedIPs = 192.168.4.3/32

We want to access a web server on the friendly peer (192.168.4.2) on port 8080. We can use onetun to open a local port, say 127.0.0.1:8080, that will tunnel through WireGuard to reach the peer web server:

./onetun 127.0.0.1:8080 192.168.4.2:8080                                  \
    --endpoint-addr 140.30.3.182:51820                                    \
    --endpoint-public-key 'PUB_****************************************'  \
    --private-key 'PRIV_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB'          \
    --source-peer-ip 192.168.4.3                                          \
    --keep-alive 10

You'll then see this log:

INFO  onetun > Tunnelling [127.0.0.1:8080]->[192.168.4.2:8080] (via [140.30.3.182:51820] as peer 192.168.4.3)

Which means you can now access the port locally!

$ curl 127.0.0.1:8080
Hello world!

Download

Normally I would publish onetun to crates.io. However, it depends on some features in smoltcp and boringtun that haven't been published yet, so I'm forced to use their Git repos as dependencies for now.

In the meantime, you can download the binary for Windows, macOS (Intel), and Linux (amd64) from the Releases page.

You can also run onetun using Docker:

docker run --rm --init --name onetun -p 8080:8080 aramperes/onetun \
       0.0.0.0:8080 192.168.4.2:8080 [...options...]

You can also build onetun locally, using Rust:

$ git clone https://github.com/aramperes/onetun && cd onetun
$ cargo build --release
$ ./target/release/onetun

Architecture

onetun uses tokio, the async runtime, to listen for new TCP connections on the given port.

When a client connects to the local TCP port, it uses smoltcp to create a "virtual interface", with a "virtual client" and a "virtual server" for the connection. These "virtual" components are the crux of how onetun works. They essentially replace the host's TCP/IP stack with smoltcp's, which fully runs inside onetun. An ephemeral "virtual port" is also assigned to the connection, in order to route packets back to the right connection.

When the real client opens the connection, the virtual client socket opens a TCP connection to the virtual server. The virtual interface (implemented by smoltcp) in turn crafts the SYN segment and wraps it in an IP packet. Because of how the virtual client and server are configured, the IP packet is crafted with a source address being the configured source-peer-ip (192.168.4.3 in the example above), and the destination address is the remote peer's (192.168.4.2).

By doing this, we let smoltcp handle the crafting of the IP packets, and the handling of the client's TCP states. Instead of actually sending those packets to the virtual server, we can intercept them in the virtual interface and encrypt the packets using boringtun, and send them to the WireGuard endpoint's UDP port.

Once the WireGuard endpoint receives an encrypted IP packet, it decrypts it using its private key and reads the IP packet. It reads the destination address, re-encrypts the IP packet using the matching peer's public key, and sends it off to the peer's UDP endpoint.

The remote peer receives the encrypted IP and decrypts it. It can then read the inner payload (the TCP segment), forward it to the server's port, which handles the TCP segment. The server responds with SYN-ACK, which goes back through the peer's local WireGuard interface, gets encrypted, forwarded to the WireGuard endpoint, and then finally back to onetun's UDP port.

When onetun receives an encrypted packet from the WireGuard endpoint, it decrypts it using boringtun. The resulting IP packet is broadcasted to all virtual interfaces running inside onetun; once the corresponding interface is matched, the IP packet is read and unpacked, and the virtual client's TCP state is updated.

Whenever data is sent by the real client, it is simply "sent" by the virtual client, which kicks off the whole IP encapsulation and WireGuard encryption again. When data is sent by the real server, it ends up routed in the virtual interface, which allows the virtual client to read it. When the virtual client reads data, it simply pushes the data back to the real client.

This work is all made possible by smoltcp and boringtun, so special thanks to the developers of those libraries.

License

MIT. See LICENSE for details.

Comments
  • SSL_ERROR_BAD_MAC_READ When forwarding SSL port

    SSL_ERROR_BAD_MAC_READ When forwarding SSL port

    I'm getting a strange problem when I attempt to connect to my forwarded GitLab instance using Firefox/Chromium: Some requests are failing with SSL_ERROR_BAD_MAC_READ. It also only seems to happen on background requests so I assume it only happens when many requests happen in parallel as the background resources are fetched in parallel.


    Here's a snapshot of the network log when I try to load GitLab:

    image

    Notice how the first request for the HTML file executes just fine. Once the browser parses the HTML file however, it fires off a bunch of requests in parallel to grab the script files and the images. A couple of these requests succeed however the rest fail with: SSL_ERROR_BAD_MAC_READ.

    Also, here's the onetun log (with --log trace) corresponding to the above requests: onetun.log.


    The issue doesn't happen when I connect directly to the GitLab instance over WireGuard.

    bug 
    opened by null-dev 15
  • Add C FFI bindings

    Add C FFI bindings

    This adds bindings for use in other languages for use of the onetun library. This allows people to use this library in languages such as C, C++ and Swift (which is what the SideStore team is using). All I've added is a simple way to convert the C datatypes to their Rust counterpart and then it feeds that data to the normal Rust library.

    opened by jkcoxson 8
  • Convert to a library

    Convert to a library

    Hello, thank you for your work on this project. It is very helpful.

    I would love to incorporate this into other projects, specifically on iOS where it must be incorporated into a Swift application through a library. Onetun's uses can be expanded for a variety of use-cases as a library.

    The SideStore team and I are willing to put in the development time to convert the core parts of onetun into a callable library, while adding a binary option to maintain onetun's current use-case. If this is something that you would be willing to merge, I'll create a PR once we have something to show. Otherwise we will maintain our own fork.

    opened by jkcoxson 8
  • Load-balancing destination address

    Load-balancing destination address

    Add support for passing multiple destination addresses for one source address. Should round-robbin the destination address to use for each incoming connection on the source address.

    As suggested by @DanielBodnar

    opened by aramperes 5
  • Multiple port-forwarding at once

    Multiple port-forwarding at once

    Would be nice to allow multiple combinations of peer port & client port at once.

    The CLI would have to be tweaked to support this. Possibly similar to SSH tunnelling.

    opened by aramperes 5
  • Add ListenPort support (source port for WireGuard socket)

    Add ListenPort support (source port for WireGuard socket)

    This program lacks support for ListenPort option of WireGuard, which, in case of client, binds outgoing UDP socket to the supplied source port. This is necessary to bypass NAT. Would appreciate this option.

    opened by ValdikSS 3
  • Add WireGuard endpoint setup guide

    Add WireGuard endpoint setup guide

    I had to setup iptables (e.g., sudo iptables -t nat -A POSTROUTING -s 192.168.133.0/24 -j MASQUERADE) on the Wireguard endpoint to make port forwarding work. Is this the right thing to do? If so, it would be helpful to add server setup guide to the documentation.

    opened by shpark 2
  • Private Key shouldn't be passed in via CLI

    Private Key shouldn't be passed in via CLI

    First off, thanks for providing this project! It's a very cool idea and I want to see about integrating it into some smaller projects at work.

    One thing I noticed though is that --private-key is a CLI option, but passing secrets in via the CLI can be a security risk. Other users (or malicious processes) see the process along with all arguments used to start that process when doing things like ps. Environment variables are better, but still present a (admittedly smaller) risk as all exec*e (i.e. execve) calls can have their environment variables read by watching for that syscall. This can be seen with strace or other similar tools.

    The best option is to have it passed in either via stdin, a file (encrypted if possible, but if not only readable by said user), or prompt the user to type it in (which in this case isn't really that feasible).

    opened by kbknapp 2
  • Receiving non-matching ACKs from server

    Receiving non-matching ACKs from server

    To debug: this happened with blasting concurrent connections on the same port:

    INFO  onetun                    > [46668] Incoming connection from 127.0.0.1:34380
     TRACE smoltcp::socket::tcp      > #0:192.168.4.2:7000: state=CLOSED=>LISTEN
     TRACE smoltcp::socket::tcp      > #0:192.168.4.16:46668:192.168.4.2:7000: state=CLOSED=>SYN-SENT
     TRACE smoltcp::socket::set      > [0]: adding
     TRACE smoltcp::socket::set      > [1]: adding
     TRACE smoltcp::socket::tcp      > #1:192.168.4.16:46668:192.168.4.2:7000: outgoing segment will send data or flags
     TRACE smoltcp::socket::tcp      > #1:192.168.4.16:46668:192.168.4.2:7000: sending SYN
     TRACE smoltcp::socket::tcp      > rtte: sampling at seq=SeqNumber(43)
     TRACE onetun                    > [46668] Virtual interface polled some packets to be processed
     TRACE onetun::wg                > Sending IP packet: IPv4 src=192.168.4.16 dst=192.168.4.2 proto=TCP
    \ TCP src=46668 dst=7000 syn seq=42 win=65535 len=0 mss=1380
     DEBUG onetun::wg                > Sent 84 bytes to WireGuard endpoint (encrypted IP packet)
     DEBUG onetun::wg                > WireGuard endpoint sent IP packet of 40 bytes
     TRACE onetun::wg                > Received IP packet: IPv4 src=192.168.4.2 dst=192.168.4.16 proto=TCP
    \ TCP src=7000 dst=46668 seq=1769813973 ack=83519 win=750 len=0
     TRACE onetun::wg                > Dispatched received IP packet to virtual port 46668
     DEBUG smoltcp::socket::tcp      > #1:192.168.4.16:46668:192.168.4.2:7000: expecting a SYN|ACK
     DEBUG smoltcp::iface::interface > cannot process ingress packet: dropped by socket
     DEBUG smoltcp::socket::tcp      > #1:192.168.4.16:46668:192.168.4.2:7000: retransmitting at t+0.701s
     TRACE smoltcp::socket::tcp      > rtte: abort sampling due to retransmit
     TRACE smoltcp::socket::tcp      > #1:192.168.4.16:46668:192.168.4.2:7000: outgoing segment will send data or flags
     TRACE smoltcp::socket::tcp      > #1:192.168.4.16:46668:192.168.4.2:7000: sending SYN
     TRACE onetun                    > [46668] Virtual interface polled some packets to be processed
     TRACE onetun::wg                > Sending IP packet: IPv4 src=192.168.4.16 dst=192.168.4.2 proto=TCP
    \ TCP src=46668 dst=7000 syn seq=42 win=65535 len=0 mss=1380
     DEBUG onetun::wg                > Sent 84 bytes to WireGuard endpoint (encrypted IP packet)
     DEBUG onetun::wg                > WireGuard endpoint sent IP packet of 40 bytes
     TRACE onetun::wg                > Received IP packet: IPv4 src=192.168.4.2 dst=192.168.4.16 proto=TCP
    \ TCP src=7000 dst=46668 seq=1769813973 ack=83519 win=750 len=0
     TRACE onetun::wg                > Dispatched received IP packet to virtual port 46668
     DEBUG smoltcp::socket::tcp      > #1:192.168.4.16:46668:192.168.4.2:7000: expecting a SYN|ACK
     DEBUG smoltcp::iface::interface > cannot process ingress packet: dropped by socket
     DEBUG smoltcp::socket::tcp      > #1:192.168.4.16:46668:192.168.4.2:7000: retransmitting at t+0.700s
     TRACE smoltcp::socket::tcp      > #1:192.168.4.16:46668:192.168.4.2:7000: outgoing segment will send data or flags
     TRACE smoltcp::socket::tcp      > #1:192.168.4.16:46668:192.168.4.2:7000: sending SYN
     TRACE onetun                    > [46668] Virtual interface polled some packets to be processed
     TRACE onetun::wg                > Sending IP packet: IPv4 src=192.168.4.16 dst=192.168.4.2 proto=TCP
    \ TCP src=46668 dst=7000 syn seq=42 win=65535 len=0 mss=1380
     DEBUG onetun::wg                > Sent 84 bytes to WireGuard endpoint (encrypted IP packet)
     DEBUG onetun::wg                > WireGuard endpoint sent IP packet of 40 bytes
     TRACE onetun::wg                > Received IP packet: IPv4 src=192.168.4.2 dst=192.168.4.16 proto=TCP
    \ TCP src=7000 dst=46668 seq=1769813973 ack=83519 win=750 len=0
     TRACE onetun::wg                > Dispatched received IP packet to virtual port 46668
     DEBUG smoltcp::socket::tcp      > #1:192.168.4.16:46668:192.168.4.2:7000: expecting a SYN|ACK
     DEBUG smoltcp::iface::interface > cannot process ingress packet: dropped by socket
     DEBUG smoltcp::socket::tcp      > #1:192.168.4.16:46668:192.168.4.2:7000: retransmitting at t+0.700s
     TRACE smoltcp::socket::tcp      > rtte: too many retransmissions, increasing: rtt=600 dev=100 rto=1000
     TRACE smoltcp::socket::tcp      > #1:192.168.4.16:46668:192.168.4.2:7000: outgoing segment will send data or flags
     TRACE smoltcp::socket::tcp      > #1:192.168.4.16:46668:192.168.4.2:7000: sending SYN
    

    So for some reason the server is responding to SYN with a random ACK (that has the right ports and all) and the sequence numbers don't match (seq=42 -> ack=83519). Logically that would mean the port 46668 might already be in use by another connection and the packets are being sent to the wrong interface but that shouldn't happen (as far as I can tell, no other connection on that virtual port 46668 had been opened until then).

    Likely this: https://github.com/smoltcp-rs/smoltcp/issues/366

    • fix was reverted/replaced in https://github.com/smoltcp-rs/smoltcp/commit/eae480525b3728f46e085973c437f723c29c45d1#diff-b7870cf088f948003fbe8fbcc97c6add63ee3507b3d67e199bf11491613a497f
    • and https://github.com/smoltcp-rs/smoltcp/commit/14971876bde4388e321e7b41e21ecd4a2b3deb89#diff-b7870cf088f948003fbe8fbcc97c6add63ee3507b3d67e199bf11491613a497f
    bug 
    opened by aramperes 2
  • Graceful shutdown of virtual client doesn't flush remaing packets to real client

    Graceful shutdown of virtual client doesn't flush remaing packets to real client

    In some cases, graceful shutdown by the virtual client socket will abort sending data back to the real client, which causes incomplete TCP exchanges with the real client. It only seems to happen on random occasions and with large HTTP requests; but I will need to investigate some more.

    bug 
    opened by aramperes 2
  • Handle WireGuardError::ConnectionExpired

    Handle WireGuardError::ConnectionExpired

    In wg.rs when an WireGuardError::ConnectionExpired is encountered, the error is just being logged. https://github.com/aramperes/onetun/blob/85195d8aba2cad41b648d693044b364ab906bcf0/src/wg.rs#L123

    It would be much better if this error was handled and the connection re-established.

    opened by TitanNano 1
  • Update to boringtun v0.5.x

    Update to boringtun v0.5.x

    The upgrade from v0.4 to v0.5 has some breaking changes, mainly with the crypto implementation having moved to a separate crate (https://github.com/cloudflare/boringtun/pull/265).

    Full diff: https://github.com/cloudflare/boringtun/compare/v0.4.0...boringtun-0.5.1

    enhancement 
    opened by aramperes 0
  • An iperf session never finishes

    An iperf session never finishes

    An iperf session never finishes with 0.3.4

    onetun run as ./onetun-linux-amd64 --endpoint-addr endpoint-host:endpoint-port --endpoint-public-key endpoint-pubkey --private-key-file wg0-private.key --source-peer-ip 192.168.177.6 --keep-alive 25 8888:192.168.177.1:6697

    A nc conversation works ok (server cmdline = nc -4lknv 192.168.177.1 6697, client cmdline nc 127.0.0.1 8888) so the tunnel is working.

    iperf server: iperf3 -s -p 6697 iperf client: iperf3 -c 127.0.0.1 -p 8888

    The logs

     2022-09-28T11:31:53.050Z INFO  onetun::tunnel > Tunneling TCP [127.0.0.1:8888]->[192.168.177.1:6697] (via [endpoint-host:endpoint-port] as peer 192.168.177.6)                                                                                                                                        
     2022-09-28T11:32:42.095Z INFO  onetun::tunnel::tcp > [[28378:TCP]] Incoming connection from 127.0.0.1:40152                                                                                                                                                                                
     2022-09-28T11:32:42.405Z INFO  onetun::tunnel::tcp > [[22848:TCP]] Incoming connection from 127.0.0.1:40154                                                                                                                                                                                
     2022-09-28T11:32:42.950Z ERROR onetun::events      > Failed to read event bus from endpoint #2                                                                                                                                                                                             
     2022-09-28T11:32:42.950Z ERROR onetun::events      > Failed to read event bus from endpoint #3                                                                                                                                                                                             
     2022-09-28T11:32:42.950Z ERROR onetun::events      > Failed to read event bus from endpoint #5                                                                                                                                                                                             
     2022-09-28T11:32:42.950Z ERROR onetun::events      > Failed to read event bus from endpoint #0                                                                                                                                                                                             
     2022-09-28T11:32:42.950Z ERROR onetun::events      > Failed to read event bus from endpoint #3                                                                                                                                                                                             
     2022-09-28T11:32:44.823Z ERROR onetun::events      > Failed to read event bus from endpoint #3                                                                                                                                                                                             
    ...
    

    and then the endpoint #3 message keeps repeating forever.

    iperf client reports a big sender bitrate (like if data were being piped to an infinite buffer) but 0.00 bits/sec from receiver. The iperf server always reports 0 bytes transferred.

    Debug logs are in https://pastebin.com/XzGjpaDE.

    Not sure if this is related to #29. It's definitely related to #9.

    opened by mmoya 1
  • HTTP Proxy Server

    HTTP Proxy Server

    Adds an option to create an HTTP proxy server which forwards all requests through the WireGuard tunnel. onetun [...] --http-proxy 127.0.0.1:8080 and then socat - PROXY:127.0.0.1:1.1.1.1:80,proxyport=8080 or curl http://1.1.1.1 -x http://127.0.0.1:8080 -p -v.

    Currently, the proxy only supports IP addresses, although it would be possible to add DNS resolution. It also only supports the CONNECT method, but it would be possible to add support for other methods.

    The use case for this feature is that you can connect to any IP address you want after starting onetun--you don't have to launch new onetun instances or know all the destination IP addresses upfront.

    opened by XMB5 0
  • Library: hot-adding/removing port-forwards

    Library: hot-adding/removing port-forwards

    Add and remove port-forwarding routes at runtime when using onetun as a library embedded in another application.

    At the moment, I'm envisioning this being an event that can be passed in the Bus, and be picked up by the various endpoints to create the necessary sockets or shutdown active connections.

    I will wait for #6 to be completed, as it may affect the feasibility of this feature.

    Originally suggested by @jkcoxson in #35

    opened by aramperes 0
  • Improve bandwidth

    Improve bandwidth

    I tested some sftp tunneling on some big files and found that each connection is capped at around 2MB/s. Naturally this depends on the latency and bandwidth with the WireGuard router, but with the official kernel implementation I get around 20MB/s for the same endpoint.

    $ sftp [email protected]
    # 20MB/s
    
    $ sftp -P 2222 [email protected]
    # onetun: 2.4MB/s
    

    Parity with the kernel is not a goal since userspace will be a bit slower, but I would like to aim for something like ~50% instead of ~10%.

    enhancement 
    opened by aramperes 4
Releases(v0.3.4)
Owner
Aram Peres
Full-stack web, Rust, Python, Java, etc.
Aram Peres
A private network system that uses WireGuard under the hood.

innernet A private network system that uses WireGuard under the hood. See the announcement blog post for a longer-winded explanation. innernet is simi

Tonari, Inc 4.1k Nov 25, 2022
Fast User-Space TCP/UDP Stack

Catnip Catnip is a TCP/IP stack that focuses on being an embeddable, low-latency solution for user-space networking. Building and Running 1. Clone Thi

Demikernel 79 Sep 9, 2022
A Prometheus exporter for WireGuard

wireguard_exporter An asynchronous Prometheus exporter for wireguard wireguard_exporter runs wg show [..] and scrapes the output to build Prometheus m

Kevin K. 14 Sep 9, 2022
A WireGuard UWP VPN plugin.

WireGuard UWP A Universal Windows Platform (UWP) VPN Plug-in for WireGuard® written in Rust. Windows provides a plug-in based model for adding 3rd-par

Luqman Aden 89 Nov 2, 2022
WireGuard front for mitmproxy (WIP)

mitmguard work-in-progress WireGuard front for mitmproxy Architecture DONE multi-threaded / asynchronous WireGuard server using tokio: one worker thre

Fabio Valentini 14 Nov 17, 2022
WireGuard frontend for mitmproxy (WIP)

mitmproxy_wireguard Transparently proxy any device that can be configured as a WireGuard client! Work-In-Progress. Architecture DONE multi-threaded /

Fabio Valentini 18 Nov 29, 2022
Rust utility crate for parsing, encoding and generating x25519 keys used by WireGuard

WireGuard Keys This is a utility crate for parsing, encoding and generating x25519 keys that are used by WireGuard. It exports custom types that can b

Fractal Networks 3 Aug 9, 2022
WireGuard gateway with SNI for portable connectivity.

Gateway This is a daemon that controls gateway servers. Gateway servers are servers that fulfil three major purposes: facilitating connectivity betwee

Fractal Networks 5 Aug 9, 2022
An app which reads data from a serial port and serves it on a TCP port.

serial-to-tcp An app which reads data from a serial port and serves it on a TCP port. How to use Clone this repo and build the app as outlined below (

Mr. E 3 Oct 21, 2022
Docker containers on a synthetic network. Run applications in a context that lets you manipulate their network conditions.

Synthetic Network Docker containers on a synthetic network. Run applications in a context that lets you manipulate their network conditions. Dependenc

Daily 56 Nov 11, 2022
A tcp port forwarding system like ngrok.

Pruxy A tcp port forwarding system like ngrok. Todo http request handler agent <-> server connection agent How to use Generate cert files mkdir ssl_ce

null 1 Jan 24, 2022
Cross-platform, low level networking using the Rust programming language.

libpnet Linux ∪ OS X Build Status: Windows Build Status: Discussion and support: #libpnet on freenode / #rust-networking on irc.mozilla.org / #rust on

null 1.7k Nov 24, 2022
A simple cross-platform remote file management tool to upload and download files over HTTP/S

A simple cross-platform remote file management tool to upload and download files over HTTP/S

sexnine 11 Oct 31, 2022
⏱ Cross-platform Prometheus style process metrics collector of metrics crate

⏱ metrics-process This crate provides Prometheus style process metrics collector of metrics crate for Linux, macOS, and Windows. Collector code is man

Alisue 11 Oct 25, 2022
A high performance TCP SYN port scanner.

Armada A High-Performance TCP SYN scanner What is Armada? Armada is a high performance TCP SYN scanner. This is equivalent to the type of scanning tha

resync 257 Nov 29, 2022
Simple utility to ping a TCP port.

TcpPing Simple utility to ping a TCP port. Example > tcpping 1.1.1.1 53 -b en0 -i 1 -t 4 Connected to 1.1.1.1:53 in 21 ms Connected to 1.1.1.1:53 in 3

null 12 Nov 13, 2022
A fast, stable, efficient, and lightweight intranet penetration, port forwarding tool supports multiple connections, cascading proxy, and transmission encryption

A fast, stable, efficient, and lightweight intranet penetration, port forwarding tool supports multiple connections, cascading proxy, and transmission encryption

editso 1.2k Nov 30, 2022
Rust port of stb_image_write.h

Overview stb_image_write_rust is Rust port of stb_image_write.h, which is library to save images in BMP, JPG, PNG and TGA formats. The porting was don

null 1 Jan 6, 2022
A simple, single threaded and minimalistic port checker

A simple, single threaded and minimalistic port checker

null 1 Feb 12, 2022