Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd63bd21

Browse files
authored
chore: add vpn-daemon run subcommand for windows (#15526)
`coder vpn-daemon run` will instantiate a RPC connection with thespecified pipe handles and communicate with the (yet to be implemented)parent process.The tests don't ensure that the tunnel is actually usable yet as thetunnel functionality isn't implemented, but it does make sure that thetunnel tries to read from the RPC pipe.Closes#14735
1 parent8ca8e01 commitd63bd21

File tree

6 files changed

+283
-0
lines changed

6 files changed

+283
-0
lines changed

‎cli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
125125
r.expCmd(),
126126
r.gitssh(),
127127
r.support(),
128+
r.vpnDaemon(),
128129
r.vscodeSSH(),
129130
r.workspaceAgent(),
130131
}

‎cli/vpndaemon.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package cli
2+
3+
import (
4+
"github.com/coder/serpent"
5+
)
6+
7+
func (r*RootCmd)vpnDaemon()*serpent.Command {
8+
cmd:=&serpent.Command{
9+
Use:"vpn-daemon [subcommand]",
10+
Short:"VPN daemon commands used by Coder Desktop.",
11+
Hidden:true,
12+
Handler:func(inv*serpent.Invocation)error {
13+
returninv.Command.HelpHandler(inv)
14+
},
15+
Children: []*serpent.Command{
16+
r.vpnDaemonRun(),
17+
},
18+
}
19+
20+
returncmd
21+
}

‎cli/vpndaemon_other.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//go:build !windows
2+
3+
package cli
4+
5+
import (
6+
"golang.org/x/xerrors"
7+
8+
"github.com/coder/serpent"
9+
)
10+
11+
func (*RootCmd)vpnDaemonRun()*serpent.Command {
12+
cmd:=&serpent.Command{
13+
Use:"run",
14+
Short:"Run the VPN daemon on Windows.",
15+
Middleware:serpent.Chain(
16+
serpent.RequireNArgs(0),
17+
),
18+
Handler:func(_*serpent.Invocation)error {
19+
returnxerrors.New("vpn-daemon subcommand is not supported on this platform")
20+
},
21+
}
22+
23+
returncmd
24+
}

‎cli/vpndaemon_windows.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//go:build windows
2+
3+
package cli
4+
5+
import (
6+
"golang.org/x/xerrors"
7+
8+
"cdr.dev/slog"
9+
"cdr.dev/slog/sloggers/sloghuman"
10+
"github.com/coder/coder/v2/vpn"
11+
"github.com/coder/serpent"
12+
)
13+
14+
func (r*RootCmd)vpnDaemonRun()*serpent.Command {
15+
var (
16+
rpcReadHandleIntint64
17+
rpcWriteHandleIntint64
18+
)
19+
20+
cmd:=&serpent.Command{
21+
Use:"run",
22+
Short:"Run the VPN daemon on Windows.",
23+
Middleware:serpent.Chain(
24+
serpent.RequireNArgs(0),
25+
),
26+
Options: serpent.OptionSet{
27+
{
28+
Flag:"rpc-read-handle",
29+
Env:"CODER_VPN_DAEMON_RPC_READ_HANDLE",
30+
Description:"The handle for the pipe to read from the RPC connection.",
31+
Value:serpent.Int64Of(&rpcReadHandleInt),
32+
Required:true,
33+
},
34+
{
35+
Flag:"rpc-write-handle",
36+
Env:"CODER_VPN_DAEMON_RPC_WRITE_HANDLE",
37+
Description:"The handle for the pipe to write to the RPC connection.",
38+
Value:serpent.Int64Of(&rpcWriteHandleInt),
39+
Required:true,
40+
},
41+
},
42+
Handler:func(inv*serpent.Invocation)error {
43+
ctx:=inv.Context()
44+
logger:=inv.Logger.AppendSinks(sloghuman.Sink(inv.Stderr)).Leveled(slog.LevelDebug)
45+
46+
ifrpcReadHandleInt<0||rpcWriteHandleInt<0 {
47+
returnxerrors.Errorf("rpc-read-handle (%v) and rpc-write-handle (%v) must be positive",rpcReadHandleInt,rpcWriteHandleInt)
48+
}
49+
ifrpcReadHandleInt==rpcWriteHandleInt {
50+
returnxerrors.Errorf("rpc-read-handle (%v) and rpc-write-handle (%v) must be different",rpcReadHandleInt,rpcWriteHandleInt)
51+
}
52+
53+
// We don't need to worry about duplicating the handles on Windows,
54+
// which is different from Unix.
55+
logger.Info(ctx,"opening bidirectional RPC pipe",slog.F("rpc_read_handle",rpcReadHandleInt),slog.F("rpc_write_handle",rpcWriteHandleInt))
56+
pipe,err:=vpn.NewBidirectionalPipe(uintptr(rpcReadHandleInt),uintptr(rpcWriteHandleInt))
57+
iferr!=nil {
58+
returnxerrors.Errorf("create bidirectional RPC pipe: %w",err)
59+
}
60+
deferpipe.Close()
61+
62+
logger.Info(ctx,"starting tunnel")
63+
tunnel,err:=vpn.NewTunnel(ctx,logger,pipe)
64+
iferr!=nil {
65+
returnxerrors.Errorf("create new tunnel for client: %w",err)
66+
}
67+
defertunnel.Close()
68+
69+
<-ctx.Done()
70+
returnnil
71+
},
72+
}
73+
74+
returncmd
75+
}

