Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 8fd9eca

Browse files
committed
Merge branch 'main' into rmwebrtc
2 parents 2403c94 + e3bbc77 commit 8fd9eca

File tree

9 files changed

+201
-11
lines changed

9 files changed

+201
-11
lines changed

agent/agent.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"go.uber.org/atomic"
3030
gossh "golang.org/x/crypto/ssh"
3131
"golang.org/x/xerrors"
32+
"tailscale.com/net/speedtest"
3233
"tailscale.com/tailcfg"
3334

3435
"cdr.dev/slog"
@@ -56,6 +57,7 @@ var (
5657
tailnetIP = netip.MustParseAddr("fd7a:115c:a1e0:49d6:b259:b7ac:b1b2:48f4")
5758
tailnetSSHPort = 1
5859
tailnetReconnectingPTYPort = 2
60+
tailnetSpeedtestPort = 3
5961
)
6062

6163
type Options struct {
@@ -246,6 +248,27 @@ func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) {
246248
go a.handleReconnectingPTY(ctx, msg, conn)
247249
}
248250
}()
251+
speedtestListener, err := a.network.Listen("tcp", ":"+strconv.Itoa(tailnetSpeedtestPort))
252+
if err != nil {
253+
a.logger.Critical(ctx, "listen for speedtest", slog.Error(err))
254+
return
255+
}
256+
go func() {
257+
for {
258+
conn, err := speedtestListener.Accept()
259+
if err != nil {
260+
a.logger.Debug(ctx, "speedtest listener failed", slog.Error(err))
261+
return
262+
}
263+
a.closeMutex.Lock()
264+
a.connCloseWait.Add(1)
265+
a.closeMutex.Unlock()
266+
go func() {
267+
a.connCloseWait.Done()
268+
_ = speedtest.ServeConn(conn)
269+
}()
270+
}
271+
}()
249272
}
250273

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

agent/agent_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"time"
2020

2121
"golang.org/x/xerrors"
22+
"tailscale.com/net/speedtest"
2223

2324
scp "github.com/bramvdbogaerde/go-scp"
2425
"github.com/google/uuid"
@@ -491,6 +492,21 @@ func TestAgent(t *testing.T) {
491492
return err == nil
492493
}, testutil.WaitMedium, testutil.IntervalFast)
493494
})
495+
496+
t.Run("Speedtest", func(t *testing.T) {
497+
t.Parallel()
498+
if testing.Short() {
499+
t.Skip("The minimum duration for a speedtest is hardcoded in Tailscale to 5s!")
500+
}
501+
derpMap := tailnettest.RunDERPAndSTUN(t)
502+
conn, _ := setupAgent(t, agent.Metadata{
503+
DERPMap: derpMap,
504+
}, 0)
505+
defer conn.Close()
506+
res, err := conn.Speedtest(speedtest.Upload, speedtest.MinDuration)
507+
require.NoError(t, err)
508+
t.Logf("%.2f MBits/s", res[len(res)-1].MBitsPerSecond())
509+
})
494510
}
495511

496512
func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exec.Cmd {

agent/conn.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"golang.org/x/crypto/ssh"
1313
"golang.org/x/xerrors"
1414
"tailscale.com/ipn/ipnstate"
15+
"tailscale.com/net/speedtest"
1516
"tailscale.com/tailcfg"
1617

1718
"github.com/coder/coder/tailnet"
@@ -115,6 +116,18 @@ func (c *Conn) SSHClient() (*ssh.Client, error) {
115116
return ssh.NewClient(sshConn, channels, requests), nil
116117
}
117118

119+
func (c *Conn) Speedtest(direction speedtest.Direction, duration time.Duration) ([]speedtest.Result, error) {
120+
speedConn, err := c.DialContextTCP(context.Background(), netip.AddrPortFrom(tailnetIP, uint16(tailnetSpeedtestPort)))
121+
if err != nil {
122+
return nil, xerrors.Errorf("dial speedtest: %w", err)
123+
}
124+
results, err := speedtest.RunClientWithConn(direction, duration, speedConn)
125+
if err != nil {
126+
return nil, xerrors.Errorf("run speedtest: %w", err)
127+
}
128+
return results, err
129+
}
130+
118131
func (c *Conn) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) {
119132
if network == "unix" {
120133
return nil, xerrors.New("network must be tcp or udp")

cli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ func Core() []*cobra.Command {
7878
schedules(),
7979
show(),
8080
ssh(),
81+
speedtest(),
8182
start(),
8283
state(),
8384
stop(),

cli/speedtest.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"cdr.dev/slog"
9+
"github.com/coder/coder/cli/cliflag"
10+
"github.com/coder/coder/cli/cliui"
11+
"github.com/coder/coder/codersdk"
12+
"github.com/jedib0t/go-pretty/v6/table"
13+
"github.com/spf13/cobra"
14+
"golang.org/x/xerrors"
15+
tsspeedtest "tailscale.com/net/speedtest"
16+
)
17+
18+
func speedtest() *cobra.Command {
19+
var (
20+
reverse bool
21+
timeStr string
22+
)
23+
cmd := &cobra.Command{
24+
Annotations: workspaceCommand,
25+
Use: "speedtest <workspace>",
26+
Short: "Run a speed test from your machine to the workspace.",
27+
RunE: func(cmd *cobra.Command, args []string) error {
28+
ctx, cancel := context.WithCancel(cmd.Context())
29+
defer cancel()
30+
31+
dur, err := time.ParseDuration(timeStr)
32+
if err != nil {
33+
return err
34+
}
35+
36+
client, err := CreateClient(cmd)
37+
if err != nil {
38+
return xerrors.Errorf("create codersdk client: %w", err)
39+
}
40+
41+
workspace, workspaceAgent, err := getWorkspaceAndAgent(ctx, cmd, client, codersdk.Me, args[0], false)
42+
if err != nil {
43+
return err
44+
}
45+
46+
err = cliui.Agent(ctx, cmd.ErrOrStderr(), cliui.AgentOptions{
47+
WorkspaceName: workspace.Name,
48+
Fetch: func(ctx context.Context) (codersdk.WorkspaceAgent, error) {
49+
return client.WorkspaceAgent(ctx, workspaceAgent.ID)
50+
},
51+
})
52+
if err != nil {
53+
return xerrors.Errorf("await agent: %w", err)
54+
}
55+
conn, err := client.DialWorkspaceAgentTailnet(ctx, slog.Logger{}, workspaceAgent.ID)
56+
if err != nil {
57+
return err
58+
}
59+
defer conn.Close()
60+
_, _ = conn.Ping()
61+
dir := tsspeedtest.Download
62+
if reverse {
63+
dir = tsspeedtest.Upload
64+
}
65+
cmd.Printf("Starting a %ds %s test...\n", int(dur.Seconds()), dir)
66+
results, err := conn.Speedtest(dir, dur)
67+
if err != nil {
68+
return err
69+
}
70+
tableWriter := cliui.Table()
71+
tableWriter.AppendHeader(table.Row{"Interval", "Transfer", "Bandwidth"})
72+
for _, r := range results {
73+
if r.Total {
74+
tableWriter.AppendSeparator()
75+
}
76+
tableWriter.AppendRow(table.Row{
77+
fmt.Sprintf("%.2f-%.2f sec", r.IntervalStart.Seconds(), r.IntervalEnd.Seconds()),
78+
fmt.Sprintf("%.4f MBits", r.MegaBits()),
79+
fmt.Sprintf("%.4f Mbits/sec", r.MBitsPerSecond()),
80+
})
81+
}
82+
_, err = fmt.Fprintln(cmd.OutOrStdout(), tableWriter.Render())
83+
return err
84+
},
85+
}
86+
cliflag.BoolVarP(cmd.Flags(), &reverse, "reverse", "r", "", false,
87+
"Specifies whether to run in reverse mode where the client receives and the server sends.")
88+
cliflag.StringVarP(cmd.Flags(), &timeStr, "time", "t", "", "5s",
89+
"Specifies the duration to monitor traffic.")
90+
return cmd
91+
}

cli/speedtest_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package cli_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"cdr.dev/slog/sloggers/slogtest"
8+
"github.com/coder/coder/agent"
9+
"github.com/coder/coder/cli/clitest"
10+
"github.com/coder/coder/coderd/coderdtest"
11+
"github.com/coder/coder/codersdk"
12+
"github.com/coder/coder/pty/ptytest"
13+
"github.com/coder/coder/testutil"
14+
"github.com/stretchr/testify/assert"
15+
)
16+
17+
func TestSpeedtest(t *testing.T) {
18+
t.Parallel()
19+
if testing.Short() {
20+
t.Skip("This test takes a minimum of 5ms per a hardcoded value in Tailscale!")
21+
}
22+
client, workspace, agentToken := setupWorkspaceForAgent(t)
23+
agentClient := codersdk.New(client.URL)
24+
agentClient.SessionToken = agentToken
25+
agentCloser := agent.New(agent.Options{
26+
FetchMetadata: agentClient.WorkspaceAgentMetadata,
27+
WebRTCDialer: agentClient.ListenWorkspaceAgent,
28+
CoordinatorDialer: agentClient.ListenWorkspaceAgentTailnet,
29+
Logger: slogtest.Make(t, nil).Named("agent"),
30+
})
31+
defer agentCloser.Close()
32+
coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
33+
34+
cmd, root := clitest.New(t, "speedtest", workspace.Name)
35+
clitest.SetupConfig(t, client, root)
36+
pty := ptytest.New(t)
37+
cmd.SetOut(pty.Output())
38+
39+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
40+
defer cancel()
41+
cmdDone := tGo(t, func() {
42+
err := cmd.ExecuteContext(ctx)
43+
assert.NoError(t, err)
44+
})
45+
<-cmdDone
46+
}

cli/ssh_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import (
3131
"github.com/coder/coder/testutil"
3232
)
3333

34-
func setupWorkspaceForSSH(t *testing.T) (*codersdk.Client, codersdk.Workspace, string) {
34+
func setupWorkspaceForAgent(t *testing.T) (*codersdk.Client, codersdk.Workspace, string) {
3535
t.Helper()
3636
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
3737
user := coderdtest.CreateFirstUser(t, client)
@@ -69,7 +69,7 @@ func TestSSH(t *testing.T) {
6969
t.Run("ImmediateExit", func(t *testing.T) {
7070
t.Parallel()
7171

72-
client, workspace, agentToken := setupWorkspaceForSSH(t)
72+
client, workspace, agentToken := setupWorkspaceForAgent(t)
7373
cmd, root := clitest.New(t, "ssh", workspace.Name)
7474
clitest.SetupConfig(t, client, root)
7575
pty := ptytest.New(t)
@@ -103,7 +103,7 @@ func TestSSH(t *testing.T) {
103103
})
104104
t.Run("Stdio", func(t *testing.T) {
105105
t.Parallel()
106-
client, workspace, agentToken := setupWorkspaceForSSH(t)
106+
client, workspace, agentToken := setupWorkspaceForAgent(t)
107107
_, _ = tGoContext(t, func(ctx context.Context) {
108108
// Run this async so the SSH command has to wait for
109109
// the build and agent to connect!
@@ -173,7 +173,7 @@ func TestSSH(t *testing.T) {
173173

174174
t.Parallel()
175175

176-
client, workspace, agentToken := setupWorkspaceForSSH(t)
176+
client, workspace, agentToken := setupWorkspaceForAgent(t)
177177

178178
agentClient := codersdk.New(client.URL)
179179
agentClient.SessionToken = agentToken

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ require (
133133
golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167
134134
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
135135
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
136-
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2
136+
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094
137137
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
138138
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8
139139
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
@@ -143,15 +143,15 @@ require (
143143
golang.zx2c4.com/wireguard v0.0.0-20220703234212-c31a7b1ab478
144144
golang.zx2c4.com/wireguard/tun/netstack v0.0.0-00010101000000-000000000000
145145
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220504211119-3d4a969bb56b
146-
google.golang.org/api v0.90.0
146+
google.golang.org/api v0.94.0
147147
google.golang.org/protobuf v1.28.0
148148
gopkg.in/natefinch/lumberjack.v2 v2.0.0
149149
gopkg.in/yaml.v3 v3.0.1
150150
gvisor.dev/gvisor v0.0.0-20220801230058-850e42eb4444
151151
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
152152
nhooyr.io/websocket v1.8.7
153153
storj.io/drpc v0.0.33-0.20220622181519-9206537a4db7
154-
tailscale.com v1.26.2
154+
tailscale.com v1.30.0
155155
)
156156

157157
require (

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2170,8 +2170,8 @@ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j
21702170
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
21712171
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
21722172
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
2173-
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 h1:+jnHzr9VPj32ykQVai5DNahi9+NSp7yYuCsl5eAQtL0=
2174-
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
2173+
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8=
2174+
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
21752175
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
21762176
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
21772177
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -2563,8 +2563,8 @@ google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69
25632563
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
25642564
google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
25652565
google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
2566-
google.golang.org/api v0.90.0 h1:WMnUWAvihIClUYFNeFA69VTuR3duKS3IalMGDQcLvq8=
2567-
google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw=
2566+
google.golang.org/api v0.94.0 h1:KtKM9ru3nzQioV1HLlUf1cR7vMYJIpgls5VhAYQXIwA=
2567+
google.golang.org/api v0.94.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI=
25682568
google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
25692569
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
25702570
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=

0 commit comments

Comments
 (0)