Skip to content

Speak protocol v1 (SPEC.md draft 2) through awp tunnel - #1

Open
kylemclaren wants to merge 1 commit into
mainfrom
draft-2
Open

kylemclaren wants to merge 1 commit into
mainfrom
draft-2

Conversation

@kylemclaren

Copy link
Copy Markdown
Contributor

Moves the Python SDK to draft 2 of the protocol. CI builds awp at the head of agentwireprotocol/awp#30 (stacked on #28). Once those merge and a release is tagged, CI moves to the tag.

How the tunnel works

Python has no userspace WireGuard, so a Peer runs awp tunnel, the reference implementation's tunnel, as a helper process for its whole life (SPEC §18.2).

  • The helper holds the Peer's key and listens on the carriers listen() asks for.
  • It opens streams on request over a control socket.
  • It forwards the streams peers open to a Unix socket of the Peer's.
  • It refuses a hello that names a key other than the tunnel's before the Peer sees it.

The Peer speaks the protocol itself, from framing on.

API changes

  • listen(carrier) takes tailcat, udp:HOST:PORT, ws:HOST:PORT[=URL], cloudflare or unix:/path. It returns the awp1 address, which lists every carrier.
  • address and rotate_psk() are new.
  • connect(address) accepts only awp1 addresses. An explicit connect now also lifts a bye.
  • Peer(awp=...) names the binary; the default is $AWP_BIN or awp on PATH. tailcat= is gone, and so are the plaintext TCP and Unix transports.

Protocol changes

  • Hello is v1, with no nonce and no auth step.
  • Hello carries the Peer's address without its pre-shared key.
  • Hello presents the held grants that concern the peer on the other end of the tunnel. The Peer matches them by X25519 key, which the helper passes on.
  • err refused closes the connection.

Bugs fixed along the way

  • connect() after a bye waited forever on a parked dial loop.
  • _dial_waiters was class-level, so it was shared across every Peer in a process.

Testing

37 tests, all over real tunnels:

  • The driver tests (30) now run with a raw client that has its own awp tunnel. They cover a hello naming another key, version 2, bad frames, oversized lines, dedup, resume replay, missed pongs, Go-style encodings, grant delegation presented in hello, and kill -9 on either side.
  • New: introductions with the grant presented in hello and honored by the audience.
  • New: rotation shuts out a stranger holding the old address, admits them with the new one, and keeps a peer already met.
  • The conformance suite passes in both modes.
  • The pure-Python Ed25519 run passes.

Every connection is a WireGuard tunnel between the peers' keys. Python
has no WireGuard, so a Peer runs the reference implementation's
`awp tunnel` as a helper for its whole life: the helper holds the key,
listens on carriers, dials on request over a control socket, forwards
the streams peers open, and refuses a hello naming another key.

- listen(carrier) takes tailcat, udp, ws, cloudflare or unix and returns
  the awp1 address; `address` and rotate_psk() are new; connect() takes
  addresses only. The tcp/unix plaintext transports and the tailcat CLI
  are gone; Peer(awp=...) names the binary.
- The handshake is hello only: v1, no nonce, no auth. hello carries our
  address without its pre-shared key and the grants that concern the
  peer on the other end of the tunnel (matched by its X25519 key, which
  the helper passes on). err refused closes.
- An explicit connect() now lifts a bye and dials even with no queued
  work; dial bookkeeping is per Peer rather than per class.
- Tests: 37, all through real tunnels, including a raw client with its
  own helper for the protocol's edges, grant presentation in hello,
  rotation, and conformance in both modes. CI builds awp at the engine
  PR's head.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant