Files
coder/vpn/vpn.proto
T
Ethan 0076e8479f chore(vpn): send ping results over tunnel (#18200)
Closes #17982.

The purpose of this PR is to expose network latency via the API used by Coder Desktop.

This PR has the tunnel ping all known agents every 5 seconds, in order to produce an instance of:
```proto
message LastPing {
	// latency is the RTT of the ping to the agent.
	google.protobuf.Duration latency = 1;
	// did_p2p indicates whether the ping was sent P2P, or over DERP.
	bool did_p2p = 2;
	// preferred_derp is the human readable name of the preferred DERP region,
	// or the region used for the last ping, if it was sent over DERP.
	string preferred_derp = 3;
	// preferred_derp_latency is the last known latency to the preferred DERP
	// region. Unset if the region does not appear in the DERP map.
	optional google.protobuf.Duration preferred_derp_latency = 4;
}
```
The contents of this message are stored and included on all subsequent upsertions of the agent. 
Note that we upsert existing agents every 5 seconds to update the `last_handshake` value.

On the desktop apps, this message will be used to produce a tooltip similar to that of the VS Code extension:
<img width="495" alt="image" src="https://github.com/user-attachments/assets/d8b65f3d-f536-4c64-9af9-35c1a42c92d2" />
(wording not final)

Unlike the VS Code extension, we omit:
- The Latency of *all* available DERP regions. It seems not ideal to send a copy of this entire map for every online agent, and it certainly doesn't make sense for it to be on the `Agent` or `LastPing` message. 
If we do want to expose this info on Coder Desktop, we should consider how best to do so; maybe we want to include it on a more generic `Netcheck` message.
- The current throughput (Bytes up/down). This is out of scope of the linked issue, and is non-trivial to implement. I'm also not sure of the value given the frequency we're doing these status updates (every 5 seconds).
If we want to expose it, it'll be in a separate PR.

<img width="343" alt="image" src="https://github.com/user-attachments/assets/8447d03b-9721-4111-8ac1-332d70a1e8f1" />
2025-06-06 14:18:57 +10:00

270 lines
7.4 KiB
Protocol Buffer
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
syntax = "proto3";
option go_package = "github.com/coder/coder/v2/vpn";
option csharp_namespace = "Coder.Desktop.Vpn.Proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
package vpn;
// The CoderVPN protocol operates over a bidirectional stream between a "manager" and a "tunnel."
// The manager is part of the Coder Desktop application and written in OS native code. It handles
// configuring the VPN and displaying status to the end user. The tunnel is written in Go and
// handles operating the actual tunnel, including reading and writing packets, & communicating with
// the Coder server control plane.
// RPC allows a very simple unary request/response RPC mechanism. The requester generates a unique
// msg_id which it sets on the request, the responder sets response_to that msg_id on the response
// message
message RPC {
uint64 msg_id = 1;
uint64 response_to = 2;
}
// ManagerMessage is a message from the manager (to the tunnel).
message ManagerMessage {
RPC rpc = 1;
oneof msg {
GetPeerUpdate get_peer_update = 2;
NetworkSettingsResponse network_settings = 3;
StartRequest start = 4;
StopRequest stop = 5;
}
}
// TunnelMessage is a message from the tunnel (to the manager).
message TunnelMessage {
RPC rpc = 1;
oneof msg {
Log log = 2;
PeerUpdate peer_update = 3;
NetworkSettingsRequest network_settings = 4;
StartResponse start = 5;
StopResponse stop = 6;
}
}
// ClientMessage is a message from the client (to the service). Windows only.
message ClientMessage {
RPC rpc = 1;
oneof msg {
StartRequest start = 2;
StopRequest stop = 3;
StatusRequest status = 4;
}
}
// ServiceMessage is a message from the service (to the client). Windows only.
message ServiceMessage {
RPC rpc = 1;
oneof msg {
StartResponse start = 2;
StopResponse stop = 3;
Status status = 4; // either in reply to a StatusRequest or broadcasted
}
}
// Log is a log message generated by the tunnel. The manager should log it to the system log. It is
// one-way tunnel -> manager with no response.
message Log {
enum Level {
// these are designed to match slog levels
DEBUG = 0;
INFO = 1;
WARN = 2;
ERROR = 3;
CRITICAL = 4;
FATAL = 5;
}
Level level = 1;
string message = 2;
repeated string logger_names = 3;
message Field {
string name = 1;
string value = 2;
}
repeated Field fields = 4;
}
// GetPeerUpdate asks for a PeerUpdate with a full set of data.
message GetPeerUpdate {}
// PeerUpdate is an update about workspaces and agents connected via the tunnel. It is generated in
// response to GetPeerUpdate (which dumps the full set). It is also generated on any changes (not in
// response to any request).
message PeerUpdate {
repeated Workspace upserted_workspaces = 1;
repeated Agent upserted_agents = 2;
repeated Workspace deleted_workspaces = 3;
repeated Agent deleted_agents = 4;
}
message Workspace {
bytes id = 1; // UUID
string name = 2;
enum Status {
UNKNOWN = 0;
PENDING = 1;
STARTING = 2;
RUNNING = 3;
STOPPING = 4;
STOPPED = 5;
FAILED = 6;
CANCELING = 7;
CANCELED = 8;
DELETING = 9;
DELETED = 10;
}
Status status = 3;
}
message Agent {
bytes id = 1; // UUID
string name = 2;
bytes workspace_id = 3; // UUID
repeated string fqdn = 4;
repeated string ip_addrs = 5;
// last_handshake is the primary indicator of whether we are connected to a peer. Zero value or
// anything longer than 5 minutes ago means there is a problem.
google.protobuf.Timestamp last_handshake = 6;
// If unset, a successful ping has not yet been made.
optional LastPing last_ping = 7;
}
message LastPing {
// latency is the RTT of the ping to the agent.
google.protobuf.Duration latency = 1;
// did_p2p indicates whether the ping was sent P2P, or over DERP.
bool did_p2p = 2;
// preferred_derp is the human readable name of the preferred DERP region,
// or the region used for the last ping, if it was sent over DERP.
string preferred_derp = 3;
// preferred_derp_latency is the last known latency to the preferred DERP
// region. Unset if the region does not appear in the DERP map.
optional google.protobuf.Duration preferred_derp_latency = 4;
}
// NetworkSettingsRequest is based on
// https://developer.apple.com/documentation/networkextension/nepackettunnelnetworksettings for
// macOS. It is a request/response message with response NetworkSettingsResponse
message NetworkSettingsRequest {
uint32 tunnel_overhead_bytes = 1;
uint32 mtu = 2;
message DNSSettings {
repeated string servers = 1;
repeated string search_domains = 2;
// domain_name is the primary domain name of the tunnel
string domain_name = 3;
repeated string match_domains = 4;
// match_domains_no_search specifies if the domains in the matchDomains list should not be
// appended to the resolvers list of search domains.
bool match_domains_no_search = 5;
}
DNSSettings dns_settings = 3;
string tunnel_remote_address = 4;
message IPv4Settings {
repeated string addrs = 1;
repeated string subnet_masks = 2;
// router is the next-hop router in dotted-decimal format
string router = 3;
message IPv4Route {
string destination = 1;
string mask = 2;
// router is the next-hop router in dotted-decimal format
string router = 3;
}
repeated IPv4Route included_routes = 4;
repeated IPv4Route excluded_routes = 5;
}
IPv4Settings ipv4_settings = 5;
message IPv6Settings {
repeated string addrs = 1;
repeated uint32 prefix_lengths = 2;
message IPv6Route {
string destination = 1;
uint32 prefix_length = 2;
// router is the address of the next-hop
string router = 3;
}
repeated IPv6Route included_routes = 3;
repeated IPv6Route excluded_routes = 4;
}
IPv6Settings ipv6_settings = 6;
}
// NetworkSettingsResponse is the response from the manager to the tunnel for a
// NetworkSettingsRequest
message NetworkSettingsResponse {
bool success = 1;
string error_message = 2;
}
// StartRequest is a request from the manager to start the tunnel. The tunnel replies with a
// StartResponse.
message StartRequest {
int32 tunnel_file_descriptor = 1;
string coder_url = 2;
string api_token = 3;
// Additional HTTP headers added to all requests
message Header {
string name = 1;
string value = 2;
}
repeated Header headers = 4;
// Device ID from Coder Desktop
string device_id = 5;
// Device OS from Coder Desktop
string device_os = 6;
// Coder Desktop version
string coder_desktop_version = 7;
}
message StartResponse {
bool success = 1;
string error_message = 2;
}
// StopRequest is a request from the manager to stop the tunnel. The tunnel replies with a
// StopResponse.
message StopRequest {}
// StopResponse is a response to stopping the tunnel. After sending this response, the tunnel closes
// its side of the bidirectional stream for writing.
message StopResponse {
bool success = 1;
string error_message = 2;
}
// StatusRequest is a request to get the status of the tunnel. The manager
// replies with a Status.
message StatusRequest {}
// Status is sent in response to a StatusRequest or broadcasted to all clients
// when the status changes.
message Status {
enum Lifecycle {
UNKNOWN = 0;
STARTING = 1;
STARTED = 2;
STOPPING = 3;
STOPPED = 4;
}
Lifecycle lifecycle = 1;
string error_message = 2;
// This will be a FULL update with all workspaces and agents, so clients
// should replace their current peer state. Only the Upserted fields will
// be populated.
PeerUpdate peer_update = 3;
}