Skip to content

feat(core): exit-node forwarding over NyUnicast - #122

Open
Evsio0n wants to merge 1 commit into
encodeous:mainfrom
Evsio0n:feat/exit-node-v2
Open

feat(core): exit-node forwarding over NyUnicast#122
Evsio0n wants to merge 1 commit into
encodeous:mainfrom
Evsio0n:feat/exit-node-v2

Conversation

@Evsio0n

@Evsio0n Evsio0n commented May 27, 2026

Copy link
Copy Markdown

Adds explicit exit-node forwarding as a new node-to-node packet type on top of polyamide TC, reworking the earlier exit-only encapsulation in response to maintainer review on #121:

  • binary NodeIdBin (uint16) replaces variable-length node id strings in the on-the-wire header. The mapping is rebuilt deterministically from CentralCfg on every apply so all peers agree.

  • the dataplane filter reads an atomic ExitFilterSnapshot pointer instead of touching n.LocalCfg / n.CentralCfg / RouterState. The snapshot is rebuilt on the dispatch goroutine on config apply and on every route mutation, and bundles the per-node next-hop table so the filter performs a single atomic.Load per packet.

  • the wire format is documented in core/nylon_unicast.go as a generic NyUnicast envelope (fixed 6-byte header, subtype/hop_limit/dst/src), leaving room for future node-targeted subtypes (state query, config push) without re-inventing the encoding.

LocalCfg gains AdvertiseExitNode and ExitNode; the corresponding NylonOptions overrides are wired through entrypoint.go. ComputeSysRouteTable adds 0.0.0.0/0 when ExitNode is set and excludes the default-local ranges (loopback, link-local, multicast) so they never tunnel.

Tests:

  • state: NodeIdBin map determinism, zero reserved.
  • core: header round-trip, source-ownership rule, sys route table.
  • e2e (tagged): three-node client/relay/exit topology with ExitEncap/ExitTransit/ExitDecap traces.

Refs #121

Adds explicit exit-node forwarding as a new node-to-node packet type on
top of polyamide TC, reworking the earlier exit-only encapsulation in
response to maintainer review on encodeous#121:

  - binary NodeIdBin (uint16) replaces variable-length node id strings
    in the on-the-wire header. The mapping is rebuilt deterministically
    from CentralCfg on every apply so all peers agree.

  - the dataplane filter reads an atomic ExitFilterSnapshot pointer
    instead of touching n.LocalCfg / n.CentralCfg / RouterState. The
    snapshot is rebuilt on the dispatch goroutine on config apply and
    on every route mutation, and bundles the per-node next-hop table
    so the filter performs a single atomic.Load per packet.

  - the wire format is documented in core/nylon_unicast.go as a generic
    NyUnicast envelope (fixed 6-byte header, subtype/hop_limit/dst/src),
    leaving room for future node-targeted subtypes (state query, config
    push) without re-inventing the encoding.

LocalCfg gains AdvertiseExitNode and ExitNode; the corresponding
NylonOptions overrides are wired through entrypoint.go. ComputeSysRouteTable
adds 0.0.0.0/0 when ExitNode is set and excludes the default-local
ranges (loopback, link-local, multicast) so they never tunnel.

Tests:
  - state: NodeIdBin map determinism, zero reserved.
  - core: header round-trip, source-ownership rule, sys route table.
  - e2e (tagged): three-node client/relay/exit topology with
    ExitEncap/ExitTransit/ExitDecap traces.

Refs encodeous#121
@Evsio0n
Evsio0n marked this pull request as ready for review May 27, 2026 14:09
@encodeous

encodeous commented May 29, 2026

Copy link
Copy Markdown
Owner

Thanks for the PR! I will review this over the weekend.

:)

Edit: I might not get to it this weekend... :(

@encodeous

Copy link
Copy Markdown
Owner

Sorry for the long delay, work has been very busy for me, and it might take another month or two to get to this PR...

@Evsio0n
Evsio0n force-pushed the feat/exit-node-v2 branch from 108fbd3 to 29f0dfc Compare July 4, 2026 01:05
@encodeous

Copy link
Copy Markdown
Owner

Hi, I just got to reading this PR.

I think the implementation looks sound, but I have one main concern regarding how you want to use NyUnicast.

Your main application of this is in #121, where we want to be able to configure an exit node for egress. Using NyUnicast, we can indeed ensure that packets from client c -> exit node e is routed correctly. However, this is not the case for returning packets from exit node e -> client c.

I can see two ways of solving this:

  1. We implement some userspace NAT, where nylon will keep track of the Node ID <-> connection mapping (this is kind of hard, and I don't want to maintain that)
  2. Apply the ostrich algorithm, and say that the client's source address must be unique to use egress.

Lmk what you think.

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.

2 participants