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

chore: add vpn-daemon run subcommand for windows#15526

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
deansheather merged 2 commits intomainfromdean/vpn-daemon-windows
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletionscli/root.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -125,6 +125,7 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
r.expCmd(),
r.gitssh(),
r.support(),
r.vpnDaemon(),
r.vscodeSSH(),
r.workspaceAgent(),
}
Expand Down
21 changes: 21 additions & 0 deletionscli/vpndaemon.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
package cli

import (
"github.com/coder/serpent"
)

func (r *RootCmd) vpnDaemon() *serpent.Command {
cmd := &serpent.Command{
Use: "vpn-daemon [subcommand]",
Short: "VPN daemon commands used by Coder Desktop.",
Hidden: true,
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
Children: []*serpent.Command{
r.vpnDaemonRun(),
},
}

return cmd
}
24 changes: 24 additions & 0 deletionscli/vpndaemon_other.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
//go:build !windows

package cli

import (
"golang.org/x/xerrors"

"github.com/coder/serpent"
)

func (*RootCmd) vpnDaemonRun() *serpent.Command {
cmd := &serpent.Command{
Use: "run",
Short: "Run the VPN daemon on Windows.",
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
),
Handler: func(_ *serpent.Invocation) error {
return xerrors.New("vpn-daemon subcommand is not supported on this platform")
},
}

return cmd
}
75 changes: 75 additions & 0 deletionscli/vpndaemon_windows.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
//go:build windows

package cli

import (
"golang.org/x/xerrors"

"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
"github.com/coder/coder/v2/vpn"
"github.com/coder/serpent"
)

func (r *RootCmd) vpnDaemonRun() *serpent.Command {
var (
rpcReadHandleInt int64
rpcWriteHandleInt int64
)

cmd := &serpent.Command{
Use: "run",
Short: "Run the VPN daemon on Windows.",
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
),
Options: serpent.OptionSet{
{
Flag: "rpc-read-handle",
Env: "CODER_VPN_DAEMON_RPC_READ_HANDLE",
Description: "The handle for the pipe to read from the RPC connection.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Slightly ambiguous about which process's perspective. Is the daemon reading (and therefore the caller of this command is writing) or vice versa?

Value: serpent.Int64Of(&rpcReadHandleInt),
Required: true,
},
{
Flag: "rpc-write-handle",
Env: "CODER_VPN_DAEMON_RPC_WRITE_HANDLE",
Description: "The handle for the pipe to write to the RPC connection.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Ditto on ambiguity on which process is reading and which is writing

Value: serpent.Int64Of(&rpcWriteHandleInt),
Required: true,
},
},
Handler: func(inv *serpent.Invocation) error {
ctx := inv.Context()
logger := inv.Logger.AppendSinks(sloghuman.Sink(inv.Stderr)).Leveled(slog.LevelDebug)

if rpcReadHandleInt < 0 || rpcWriteHandleInt < 0 {
return xerrors.Errorf("rpc-read-handle (%v) and rpc-write-handle (%v) must be positive", rpcReadHandleInt, rpcWriteHandleInt)
}
if rpcReadHandleInt == rpcWriteHandleInt {
return xerrors.Errorf("rpc-read-handle (%v) and rpc-write-handle (%v) must be different", rpcReadHandleInt, rpcWriteHandleInt)
}

// We don't need to worry about duplicating the handles on Windows,
// which is different from Unix.
logger.Info(ctx, "opening bidirectional RPC pipe", slog.F("rpc_read_handle", rpcReadHandleInt), slog.F("rpc_write_handle", rpcWriteHandleInt))
pipe, err := vpn.NewBidirectionalPipe(uintptr(rpcReadHandleInt), uintptr(rpcWriteHandleInt))
if err != nil {
return xerrors.Errorf("create bidirectional RPC pipe: %w", err)
}
defer pipe.Close()

logger.Info(ctx, "starting tunnel")
tunnel, err := vpn.NewTunnel(ctx, logger, pipe)
if err != nil {
return xerrors.Errorf("create new tunnel for client: %w", err)
}
defer tunnel.Close()

<-ctx.Done()
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I don't think this will actually work in practice, should we add atunnel.Wait() or something instead to wait for the *vpn.Tunnel to encounter a permanent error?

ethanndickson reacted with eyes emoji
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The way a clean shutdown should work is that the manager sends StopRequest, the tunnel replies StopResponse and closes the tunnel → manager pipe. We could also add atunnel.Wait() that returns a channel that closes when the tunnel is fully shut down like this, so that we can also have the daemon process exit.

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I will add atunnel.Wait() in a follow-up PR tomorrow since tunnel is mostly unimplemented anyways

spikecurtis reacted with thumbs up emoji
return nil
},
}

return cmd
}
93 changes: 93 additions & 0 deletionscli/vpndaemon_windows_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
//go:build windows

