Skip to content

Commit e79d69b

Browse files
committed
Move all connection negotiation in-memory
1 parent f4e5887 commit e79d69b

30 files changed

+223
-1315
lines changed

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"idtoken",
3939
"Iflag",
4040
"incpatch",
41+
"ipnstate",
4142
"isatty",
4243
"Jobf",
4344
"Keygen",
@@ -52,6 +53,7 @@
5253
"namesgenerator",
5354
"namespacing",
5455
"netaddr",
56+
"netip",
5557
"netmap",
5658
"netns",
5759
"netstack",

agent/agent.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ const (
5151
MagicSessionErrorCode = 229
5252
)
5353

54+
var (
55+
// tailnetIP is a static IPv6 address with the Tailscale prefix that is used to route
56+
// connections from clients to this node. A dynamic address is not required because a Tailnet
57+
// client only dials a single agent at a time.
58+
tailnetIP = netip.MustParseAddr("fd7a:115c:a1e0:49d6:b259:b7ac:b1b2:48f4")
59+
tailnetSSHPort = 1
60+
tailnetReconnectingPTYPort = 2
61+
)
62+
5463
type Options struct {
5564
EnableTailnet bool
5665
CoordinatorDialer CoordinatorDialer
@@ -63,7 +72,6 @@ type Options struct {
6372
}
6473

6574
type Metadata struct {
66-
IPAddresses []netip.Addr `json:"ip_addresses"`
6775
DERPMap *tailcfg.DERPMap `json:"derpmap"`
6876
EnvironmentVariables map[string]string `json:"environment_variables"`
6977
StartupScript string `json:"startup_script"`
@@ -163,18 +171,14 @@ func (a *agent) run(ctx context.Context) {
163171

164172
go a.runWebRTCNetworking(ctx)
165173
if a.enableTailnet {
166-
go a.runTailnet(ctx, metadata.IPAddresses, metadata.DERPMap)
174+
go a.runTailnet(ctx, metadata.DERPMap)
167175
}
168176
}
169177

170-
func (a *agent) runTailnet(ctx context.Context, addresses []netip.Addr, derpMap *tailcfg.DERPMap) {
171-
ipRanges := make([]netip.Prefix, 0, len(addresses))
172-
for _, address := range addresses {
173-
ipRanges = append(ipRanges, netip.PrefixFrom(address, 128))
174-
}
178+
func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) {
175179
var err error
176180
a.network, err = tailnet.NewConn(&tailnet.Options{
177-
Addresses: ipRanges,
181+
Addresses: []netip.Prefix{netip.PrefixFrom(tailnetIP, 128)},
178182
DERPMap: derpMap,
179183
Logger: a.logger.Named("tailnet"),
180184
})
@@ -184,7 +188,7 @@ func (a *agent) runTailnet(ctx context.Context, addresses []netip.Addr, derpMap
184188
}
185189
go a.runCoordinator(ctx)
186190

187-
sshListener, err := a.network.Listen("tcp", ":12212")
191+
sshListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(tailnetSSHPort))
188192
if err != nil {
189193
a.logger.Critical(ctx, "listen for ssh", slog.Error(err))
190194
return
@@ -198,6 +202,20 @@ func (a *agent) runTailnet(ctx context.Context, addresses []netip.Addr, derpMap
198202
go a.sshServer.HandleConn(conn)
199203
}
200204
}()
205+
reconnectingPTYListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(tailnetReconnectingPTYPort))
206+
if err != nil {
207+
a.logger.Critical(ctx, "listen for reconnecting pty", slog.Error(err))
208+
return
209+
}
210+
go func() {
211+
for {
212+
conn, err := reconnectingPTYListener.Accept()
213+
if err != nil {
214+
return
215+
}
216+
go a.handleReconnectingPTY(ctx, "tailnet", conn)
217+
}
218+
}()
201219
}
202220

203221
// runCoordinator listens for nodes and updates the self-node as it changes.

agent/conn.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ func (c *WebRTCConn) Close() error {
135135
}
136136