‎cli/vpndaemon_windows_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//go:build windows
2+
3+
package cli_test
4+
5+
import (
6+
"fmt"
7+
"os"
8+
"testing"
9+
10+
"github.com/stretchr/testify/require"
11+
12+
"github.com/coder/coder/v2/cli/clitest"
13+
"github.com/coder/coder/v2/testutil"
14+
)
15+
16+
funcTestVPNDaemonRun(t*testing.T) {
17+
t.Parallel()
18+
19+
t.Run("InvalidFlags",func(t*testing.T) {
20+
t.Parallel()
21+
22+
cases:= []struct {
23+
Namestring
24+
Args []string
25+
ErrorContainsstring
26+
}{
27+
{
28+
Name:"NoReadHandle",
29+
Args: []string{"--rpc-write-handle","10"},
30+
ErrorContains:"rpc-read-handle",
31+
},
32+
{
33+
Name:"NoWriteHandle",
34+
Args: []string{"--rpc-read-handle","10"},
35+
ErrorContains:"rpc-write-handle",
36+
},
37+
{
38+
Name:"NegativeReadHandle",
39+
Args: []string{"--rpc-read-handle","-1","--rpc-write-handle","10"},
40+
ErrorContains:"rpc-read-handle",
41+
},
42+
{
43+
Name:"NegativeWriteHandle",
44+
Args: []string{"--rpc-read-handle","10","--rpc-write-handle","-1"},
45+
ErrorContains:"rpc-write-handle",
46+
},
47+
{
48+
Name:"SameHandles",
49+
Args: []string{"--rpc-read-handle","10","--rpc-write-handle","10"},
50+
ErrorContains:"rpc-read-handle",
51+
},
52+
}
53+
54+
for_,c:=rangecases {
55+
c:=c
56+
t.Run(c.Name,func(t*testing.T) {
57+
t.Parallel()
58+
ctx:=testutil.Context(t,testutil.WaitLong)
59+
inv,_:=clitest.New(t,append([]string{"vpn-daemon","run"},c.Args...)...)
60+
err:=inv.WithContext(ctx).Run()
61+
require.ErrorContains(t,err,c.ErrorContains)
62+
})
63+
}
64+
})
65+
66+
t.Run("StartsTunnel",func(t*testing.T) {
67+
t.Parallel()
68+
69+
r1,w1,err:=os.Pipe()
70+
require.NoError(t,err)
71+
deferr1.Close()
72+
deferw1.Close()
73+
r2,w2,err:=os.Pipe()
74+
require.NoError(t,err)
75+
deferr2.Close()
76+
deferw2.Close()
77+
78+
ctx:=testutil.Context(t,testutil.WaitLong)
79+
inv,_:=clitest.New(t,"vpn-daemon","run","--rpc-read-handle",fmt.Sprint(r1.Fd()),"--rpc-write-handle",fmt.Sprint(w2.Fd()))
80+
waiter:=clitest.StartWithWaiter(t,inv.WithContext(ctx))
81+
82+
// Send garbage which should cause the handshake to fail and the daemon
83+
// to exit.
84+
_,err=w1.Write([]byte("garbage"))
85+
require.NoError(t,err)
86+
waiter.Cancel()
87+
err=waiter.Wait()
88+
require.ErrorContains(t,err,"handshake failed")
89+
})
90+
91+
// TODO: once the VPN tunnel functionality is implemented, add tests that
92+
// actually try to instantiate a tunnel to a workspace
93+
}

‎vpn/pipe.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package vpn
2+
3+
import (
4+
"io"
5+
"os"
6+
7+
"github.com/hashicorp/go-multierror"
8+
"golang.org/x/xerrors"
9+
)
10+
11+
// BidirectionalPipe combines a pair of files that can be used for bidirectional
12+
// communication.
13+
typeBidirectionalPipestruct {
14+
read*os.File
15+
write*os.File
16+
}
17+
18+
var_ io.ReadWriteCloser=BidirectionalPipe{}
19+
20+
// NewBidirectionalPipe creates a new BidirectionalPipe from the given file
21+
// descriptors.
22+
funcNewBidirectionalPipe(readFd,writeFduintptr) (BidirectionalPipe,error) {
23+
read:=os.NewFile(readFd,"pipe_read")
24+
_,err:=read.Stat()
25+
iferr!=nil {
26+
returnBidirectionalPipe{},xerrors.Errorf("stat pipe_read (fd=%v): %w",readFd,err)
27+
}
28+
write:=os.NewFile(writeFd,"pipe_write")
29+
_,err=write.Stat()
30+
iferr!=nil {
31+
returnBidirectionalPipe{},xerrors.Errorf("stat pipe_write (fd=%v): %w",writeFd,err)
32+
}
33+
returnBidirectionalPipe{
34+
read:read,
35+
write:write,
36+
},nil
37+
}
38+
39+
// Read implements io.Reader. Data is read from the read pipe.
40+
func (bBidirectionalPipe)Read(p []byte) (int,error) {
41+
n,err:=b.read.Read(p)
42+
iferr!=nil {
43+
returnn,xerrors.Errorf("read from pipe_read (fd=%v): %w",b.read.Fd(),err)
44+
}
45+
returnn,nil
46+
}
47+
48+
// Write implements io.Writer. Data is written to the write pipe.
49+
func (bBidirectionalPipe)Write(p []byte) (nint,errerror) {
50+
n,err=b.write.Write(p)
51+
iferr!=nil {
52+
returnn,xerrors.Errorf("write to pipe_write (fd=%v): %w",b.write.Fd(),err)
53+
}
54+
returnn,nil
55+
}
56+
57+
// Close implements io.Closer. Both the read and write pipes are closed.
58+
func (bBidirectionalPipe)Close()error {
59+
varerrerror
60+
rErr:=b.read.Close()
61+
ifrErr!=nil {
62+
err=multierror.Append(err,xerrors.Errorf("close pipe_read (fd=%v): %w",b.read.Fd(),rErr))
63+
}
64+
wErr:=b.write.Close()
65+
iferr!=nil {
66+
err=multierror.Append(err,xerrors.Errorf("close pipe_write (fd=%v): %w",b.write.Fd(),wErr))
67+
}
68+
returnerr
69+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp