...is a toy project for port-forwarding between machines on the internet. It works by passing traffic through an intermediary server which is reachable by all parties.
It uses gRPC streaming to pass control messages and proxy traffic, which means that the intermediary server can run in Cloud Run.
In pre-COVID times we would often port-forward between machines to quickly test new servers as they were being developed. When everyone was in the same room this was easy! Working remotely has made this a lot more complex...
Although there are a lot of existing solutions out there for this, seemed like an interesting problem to tackle - especially from the point of view of trying to make it as simple as possible, and practical to run (i.e. not too expensive).
There are two main pieces which make the system work:
router
: the intermediary process (seecmd/crrouter
, named as it is the Cloud Run variant of the originalmmrouter
).client
the CLI tool run by users to create/use forwards (seecmd/mmclient
).
Users share a local forward on their machine by registering a service
.
- Client process calls
CreateService
which creates the service inrouter
, and waits for forward requests to come through. - When a forward request arrives on the
router
it responds to theclient
via the response stream waiting on theCreateService
request, signalling it to create a new proxying connection to handle the traffic for the new forward. The connection is given atoken
which identifies it when it is received by therouter
. - When the proxying request arrives at the
router
, it matches it to the forward (using thetoken
) and bridges the two connections.
Users access a service
by:
- Client exposes a local port, which has a TCP listener. Connections made to this listener will be forwarded to the service.
- For each new connection, the client process calls
ForwardToService
which checks the service still exists, and returns atoken
to identify the proxying connection. - Client creates a proxying connection, using the provided
token
, and begins to copy data between the local connection and the proxying connection.
The code was initially designed so that a separate TCP server would run on the router and host the proxy connections. Though easier to debug, this meant it couldn't be used in Cloud Run.
Since Cloud Run was updated to support gRPC bi-directional streaming, it was suddenly a possibility!
The proxying connections are now handled via internal/protoproxy
which creates implementations of net.Conn
and net.Listener
to translate calls to Write
into stream sends, calls to Read
into stream receives, and Accept
into new connections.
The tricky bit is correctly handling when a connection closes, and so there are likely some lingering bugs here.