137137
type TailnetConn struct {
138-
Target netip.Addr
139138
*tailnet.Conn
140139
}
141140

@@ -152,11 +151,11 @@ func (c *TailnetConn) CloseWithError(err error) error {
152151
}
153152

154153
func (c *TailnetConn) ReconnectingPTY(id string, height, width uint16, command string) (net.Conn, error) {
155-
return nil, xerrors.New("not implemented")
154+
return c.DialContextTCP(context.Background(), netip.AddrPortFrom(tailnetIP, uint16(tailnetReconnectingPTYPort)))
156155
}
157156

158157
func (c *TailnetConn) SSH() (net.Conn, error) {
159-
return c.DialContextTCP(context.Background(), netip.AddrPortFrom(c.Target, 12212))
158+
return c.DialContextTCP(context.Background(), netip.AddrPortFrom(tailnetIP, uint16(tailnetSSHPort)))
160159
}
161160

162161
// SSHClient calls SSH to create a client that uses a weak cipher
@@ -181,5 +180,5 @@ func (c *TailnetConn) SSHClient() (*ssh.Client, error) {
181180
func (c *TailnetConn) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) {
182181
_, rawPort, _ := net.SplitHostPort(addr)
183182
port, _ := strconv.Atoi(rawPort)
184-
return c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(c.Target, uint16(port)))
183+
return c.Conn.DialContextTCP(ctx, netip.AddrPortFrom(tailnetIP, uint16(port)))
185184
}

cli/root.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ func Root() *cobra.Command {
133133
update(),
134134
users(),
135135
versionCmd(),
136-
wireguardPortForward(),
137136
workspaceAgent(),
138137
)
139138

cli/server.go

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
"golang.org/x/xerrors"
4242
"google.golang.org/api/idtoken"
4343
"google.golang.org/api/option"
44+
"tailscale.com/tailcfg"
4445

