Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

piko-client

A Zig port of Piko's Go client SDK (piko/client) — connect an outbound-only tunnel to a Piko server and accept or forward TCP connections through it. Built on zio for async I/O.

Layout

Path Responsibility
src/yamux/ Standalone yamux multiplexing implementation (many logical streams over one connection). Transport-agnostic — used here over a WebSocket, but has no Piko-specific code. See src/yamux/README.md.
src/piko/ws_conn.zig WebSocket(-over-TLS) transport (WsConn), exposed as a std.Io.Reader/Writer pair so it satisfies the same shape zio.net.Stream does — hands straight to yamux.ZioPeer.init. Built on a vendored, patched copy of karlseguin/websocket.zig (src/piko/vendor/ws_client/, see its own README for why it's vendored rather than a plain dependency).
src/piko/backoff.zig Exponential backoff with jitter, direct port of piko/pkg/backoff.
src/piko/upstream.zig Upstream: dials the Piko server and returns an established yamux session, retrying with backoff on transient failures (DNS/TCP/TLS/handshake failures, and retryable HTTP statuses). Port of piko/client/upstream.go.
src/piko/listener.zig Listener: the net.Listener-shaped accept surface for incoming tunneled connections, reconnecting automatically if the underlying connection drops. Port of piko/client/listener.go.
src/piko/forwarder.zig Forwarder: accepts from a Listener and forwards each connection to a local TCP address via a bidirectional copy. Port of piko/client/forwarder.go.
src/piko/dialer.zig Dialer: opens a single, non-multiplexed connection to a Piko endpoint (no retry). Port of piko/client/dialer.go.

src/piko.zig re-exports all of the above; src/yamux.zig re-exports the yamux package. Both are re-exported again from src/root.zig as piko and yamux.

Usage

Register a listener for an endpoint and forward every accepted connection to a local address (mirrors Go's Upstream.Listen + Forwarder):

const piko_client = @import("piko_client");
const piko = piko_client.piko;

var runtime = try zio.Runtime.init(allocator, .{});
defer runtime.deinit();
const io = runtime.io();

const upstream: piko.Upstream = .{
    .url = "https://piko.example.com",
    .token = "...",
};

var listener = try piko.Listener.init(allocator, io, &upstream, "my-endpoint");
defer listener.deinit();

var forwarder = piko.Forwarder.init(allocator, io, &listener, "127.0.0.1:8080");
try forwarder.run(); // blocks; forwarder.close() from another task to stop

Or accept streams directly (no local forwarding) using the same yamux.StreamHandle (std.Io.Reader/Writer) the yamux package exposes:

var handle = try listener.accept(.{});
defer handle.deinit(allocator);
try handle.writer.writeAll("hello");
try handle.writer.flush();

For a single non-multiplexed outbound connection instead (Go's Dialer):

const dialer: piko.Dialer = .{ .url = "https://piko.example.com" };
var conn = try dialer.dial(allocator, io, "my-endpoint");
defer conn.deinit();

src/main.zig is a small manual smoke-test driver (zig build run -- <endpoint-id> [local-addr]) that connects to a real Piko server and forwards accepted streams, logging every step — useful for exercising this against a live deployment, since piko.Forwarder itself deliberately logs nothing (see "Deliberately out of scope" below).

Status

The yamux layer is complete and tested independently (see src/yamux/README.md), including wire interop against a separate Go peer binary. The Piko client port covers Upstream/Listener/Forwarder/Dialer with reconnect-with-backoff, TLS (std.crypto.tls, no external TLS dependency), and bidirectional forwarding — verified against real WebSocket test servers and, for the connect/listen path, against a real Piko deployment.

Not yet done: end-to-end verification of a full forwarded connection (dial → tunnel → local target → response) against a real Piko Go server; so far only the connect/listen half has been exercised live.

Deliberately out of scope (add if a real need shows up, not speculatively):

  • No internal logging in piko.Forwarder/piko.Listener — errors propagate through return values, same convention as the yamux layer; log at the call site instead (see src/main.zig for an example wrapper that does).
  • websocket/httpz in build.zig.zon are unused by this port (the WebSocket transport is the vendored, patched copy under src/piko/vendor/ws_client/ instead) — flagged for removal, not removed unilaterally in case they're wanted for something else.

Testing

zig build test

Covers the frame codec and session/stream state machines (pure unit tests), yamux's async behavior over real loopback TCP and Go-interop sockets, the WebSocket transport (handshake, framing, TLS) over real loopback sockets, and Upstream/Listener/Forwarder/Dialer against hand-rolled WebSocket test servers.

About

A Zig port of Piko's Go client SDK (piko/client) — connect an outbound-only tunnel to a Piko server and accept or forward TCP connections through it. Built on zio for async I/O.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages