connet

connet is a peer-to-peer reverse proxy for NAT traversal. It is inspired by ngrok, frp, rathole and others.
connet helps expose a service running on a device to another device on the internet. Unlike the others,
the connet client runs on both the device that exposes the service (called destination in connet's terms)
and the device that wants to access the service (called source). This means that communication between connet
clients is never public and visible to the rest of the internet, and in many cases peers can communicate directly.
Status connet is currently alpha software. We expect some issues and its APIs are subject to change.
Features
- Peer-to-peer communication Because you run the
connet client on both the destination and the source, the server
is only used for sharing configuration. In many cases clients can communicate directly, which enables better privacy and
performance.
- Relay support There are cases when clients are unable to find a path to communicate directly. In such cases, they
can use a relay server to maintain connectivity.
- Security Everything is private, encrypted with TLS. Public server and client certificates are exchanges between peers
and are required and verified to establish connectivity. Clients and relays need to present a mandatory token when communicating
with the control server, allowing tight control over who can use
connet.
- Embeddable In case you want
connet running as part of another (golang) program (as opposed to a separate executable),
connet has a well defined api for running both the client and the server.
Architecture
flowchart LR
subgraph Server
C(Control Server)
R(Relay Server)
C -.->|Relay Info| R
end
subgraph Client Source
S[Source] -->|Exchange Direct and Relay Info| C(Control Server)
SC(Client) -..-> S
end
subgraph Client Destination
D[Destination] -->|Exchange Direct and Relay Info| C(Control Server)
D -..-> DC(Server)
end
S ---|Direct Connection| D
S -->|Relay Connection| R
R -->|Relay Connection| D
For all communication connet uses the QUIC protocol, which is build on top of UDP.
Quickstart
Latest builds of connet can be acquired from our releases page.
We also provide docker images,
check our docker section to see how you can use them.
If you are using NixOS, check also the NixOS section.
To get started with connet, you'll need 3 devices:
- Server which your clients can communicate with. In most cases, this server will have a public IP and be directly
accessible by clients. A VPS instance at one of the many cloud providers goes a long way here.
- Device
D that has the destination service you want to connect to, running at port 3000.
- Device
S (aka source) which you want to connect to the service, at port 8000.
Server
In the setup above, start connet server --config server.toml with the following server.toml:
[server]
tokens = ["client-d-token", "client-s-token"]
cert-file = "cert.pem"
key-file = "key.pem"
TLS Certificates
To run a connet server, you'll need a TLS certificate. You have a few options to create such certificate:
- Recommended use an ACME client to provision one for you. We've had good experiences
running lego.
- Buy a TLS certificate from a Certificate Authority like verisign, namecheap, etc.
- Use a self-signed TLS certificate, an option most appropriate for testing.
To create a self-signed certificate, you can use openssl. Alternatively, you can use a tool like
minica. When using self-signed certificate, you'll need your clients (and relays)
trusting the server's certificate. Copying the certificate (or CA) public key to the clients and using server-cas
configuration option is the easiest way to achieve this.
Client D (aka the destination)
Then, on device D run connet --config client-d.toml with the following client-d.toml:
[client]
token = "client-d-token"
server-addr = "SERVER_IP:19190"
server-cas = "cert.pem"
[client.destinations.serviceA]
addr = ":3000"
Client S (aka the source)
On device S run connet --config client-s.toml with the following client-s.toml:
[client]
token = "client-s-token"
server-addr = "SERVER_IP:19190"
server-cas = "cert.pem"
[client.sources.serviceA]
addr = ":8000"
Configuration
You can use both a toml config file as well as command line when running connet. If you use both a config file and
command line options, the latter takes precence, overriding any config file options. For simplicity, command line options
only support a single destination or source configuration.
Client
To run a client, use connet --config client-config.toml command. Here is the full client client-config.toml
configuration spec:
[client]
token = "client-token-1" # the token which the client uses to authenticate against the control server
token-file = "path/to/relay/token" # a file that contains the token, one of token or token-file is required
server-addr = "localhost:19190" # the control server address to connect to
server-cas = "path/to/cert.pem" # the control server certificate
direct-addr = ":19192" # at what address this client listens for direct connections, defaults to :19192
status-addr = "127.0.0.1:19182" # at what address this client listens for status connections, disabled unless set
[client.destinations.serviceX]
addr = "localhost:3000" # where this destination connects to, required
route = "any" # what kind of routes to use, `any` will use both `direct` and `relay`
[client.destinations.serviceY]
addr = "192.168.1.100:8000" # multiple destinations can be defined, they are matched by name at the server
route = "direct" # force only direct communication between clients
[client.sources.serviceX] # matches destinations.serviceX
addr = ":8000" # the address at which to listen for incoming connections to be forwarded
route = "relay" # the kind of route to use
[client.sources.serviceY] # both sources and destinations can be defined in a single file
addr = ":8001" # again, mulitple sources can be defined
route = "direct" # force only direct communication between clients, even if other end allows any
Server
To run a server (e.g. running both control and a relay server), use connet server --config server-config.toml command.
Here is the full server server-config.toml configuration specification:
[server]
addr = ":19190" # the address at which the control server will listen for connections, defaults to :19190
cert-file = "path/to/cert.pem" # the server certificate file, in pem format
key-file = "path/to/key.pem" # the server certificate private key file
tokens = ["client-token-1", "client-token-n"] # set of recognized client tokens
tokens-file = "path/to/client/tokens" # a file that contains a list of client tokens, one token per line
# one of tokens or tokens-file is required
relay-addr = ":19191" # the address at which the relay will listen for connectsion, defaults to :19191
relay-hostname = "localhost" # the public hostname (e.g. domain, ip address) which will be advertised to clients, defaults to localhost
status-addr = "127.0.0.1:19180" # at what address the server listens for status connections, disabled unless set
store-dir = "path/to/server-store" # where does this server persist runtime information, defaults to a /tmp subdirectory
[server.ip-restriction] # defines restriction applicable for all client tokens, checked before verifying the token
allow-cidrs = [] # set of networks in CIDR format, to allow client connetctions from
deny-cidrs = [] # set of networks in CIDR format, to deny client connetctions from
[[server.token-ip-restriction]] # defines restriction per client token, if specified must match the number of client tokens
allow-cidrs = [] # set of networks in CIDR format, to allow token client connetctions from
deny-cidrs = [] # set of networks in CIDR format, to deny token client connetctions from
Control server
To run a control server, use connet control --config control-config.toml command. Here is the full control server
control-config.toml configuration specification:
[control]
addr = ":19190" # the address at which the control server will listen for connections, defaults to :19190
cert-file = "path/to/cert.pem" # the server certificate file, in pem format
key-file = "path/to/key.pem" # the server certificate private key file
client-tokens = ["client-token-1", "client-token-n"] # set of recognized client tokens
client-tokens-file = "path/to/client/tokens" # a file that contains a list of client tokens, one token per line
# one of client-tokens or client-tokens-file is required
relay-tokens = ["relay-token-1", "relay-token-n"] # set of recognized relay tokens
relay-tokens-file = "path/to/relay/token" # a file that contains a list of relay tokens, one token per line
# one of relay-tokens or relay-tokens-file is necessary when connecting relays
status-addr = "127.0.0.1:19180" # at what address the control server listens for status connections, disabled unless set
store-dir = "path/to/control-store" # where does this control server persist runtime information, defaults to a /tmp subdirectory
[control.client-ip-restriction] # defines restriction applicable for all client tokens, checked before verifying the token
allow-cidrs = [] # set of networks in CIDR format, to allow client connetctions from
deny-cidrs = [] # set of networks in CIDR format, to deny client connetctions from
[[control.client-token-ip-restriction]] # defines restriction per client token, if specified must match the number of client tokens
allow-cidrs = [] # set of networks in CIDR format, to allow client connetctions from
deny-cidrs = [] # set of networks in CIDR format, to deny client connetctions from
[control.relay-ip-restriction] # defines restriction applicable for all relay tokens, checked before verifying the token
allow-cidrs = [] # set of networks in CIDR format, to allow relay connetctions from
deny-cidrs = [] # set of networks in CIDR format, to deny relay connetctions from
[[control.relay-token-ip-restriction]] # defines restriction per relay token, if specified must match the number of relay tokens
allow-cidrs = [] # set of networks in CIDR format, to allow relay connetctions from
deny-cidrs = [] # set of networks in CIDR format, to deny relay connetctions from
Relay server
To run a relay server, use connet relay --config relay-config.toml command. Here is the full relay server
relay-config.toml configuration specification:
[relay]
token = "relay-token-1" # the token which the relay server uses to authenticate against the control server
token-file = "path/to/relay/token" # a file that contains the token, one of token or token-file is required
addr = ":19191" # the address at which the relay will listen for connectsion, defaults to :19191
hostname = "localhost" # the public hostname (e.g. domain, ip address) which will be advertised to clients, defaults to localhost
control-addr = "localhost:19190" # the control server address to connect to, defaults to localhost:19191
control-cas = "path/to/ca/file.pem" # the public certificate root of the control server, no default, required when using self-signed certs
status-addr = "127.0.0.1:19181" # at what address the relay server listens for status connections, disabled unless set
store-dir = "path/to/relay-store" # where does this relay persist runtime information, defaults to a /tmp subdirectory
IP Restrictions
You can restrict clients and relays to connect only from specific IPs using different ip-restriction options.
They accept allow/deny list of strings in CIDR format, as defined by RFC 4632 and
RFC 4291, for example (to restrict the set of client IPs that can connect to the server):
[server.ip-restriction]
allow-cidrs = ["192.0.2.0/24"]
deny-cidrs = ["198.51.100.0/24"]
If these options are specified, an IP will be rejected if:
- it matches any of the CIDRs in the
deny-cidrs list
- it matches none of the CIDRs in the
allow-cidrs list. If the allow-cidrs list is empty, the IP will not be rejected.
Storage
connet servers (both control and relay servers) store runtime state on the file system. If you don't explicitly specify
store-dir, they will use a new subdirectory in /tmp by default, which means that every time they restart they'll loose
any state and identity. To prevent this, you can specify an explicit store-dir location, which can be reused between runs.
Logging
At the root of the config file, you can configure logging (connet uses slog internally):
log-level = "info" # supports debug, info, warn, error, defaults to info
log-format = "text" # supports text and json, defaults to text
Tunning
On some systems, if you might see the following line in the logs:
failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 7168 kiB, got: 416 kiB). See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details.
In which case, we recommend visiting the wiki page and applying the recommended changes.
NixOS
connet contains NixOS modules that you can use for running:
To configure the client as a service:
# configuration.nix
{ config, pkgs, ... }:
let
connet-repo = builtins.fetchGit {
url = "https://github.com/connet-dev/connet";
ref = "main";
};
in
{
imports = [
# ...
"${connet-repo}/nix/client-module.nix"
];
# ...
services.connet = {
enable = true;
tokenFile = "/run/keys/connet.token";
serverAddr = "localhost:19190";
sources.example.addr = ":9000";
};
}
Flakes
To configure the client as a service:
# flake.nix
{
inputs = {
# ...
connet.url = "github.com/connet-dev/connet";
};
outputs = { connet, ... }: {
nixosConfigurations.example = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
# ...
connet.nixosModules.default
{
services.connet = {
enable = true;
tokenFile = "/run/keys/connet.token";
serverAddr = "localhost:19190";
sources.example.addr = ":9000";
};
}
];
};
};
}
Docker
Docker images are available at ghcr.io and you can pull them via:
docker pull ghcr.io/connet-dev/connet:latest
To run the client you can use something like:
docker run -p 19192:19192 -p 9000:9000 connet \
--server-addr "localhost:19190" --token "CLIENT_TOKEN" \
--src-name "example" --src-addr ":9000"
Or if you are using a config file on your system:
docker run -p 19192:19192 -p 9000:9000 \
--mount "type=bind,source=/path/to/config,target=/data" \
connet --config "/data/config.toml"
Examples
TBD
Hosting
If you want to use connet, but you don't want to run the server yourself, we have also built a hosted service
at connet.dev. It is free when clients connect directly, builds upon the open source components
by adding account management and it is one of the easiest way to start.
Future
- UDP support
- Use quic-go tracer, instead of ping (and duration estimation)
- Stateless reset key for the server