4546
"cdr.dev/slog"
4647
"cdr.dev/slog/sloggers/sloghuman"
@@ -94,6 +95,7 @@ func server() *cobra.Command {
9495
oidcEmailDomain string
9596
oidcIssuerURL string
9697
oidcScopes []string
98+
tailscaleEnable bool
9799
telemetryEnable bool
98100
telemetryURL string
99101
tlsCertFile string
@@ -231,6 +233,17 @@ func server() *cobra.Command {
231233
if err != nil {
232234
return xerrors.Errorf("parse URL: %w", err)
233235
}
236+
accessURLPortRaw := accessURLParsed.Port()
237+
if accessURLPortRaw == "" {
238+
accessURLPortRaw = "80"
239+
if accessURLParsed.Scheme == "https" {
240+
accessURLPortRaw = "443"
241+
}
242+
}
243+
accessURLPort, err := strconv.Atoi(accessURLPortRaw)
244+
if err != nil {
245+
return xerrors.Errorf("parse access URL port: %w", err)
246+
}
234247

235248
// Warn the user if the access URL appears to be a loopback address.
236249
isLocal, err := isLocalURL(ctx, accessURLParsed)
@@ -272,10 +285,61 @@ func server() *cobra.Command {
272285
})
273286
}
274287
options := &coderd.Options{
275-
AccessURL: accessURLParsed,
276-
ICEServers: iceServers,
277-
Logger: logger.Named("coderd"),
278-
Database: databasefake.New(),
288+
AccessURL: accessURLParsed,
289+
ICEServers: iceServers,
290+
Logger: logger.Named("coderd"),
291+
Database: databasefake.New(),
292+
DERPMap: &tailcfg.DERPMap{
293+
Regions: map[int]*tailcfg.DERPRegion{
294+
1: {
295+
RegionID: 1,
296+
RegionCode: "coder",
297+
RegionName: "Coder",
298+
Nodes: []*tailcfg.DERPNode{{
299+
Name: "1a",
300+
RegionID: 1,
301+
STUNOnly: true,
302+
HostName: "stun.l.google.com",
303+
STUNPort: 19302,
304+
}, {
305+
Name: "1b",
306+
RegionID: 1,
307+
HostName: accessURLParsed.Hostname(),
308+
DERPPort: accessURLPort,
309+
STUNPort: -1,
310+
HTTPForTests: accessURLParsed.Scheme == "http",
311+
}},
312+
},
313+
2: {
314+
RegionID: 2,
315+
RegionCode: "nyc",
316+
RegionName: "New York City",
317+
Nodes: []*tailcfg.DERPNode{
318+
{
319+
Name: "2c",
320+
RegionID: 2,
321+
HostName: "derp1c.tailscale.com",
322+
IPv4: "104.248.8.210",
323+
IPv6: "2604:a880:800:10::7a0:e001",
324+
},
325+
},
326+
},
327+
3: {
328+
RegionID: 3,
329+
RegionCode: "sin",
330+
RegionName: "Singapore",
331+
Nodes: []*tailcfg.DERPNode{
332+
{
333+
Name: "3a",
334+
RegionID: 3,
335+
HostName: "derp3.tailscale.com",
336+
IPv4: "68.183.179.66",
337+
IPv6: "2400:6180:0:d1::67d:8001",
338+
},
339+
},
340+
},
341+
},
342+
},
279343
Pubsub: database.NewPubsubInMemory(),
280344
CacheDir: cacheDir,
281345
GoogleTokenValidator: googleTokenValidator,
@@ -710,6 +774,9 @@ func server() *cobra.Command {
710774
"Specifies an issuer URL to use for OIDC.")
711775
cliflag.StringArrayVarP(root.Flags(), &oidcScopes, "oidc-scopes", "", "CODER_OIDC_SCOPES", []string{oidc.ScopeOpenID, "profile", "email"},
712776
"Specifies scopes to grant when authenticating with OIDC.")
777+
cliflag.BoolVarP(root.Flags(), &tailscaleEnable, "tailscale", "", "CODER_TAILSCALE", false,
778+
"Specifies whether Tailscale networking is used for web applications and terminals.")
779+
_ = root.Flags().MarkHidden("tailscale")
713780
enableTelemetryByDefault := !isTest()
714781
cliflag.BoolVarP(root.Flags(), &telemetryEnable, "telemetry", "", "CODER_TELEMETRY", enableTelemetryByDefault, "Specifies whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product.")
715782
cliflag.StringVarP(root.Flags(), &telemetryURL, "telemetry-url", "", "CODER_TELEMETRY_URL", "https://telemetry.coder.com", "Specifies a URL to send telemetry to.")

cli/ssh.go

Lines changed: 26 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@ import (
1919
gosshagent "golang.org/x/crypto/ssh/agent"
2020
"golang.org/x/term"
2121
"golang.org/x/xerrors"
22-
tslogger "tailscale.com/types/logger"
2322

23+
"cdr.dev/slog"
24+
"cdr.dev/slog/sloggers/sloghuman"
25+
26+
"github.com/coder/coder/agent"
2427
"github.com/coder/coder/cli/cliflag"
2528
"github.com/coder/coder/cli/cliui"
2629
"github.com/coder/coder/coderd/autobuild/notify"
2730
"github.com/coder/coder/coderd/util/ptr"
2831
"github.com/coder/coder/codersdk"
2932
"github.com/coder/coder/cryptorand"
30-
"github.com/coder/coder/peer/peerwg"
3133
)
3234

3335
var workspacePollInterval = time.Minute
@@ -85,86 +87,35 @@ func ssh() *cobra.Command {
8587
return xerrors.Errorf("await agent: %w", err)
8688
}
8789

88-
var newSSHClient func() (*gossh.Client, error)
89-
90+
var conn agent.Conn
9091
if !wireguard {
91-
conn, err := client.DialWorkspaceAgent(ctx, workspaceAgent.ID, nil)
92-
if err != nil {
93-
return err
94-
}
95-
defer conn.Close()
96-
97-
stopPolling := tryPollWorkspaceAutostop(ctx, client, workspace)
98-
defer stopPolling()
92+
conn, err = client.DialWorkspaceAgent(ctx, workspaceAgent.ID, nil)
93+
} else {
94+
conn, err = client.DialWorkspaceAgentTailnet(ctx, slog.Make(sloghuman.Sink(cmd.ErrOrStderr())).Leveled(slog.LevelDebug), workspaceAgent.ID)
95+
}
96+
if err != nil {
97+
return err
98+
}
99+
defer conn.Close()
99100

100-
if stdio {
101-
rawSSH, err := conn.SSH()
102-
if err != nil {
103-
return err
104-
}
105-
defer rawSSH.Close()
101+
stopPolling := tryPollWorkspaceAutostop(ctx, client, workspace)
102+
defer stopPolling()
106103

107-
go func() {
108-
_, _ = io.Copy(cmd.OutOrStdout(), rawSSH)
109-
}()
110-
_, _ = io.Copy(rawSSH, cmd.InOrStdin())
111-
return nil
104+
if stdio {
105+
rawSSH, err := conn.SSH()
106+
if err != nil {
107+
return err
112108
}
109+
defer rawSSH.Close()
113110

114-
newSSHClient = conn.SSHClient
115-
} else {
116-
// TODO: more granual control of Tailscale logging.
117-
peerwg.Logf = tslogger.Discard //nolint
118-
119-
// ipv6 := peerwg.UUIDToNetaddr(uuid.New())
120-
// wgn, err := peerwg.New(
121-
// slog.Make(sloghuman.Sink(os.Stderr)),
122-
// []netip.Prefix{netip.PrefixFrom(ipv6, 128)},
123-
// )
124-
// if err != nil {
125-
// return xerrors.Errorf("create wireguard network: %w", err)
126-
// }
127-
128-
// err = client.PostWireguardPeer(cmd.Context(), workspace.ID, peerwg.Handshake{
129-
// Recipient: workspaceAgent.ID,
130-
// NodePublicKey: wgn.NodePrivateKey.Public(),
131-
// DiscoPublicKey: wgn.DiscoPublicKey,
132-
// IPv6: ipv6,
133-
// })
134-
// if err != nil {
135-
// return xerrors.Errorf("post wireguard peer: %w", err)
136-
// }
137-
138-
// err = wgn.AddPeer(peerwg.Handshake{
139-
// Recipient: workspaceAgent.ID,
140-
// DiscoPublicKey: workspaceAgent.DiscoPublicKey,
141-
// NodePublicKey: workspaceAgent.NodePublicKey,
142-
// IPv6: workspaceAgent.IPAddresses[0], // TODO: fix?
143-
// })
144-
// if err != nil {
145-
// return xerrors.Errorf("add workspace agent as peer: %w", err)
146-
// }
147-
148-
// if stdio {
149-
// rawSSH, err := wgn.SSH(cmd.Context(), workspaceAgent.IPAddresses[0]) // TODO: fix?
150-
// if err != nil {
151-
// return err
152-
// }
153-
// defer rawSSH.Close()
154-
155-
// go func() {
156-
// _, _ = io.Copy(cmd.OutOrStdout(), rawSSH)
157-
// }()
158-
// _, _ = io.Copy(rawSSH, cmd.InOrStdin())
159-
// return nil
160-
// }
161-
162-
// newSSHClient = func() (*gossh.Client, error) {
163-
// return wgn.SSHClient(ctx, workspaceAgent.IPAddresses[0]) // TODO: fix?
164-
// }
111+
go func() {
112+
_, _ = io.Copy(cmd.OutOrStdout(), rawSSH)
113+
}()
114+
_, _ = io.Copy(rawSSH, cmd.InOrStdin())
115+
return nil
165116
}
166117

167-
sshClient, err := newSSHClient()
118+
sshClient, err := conn.SSHClient()
168119
if err != nil {
169120
return err
170121
}

0 commit comments

Comments
 (0)