package cli_test

import (
"fmt"
"os"
"testing"

"github.com/stretchr/testify/require"

"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/testutil"
)

func TestVPNDaemonRun(t *testing.T) {
t.Parallel()

t.Run("InvalidFlags", func(t *testing.T) {
t.Parallel()

cases := []struct {
Name string
Args []string
ErrorContains string
}{
{
Name: "NoReadHandle",
Args: []string{"--rpc-write-handle", "10"},
ErrorContains: "rpc-read-handle",
},
{
Name: "NoWriteHandle",
Args: []string{"--rpc-read-handle", "10"},
ErrorContains: "rpc-write-handle",
},
{
Name: "NegativeReadHandle",
Args: []string{"--rpc-read-handle", "-1", "--rpc-write-handle", "10"},
ErrorContains: "rpc-read-handle",
},
{
Name: "NegativeWriteHandle",
Args: []string{"--rpc-read-handle", "10", "--rpc-write-handle", "-1"},
ErrorContains: "rpc-write-handle",
},
{
Name: "SameHandles",
Args: []string{"--rpc-read-handle", "10", "--rpc-write-handle", "10"},
ErrorContains: "rpc-read-handle",
},
}

for _, c := range cases {
c := c
t.Run(c.Name, func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
inv, _ := clitest.New(t, append([]string{"vpn-daemon", "run"}, c.Args...)...)
err := inv.WithContext(ctx).Run()
require.ErrorContains(t, err, c.ErrorContains)
})
}
})

t.Run("StartsTunnel", func(t *testing.T) {
t.Parallel()

r1, w1, err := os.Pipe()
require.NoError(t, err)
defer r1.Close()
defer w1.Close()
r2, w2, err := os.Pipe()
require.NoError(t, err)
defer r2.Close()
defer w2.Close()

ctx := testutil.Context(t, testutil.WaitLong)
inv, _ := clitest.New(t, "vpn-daemon", "run", "--rpc-read-handle", fmt.Sprint(r1.Fd()), "--rpc-write-handle", fmt.Sprint(w2.Fd()))
waiter := clitest.StartWithWaiter(t, inv.WithContext(ctx))

// Send garbage which should cause the handshake to fail and the daemon
// to exit.
_, err = w1.Write([]byte("garbage"))
require.NoError(t, err)
waiter.Cancel()
err = waiter.Wait()
require.ErrorContains(t, err, "handshake failed")
})

// TODO: once the VPN tunnel functionality is implemented, add tests that
// actually try to instantiate a tunnel to a workspace
}
69 changes: 69 additions & 0 deletionsvpn/pipe.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
package vpn

import (
"io"
"os"

"github.com/hashicorp/go-multierror"
"golang.org/x/xerrors"
)

// BidirectionalPipe combines a pair of files that can be used for bidirectional
// communication.
type BidirectionalPipe struct {
read *os.File
write *os.File
}

var _ io.ReadWriteCloser = BidirectionalPipe{}

// NewBidirectionalPipe creates a new BidirectionalPipe from the given file
// descriptors.
func NewBidirectionalPipe(readFd, writeFd uintptr) (BidirectionalPipe, error) {
read := os.NewFile(readFd, "pipe_read")
_, err := read.Stat()
if err != nil {
return BidirectionalPipe{}, xerrors.Errorf("stat pipe_read (fd=%v): %w", readFd, err)
}
write := os.NewFile(writeFd, "pipe_write")
_, err = write.Stat()
if err != nil {
return BidirectionalPipe{}, xerrors.Errorf("stat pipe_write (fd=%v): %w", writeFd, err)
}
return BidirectionalPipe{
read: read,
write: write,
}, nil
}

// Read implements io.Reader. Data is read from the read pipe.
func (b BidirectionalPipe) Read(p []byte) (int, error) {
n, err := b.read.Read(p)
if err != nil {
return n, xerrors.Errorf("read from pipe_read (fd=%v): %w", b.read.Fd(), err)
}
return n, nil
}

// Write implements io.Writer. Data is written to the write pipe.
func (b BidirectionalPipe) Write(p []byte) (n int, err error) {
n, err = b.write.Write(p)
if err != nil {
return n, xerrors.Errorf("write to pipe_write (fd=%v): %w", b.write.Fd(), err)
}
return n, nil
}

// Close implements io.Closer. Both the read and write pipes are closed.
func (b BidirectionalPipe) Close() error {
var err error
rErr := b.read.Close()
if rErr != nil {
err = multierror.Append(err, xerrors.Errorf("close pipe_read (fd=%v): %w", b.read.Fd(), rErr))
}
wErr := b.write.Close()
if err != nil {
err = multierror.Append(err, xerrors.Errorf("close pipe_write (fd=%v): %w", b.write.Fd(), wErr))
}
return err
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp