derptun 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +29 -0
- package/README.md +360 -0
- package/bin/derptun.js +66 -0
- package/package.json +30 -0
- package/vendor/aarch64-apple-darwin/derptun/derptun +0 -0
- package/vendor/aarch64-unknown-linux-musl/derptun/derptun +0 -0
- package/vendor/x86_64-apple-darwin/derptun/derptun +0 -0
- package/vendor/x86_64-unknown-linux-musl/derptun/derptun +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shayne
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
# derphole
|
|
2
|
+
|
|
3
|
+
`derphole` is a standalone CLI for session-scoped transfers and temporary local TCP service sharing.
|
|
4
|
+
|
|
5
|
+
It supports:
|
|
6
|
+
|
|
7
|
+
- raw byte streams with `listen` and `pipe`
|
|
8
|
+
- text, file, and directory transfer with `send` and `receive`
|
|
9
|
+
- local TCP service sharing with `share` and `open`
|
|
10
|
+
- SSH access exchange with `ssh invite` and `ssh accept`
|
|
11
|
+
|
|
12
|
+
`derphole` uses the public Tailscale [DERP](#what-is-derp) relay network for rendezvous and relay fallback. It is **not** affiliated with Tailscale, does **not** require a Tailscale account or tailnet, and does **not** use `tailscaled` for transport.
|
|
13
|
+
|
|
14
|
+
`derphole` is **not** a WireGuard overlay and **not** a VPN. Tailscale builds a general-purpose secure network on WireGuard. `derphole` is optimized for a different job: one session, one token, one transfer or one shared service, on the shortest secure path it can find for that session. See [Transport Model](#transport-model), [Why It Is Fast](#why-it-is-fast), and [Security Model](#security-model).
|
|
15
|
+
|
|
16
|
+
For one-shot transfers and temporary service sharing, `derphole` can beat sending the same traffic through a WireGuard-based overlay. It does not build a general-purpose encrypted network path first, then route application traffic through it. It uses DERP for rendezvous and fallback, then moves the live session onto the best direct path it can establish for that workload. Details are in [Transport Model](#transport-model) and [How This Differs From Tailscale / WireGuard](#how-this-differs-from-tailscale--wireguard).
|
|
17
|
+
|
|
18
|
+
It does **not** require:
|
|
19
|
+
|
|
20
|
+
- a Tailscale account
|
|
21
|
+
- a tailnet
|
|
22
|
+
- `tailscaled`
|
|
23
|
+
- a separate control plane to run yourself
|
|
24
|
+
|
|
25
|
+
Session tokens carry authorization, and receive-code flows resolve into the same session model. Public sessions fetch the DERP map at runtime so both sides can find relay and bootstrap nodes. See [Security Model](#security-model) for what the token authorizes and what intermediaries can and cannot see.
|
|
26
|
+
|
|
27
|
+
## Pick the Workflow
|
|
28
|
+
|
|
29
|
+
- Use `listen` and `pipe` for raw byte streams and shell pipelines.
|
|
30
|
+
- Use `send` and `receive` for text, files, directories, progress, and receive-code UX.
|
|
31
|
+
- Use `share` and `open` for temporary access to a local TCP service.
|
|
32
|
+
- Use `ssh invite` and `ssh accept` for SSH public key exchange.
|
|
33
|
+
- Use `derptun` for durable TCP tunnels with reusable, longer-lived tokens.
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
`listen` receives bytes and prints a token. `pipe` sends stdin into that token. `share` and `open` use the same token shape for local TCP services.
|
|
38
|
+
|
|
39
|
+
### Stream a Raw File
|
|
40
|
+
|
|
41
|
+
On the receiving machine:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx -y derphole@latest listen > received.img
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`listen` prints a token to stderr so stdout stays clean for received bytes. Copy that token to the sending machine.
|
|
48
|
+
|
|
49
|
+
On the sending machine:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cat ./disk.img | npx -y derphole@latest pipe <token>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
For quick text:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
printf 'hello\n' | npx -y derphole@latest pipe <token>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Send with a Receive Code
|
|
62
|
+
|
|
63
|
+
On the sending machine:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx -y derphole@latest send ./photo.jpg
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`send` prints the command to run on the receiving machine:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx -y derphole@latest receive <code>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For known-size file and directory transfers, `derphole` prints wormhole-shaped progress and rate output on stderr. Use `--hide-progress` for quiet output.
|
|
76
|
+
|
|
77
|
+
Text uses the same flow:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npx -y derphole@latest send hello
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Directories stream as tar on the wire and re-materialize on the receiver:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npx -y derphole@latest send ./project-dir
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Exchange SSH Access
|
|
90
|
+
|
|
91
|
+
The host receiving access runs:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npx -y derphole@latest ssh invite --user deploy
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The other side accepts with:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npx -y derphole@latest ssh accept <token>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Watch Progress with `pv`
|
|
104
|
+
|
|
105
|
+
`derphole` is plain stdin/stdout, so `pv` fits naturally in the pipeline.
|
|
106
|
+
|
|
107
|
+
Install `pv` if needed:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
brew install pv
|
|
111
|
+
sudo apt install -y pv
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
On the receiving machine:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx -y derphole@latest listen | pv -brt > received.img
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
On the sending machine:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
cat ./disk.img | pv -brt | npx -y derphole@latest pipe <token>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
For a concrete Internet/NAT version of the same pattern, see [Real-World Example: Tar Pipe Over Internet](#real-world-example-tar-pipe-over-internet).
|
|
127
|
+
|
|
128
|
+
### Share a Local TCP Service
|
|
129
|
+
|
|
130
|
+
On the machine running the local web app or API:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npx -y derphole@latest share 127.0.0.1:3000
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`share` prints a token to stderr. Copy that token to the machine that needs access.
|
|
137
|
+
|
|
138
|
+
On another machine, expose the shared service locally:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npx -y derphole@latest open <token>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`open` prints the local listening address to stderr.
|
|
145
|
+
|
|
146
|
+
Bind `open` to a specific local port:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
npx -y derphole@latest open <token> 127.0.0.1:8080
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Durable SSH Tunnels with `derptun`
|
|
153
|
+
|
|
154
|
+
`derptun` is the durable TCP tunnel companion to `derphole`. Use it when a host is behind NAT and you want a stable token you can reuse for days instead of a one-hour, session-scoped share token.
|
|
155
|
+
|
|
156
|
+
On the target host:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npx -y derptun@latest token --days 7 > alpha.token
|
|
160
|
+
npx -y derptun@latest serve --token "$(cat alpha.token)" --tcp 127.0.0.1:22
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
On the client:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
npx -y derptun@latest open --token "$(cat alpha.token)" --listen 127.0.0.1:2222
|
|
167
|
+
ssh -p 2222 foo@127.0.0.1
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
For SSH without a separate local listener, use `ProxyCommand`:
|
|
171
|
+
|
|
172
|
+
```sshconfig
|
|
173
|
+
Host alpha-derptun
|
|
174
|
+
HostName alpha
|
|
175
|
+
User foo
|
|
176
|
+
ProxyCommand derptun connect --token ~/.config/derptun/alpha.token --stdio
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`derptun` keeps trying when the network path drops, and it can reconnect while both `derptun` processes stay alive. If either process exits, the token can bring the tunnel back, but an already-open TCP session is gone. Use `tmux` or `screen` on the remote host when shell continuity matters.
|
|
180
|
+
|
|
181
|
+
The first `derptun` release is TCP-only. UDP forwarding is planned for use cases like Minecraft Bedrock servers, but it is not part of this release.
|
|
182
|
+
|
|
183
|
+
### Useful Extras
|
|
184
|
+
|
|
185
|
+
Use the development channel for the latest commit from `main`:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npx -y derphole@dev version
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
By default, `listen`, `pipe`, `send`, `receive`, `share`, and `open` keep transport status quiet. `listen` and `share` print tokens, `open` prints the local listening address, and `send` / `receive` print the receiver command or code needed to complete the transfer. Known-size transfers show wormhole-shaped progress on stderr. Use `--hide-progress` to suppress the progress bar. Use `--verbose` to see state transitions like `connected-relay` and `connected-direct`:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
npx -y derphole@latest --verbose listen
|
|
195
|
+
npx -y derphole@latest --verbose pipe <token>
|
|
196
|
+
npx -y derphole@latest --verbose send ./photo.jpg
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
For transport details, see [Transport Model](#transport-model), [Behavior](#behavior), or [Security Model](#security-model).
|
|
200
|
+
|
|
201
|
+
## Transport Model
|
|
202
|
+
|
|
203
|
+
High-level flow:
|
|
204
|
+
|
|
205
|
+
1. `listen`, `share`, or `receive` creates an ephemeral session and prints an opaque bearer token or receive code.
|
|
206
|
+
2. The token carries the session ID, expiry, DERP bootstrap hints, the listener's public peer identity, bearer secret, and allowed capability.
|
|
207
|
+
3. `pipe`, `send`, or `open` uses that token to contact the listener through DERP and claim the session.
|
|
208
|
+
4. The listener validates the claim, checks the requested capability, and returns current direct-path candidates.
|
|
209
|
+
5. Both sides start on the first working path, including DERP relay if direct connectivity is not ready yet.
|
|
210
|
+
6. Both sides keep probing for a better direct path. If a direct path succeeds, the live session upgrades in place without restarting the transfer.
|
|
211
|
+
|
|
212
|
+
### Data Plane Selection
|
|
213
|
+
|
|
214
|
+
DERP provides **rendezvous** and **relay fallback**. If the term is new, see [What Is DERP?](#what-is-derp):
|
|
215
|
+
|
|
216
|
+
- rendezvous: exchange initial claim, decision, and direct-path coordination messages without an account-backed control plane
|
|
217
|
+
- relay fallback: keep the session working when NAT traversal fails or direct connectivity is not ready yet
|
|
218
|
+
|
|
219
|
+
The data plane is selected per session:
|
|
220
|
+
|
|
221
|
+
- `share/open` uses multiplexed QUIC streams over `derphole`'s relay/direct UDP transport, so one claimed session can carry many independent TCP connections to the shared service.
|
|
222
|
+
- `listen/pipe` uses a one-shot byte stream. By default, `derphole` coordinates through DERP, promotes to rate-adaptive direct UDP when traversal succeeds, and stays on encrypted relay fallback when no direct path is available.
|
|
223
|
+
- `send/receive` wraps the same one-shot stream with text, file, directory, and progress metadata.
|
|
224
|
+
|
|
225
|
+
Candidate discovery splits into two phases:
|
|
226
|
+
|
|
227
|
+
- fast local candidates first: advertise local socket/interface candidates and any cached port mapping immediately
|
|
228
|
+
- background traversal discovery: run STUN and UPnP / NAT-PMP / PCP refresh, then send updated candidates and `call-me-maybe` probes when a new direct endpoint appears
|
|
229
|
+
|
|
230
|
+
This keeps startup latency low while still allowing relay-to-direct promotion.
|
|
231
|
+
|
|
232
|
+
## How This Differs From Tailscale / WireGuard
|
|
233
|
+
|
|
234
|
+
Tailscale uses WireGuard to build a secure general-purpose network between peers. That is the right abstraction for durable machine-to-machine connectivity, stable private addressing, ACLs, subnet routing, exit nodes, and a long-lived encrypted overlay.
|
|
235
|
+
|
|
236
|
+
`derphole` does something narrower. It creates session-scoped transport for a single transfer or one shared service:
|
|
237
|
+
|
|
238
|
+
- no WireGuard tunnel device
|
|
239
|
+
- no overlay network interface
|
|
240
|
+
- no persistent mesh control plane
|
|
241
|
+
- no need to route arbitrary traffic through a general encrypted network
|
|
242
|
+
|
|
243
|
+
Instead, `derphole` uses a bearer token to authorize one session, uses DERP to get both peers talking immediately, and promotes the session onto the best direct path it can establish for that workload. Supporting details are in [Transport Model](#transport-model) and [Security Model](#security-model).
|
|
244
|
+
|
|
245
|
+
For `listen/pipe`, `send/receive`, and `share/open`, this can beat routing the same traffic through a WireGuard-based overlay because `derphole` is purpose-built for the active session, not for a general secure network abstraction. See [Why It Is Fast](#why-it-is-fast) for concrete transport reasons.
|
|
246
|
+
|
|
247
|
+
## Why It Is Fast
|
|
248
|
+
|
|
249
|
+
`derphole` gets performance from its transport design:
|
|
250
|
+
|
|
251
|
+
- DERP is for rendezvous and relay fallback, not the preferred steady-state data plane.
|
|
252
|
+
- Sessions can start relayed immediately, then promote in place to direct without restarting the transfer.
|
|
253
|
+
- `listen/pipe` and `send/receive` can scale from one to multiple direct UDP lanes, run a short path-rate probe, then use paced sending, adaptive rate control, and targeted replay/repair. Fast links can run near their WAN ceiling without forcing slower links into the same send rate.
|
|
254
|
+
- Direct UDP payload packets are AEAD-protected with a per-session key derived from the bearer secret. The packet header stays visible for sequencing and repair, while user bytes stay encrypted and authenticated.
|
|
255
|
+
- `share/open` keeps QUIC stream multiplexing for service sharing, where many independent TCP streams need one claimed session.
|
|
256
|
+
- Candidate discovery is front-loaded with local interface candidates and cached mappings, then refined in the background with STUN and port mapping refresh. That keeps the first byte moving quickly instead of stalling the session until every traversal probe finishes.
|
|
257
|
+
|
|
258
|
+
In practice: move bytes early, keep them moving through relay if needed, then shift the live session onto a faster direct path as soon as direct connectivity is ready.
|
|
259
|
+
|
|
260
|
+
## Security Model
|
|
261
|
+
|
|
262
|
+
The session token is a **bearer capability**. Anyone with the token can claim the session until it expires, so share it over a trusted channel. Tokens expire after one hour.
|
|
263
|
+
|
|
264
|
+
DERP relays do **not** get the secret material needed to read or impersonate the session:
|
|
265
|
+
|
|
266
|
+
- On the default `listen/pipe` and `send/receive` direct UDP path, payload packets are encrypted and authenticated with session AEAD derived from the token bearer secret.
|
|
267
|
+
- On `share/open`, stream traffic uses authenticated QUIC streams for the claimed session.
|
|
268
|
+
- If packets are relayed through DERP, DERP only forwards encrypted session bytes.
|
|
269
|
+
|
|
270
|
+
Important security property: `derphole` does not trade speed for plaintext shortcuts:
|
|
271
|
+
|
|
272
|
+
- the token authorizes the session, but does not turn DERP into a trusted decrypting proxy
|
|
273
|
+
- direct UDP data packets are encrypted and authenticated per session
|
|
274
|
+
- QUIC stream-mode peers are pinned to the expected public identity from the token
|
|
275
|
+
- DERP forwards encrypted traffic but does not have the keys required to decrypt or impersonate the session
|
|
276
|
+
|
|
277
|
+
Simple rule: token possession authorizes the session, but intermediaries that only see DERP traffic do not have the keys needed to decrypt it.
|
|
278
|
+
|
|
279
|
+
## Behavior
|
|
280
|
+
|
|
281
|
+
Sessions can start on DERP relay and later promote to a direct path without restarting. By default, the CLI keeps transport status quiet and prints only the user-facing token, bind address, or transfer UI needed to use the session. Use `--verbose` to inspect path changes, NAT traversal state, and direct-path tuning.
|
|
282
|
+
|
|
283
|
+
## Use Cases
|
|
284
|
+
|
|
285
|
+
- cross-host transfer with no account setup
|
|
286
|
+
- NAT-heavy networks where direct connectivity may or may not work
|
|
287
|
+
- quick sharing of local web apps, APIs, and admin interfaces
|
|
288
|
+
- `npx` use without manual install
|
|
289
|
+
|
|
290
|
+
## Real-World Example: Tar Pipe Over Internet
|
|
291
|
+
|
|
292
|
+
Classic tar pipe is fast because it streams bytes from `tar` on one host into `tar` on another host. Good reference: [Using netcat and tar to quickly transfer files between machines, aka tar pipe](https://toast.djw.org.uk/tarpipe.html).
|
|
293
|
+
|
|
294
|
+
Problem: classic `tar | nc` assumes the receiver can expose a listening port and the sender can reach it. That breaks down when both hosts are on the public Internet, both sit behind NAT, and neither side should expose an inbound port.
|
|
295
|
+
|
|
296
|
+
`derphole` keeps the same streaming shape, but removes the open-port requirement.
|
|
297
|
+
|
|
298
|
+
Receiver:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
npx -y derphole@latest listen | tar -xpf - -C /restore/path
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
`listen` prints a token on stderr. Copy that token to the sender over a channel you trust.
|
|
305
|
+
|
|
306
|
+
Sender:
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
tar -cpf - /srv/data | npx -y derphole@latest pipe <token>
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
This is still tar pipe. Difference: no public listener to expose, no SSH daemon required for the data path, no VPN to join, and no permanent mesh to set up. `derphole` starts with DERP if needed, then promotes the live transfer onto direct UDP when a faster direct path becomes available.
|
|
313
|
+
|
|
314
|
+
## Development
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
mise install
|
|
318
|
+
mise run install-githooks
|
|
319
|
+
mise run check
|
|
320
|
+
mise run build
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`mise run build` writes `dist/derphole` and `dist/derptun`.
|
|
324
|
+
|
|
325
|
+
## Verification
|
|
326
|
+
|
|
327
|
+
Local smoke test:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
mise run smoke-local
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Remote smoke tests against a host you control:
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
REMOTE_HOST=my-server.example.com mise run smoke-remote
|
|
337
|
+
REMOTE_HOST=my-server.example.com mise run smoke-remote-share
|
|
338
|
+
REMOTE_HOST=my-server.example.com mise run smoke-remote-derptun
|
|
339
|
+
REMOTE_HOST=my-server.example.com mise run promotion-1g
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
## Releases
|
|
343
|
+
|
|
344
|
+
- npm packages: `derphole`, `derptun`
|
|
345
|
+
- production channels: `derphole@latest`, `derptun@latest`
|
|
346
|
+
- development channels: `derphole@dev`, `derptun@dev`
|
|
347
|
+
- bootstrap runbook: [docs/releases/npm-bootstrap.md](docs/releases/npm-bootstrap.md)
|
|
348
|
+
|
|
349
|
+
## What Is DERP?
|
|
350
|
+
|
|
351
|
+
DERP stands for **Designated Encrypted Relay for Packets**. In plain terms, it is a globally reachable relay network that both peers can talk to even when they cannot yet talk directly to each other.
|
|
352
|
+
|
|
353
|
+
DERP was built by Tailscale for the Tailscale networking stack, and the public Tailscale-operated DERP network is reachable without running your own relays. The same DERP model is also used by Headscale, the open-source Tailscale control server implementation, which can serve its own DERP map and DERP servers.
|
|
354
|
+
|
|
355
|
+
In `derphole`, DERP has two jobs:
|
|
356
|
+
|
|
357
|
+
- rendezvous: carry the initial claim, decision, and direct-path coordination messages so the two peers can find each other without a separate account-backed control plane
|
|
358
|
+
- fallback relay: carry encrypted session traffic when NAT traversal has not succeeded yet or when direct connectivity is unavailable
|
|
359
|
+
|
|
360
|
+
DERP is not the preferred steady-state path. It is the safety net that gets the session started and keeps it working. If a direct UDP path becomes available, `derphole` promotes the live session onto that direct path. DERP only forwards bytes; it does not get the session keys needed to decrypt the traffic.
|
package/bin/derptun.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
const triples = new Map([
|
|
13
|
+
["linux:x64", "x86_64-unknown-linux-musl"],
|
|
14
|
+
["linux:arm64", "aarch64-unknown-linux-musl"],
|
|
15
|
+
["darwin:x64", "x86_64-apple-darwin"],
|
|
16
|
+
["darwin:arm64", "aarch64-apple-darwin"]
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
const triple = triples.get(`${process.platform}:${process.arch}`);
|
|
20
|
+
if (!triple) {
|
|
21
|
+
console.error(`Unsupported platform: ${process.platform} (${process.arch})`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const binaryName = process.platform === "win32" ? "derptun.exe" : "derptun";
|
|
26
|
+
const binaryPath = path.join(__dirname, "..", "vendor", triple, "derptun", binaryName);
|
|
27
|
+
if (!existsSync(binaryPath)) {
|
|
28
|
+
console.error(`Missing vendored binary: ${binaryPath}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
33
|
+
stdio: "inherit",
|
|
34
|
+
env: { ...process.env, DERPTUN_MANAGED_BY_NPM: "1" }
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
child.on("error", (err) => {
|
|
38
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
39
|
+
console.error(`Failed to launch vendored binary: ${reason}`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
["SIGINT", "SIGTERM", "SIGHUP"].forEach((sig) => {
|
|
44
|
+
process.on(sig, () => {
|
|
45
|
+
if (!child.killed) {
|
|
46
|
+
child.kill(sig);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const result = await new Promise((resolve) => {
|
|
52
|
+
child.on("exit", (code, signal) => {
|
|
53
|
+
if (signal) {
|
|
54
|
+
resolve({ signal });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
resolve({ code: code ?? 1 });
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (result.signal) {
|
|
62
|
+
const signalNumber = os.constants.signals[result.signal];
|
|
63
|
+
process.exit(typeof signalNumber === "number" ? 128 + signalNumber : 1);
|
|
64
|
+
} else {
|
|
65
|
+
process.exit(result.code);
|
|
66
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "derptun",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "BSD-3-Clause",
|
|
5
|
+
"bin": {
|
|
6
|
+
"derptun": "bin/derptun.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"os": [
|
|
10
|
+
"linux",
|
|
11
|
+
"darwin"
|
|
12
|
+
],
|
|
13
|
+
"cpu": [
|
|
14
|
+
"x64",
|
|
15
|
+
"arm64"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=16"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"bin",
|
|
22
|
+
"vendor",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/shayne/derphole.git"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|