Skip to content

Commit

Permalink
[v17] vnet: windows service stub (#51197)
Browse files Browse the repository at this point in the history
* [vnet] add windows tsh cli commands (#50935)

* [vnet] add windows tsh cli commands

This PR refactors the VNet tsh CLI commands and adds stubs for the
commands that are going to be added for Windows VNet support.

* fix linux build

* update copyright year

* use context.AfterFunc

* fix typo

* [vnet] windows service stub (#50468)
  • Loading branch information
nklaassen authored Jan 20, 2025
1 parent a712958 commit c5d4370
Show file tree
Hide file tree
Showing 24 changed files with 585 additions and 459 deletions.
2 changes: 1 addition & 1 deletion lib/teleterm/vnet/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ func (s *Service) Start(ctx context.Context, req *api.StartRequest) (*api.StartR
}

s.clusterConfigCache = vnet.NewClusterConfigCache(s.cfg.Clock)
processManager, err := vnet.Run(ctx, &vnet.RunConfig{
processManager, err := vnet.RunUserProcess(ctx, &vnet.UserProcessConfig{
AppProvider: appProvider,
ClusterConfigCache: s.clusterConfigCache,
})
Expand Down
25 changes: 12 additions & 13 deletions lib/vnet/admin_process.go → lib/vnet/admin_process_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,17 @@ import (
"github.com/gravitational/teleport/lib/vnet/daemon"
)

// RunAdminProcess must run as root. It creates and sets up a TUN device and passes
// the file descriptor for that device over the unix socket found at config.socketPath.
// RunDarwinAdminProcess must run as root. It creates and sets up a TUN device
// and passes the file descriptor for that device over the unix socket found at
// config.socketPath.
//
// It also handles host OS configuration that must run as root, and stays alive to keep the host configuration
// up to date. It will stay running until the socket at config.socketPath is deleted or until encountering an
// unrecoverable error.
//
// OS configuration is updated every [osConfigurationInterval]. During the update, it temporarily
// changes egid and euid of the process to that of the client connecting to the daemon.
func RunAdminProcess(ctx context.Context, config daemon.Config) error {
// It also handles host OS configuration that must run as root, and stays alive
// to keep the host configuration up to date. It will stay running until the
// socket at config.socketPath is deleted, ctx is canceled, or until
// encountering an unrecoverable error.
func RunDarwinAdminProcess(ctx context.Context, config daemon.Config) error {
if err := config.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
return trace.Wrap(err, "checking daemon process config")
}

ctx, cancel := context.WithCancel(ctx)
Expand Down Expand Up @@ -74,7 +73,7 @@ func RunAdminProcess(ctx context.Context, config daemon.Config) error {
}

// createAndSendTUNDevice creates a virtual network TUN device and sends the open file descriptor on
// [socketPath]. It returns the name of the TUN device or an error.
// socketPath. It returns the name of the TUN device or an error.
func createAndSendTUNDevice(ctx context.Context, socketPath string) (string, error) {
tun, tunName, err := createTUNDevice(ctx)
if err != nil {
Expand Down Expand Up @@ -107,7 +106,7 @@ func createTUNDevice(ctx context.Context) (tun.Device, string, error) {
return dev, name, nil
}

// osConfigurationLoop will keep running until [ctx] is canceled or an unrecoverable error is encountered, in
// osConfigurationLoop will keep running until ctx is canceled or an unrecoverable error is encountered, in
// order to keep the host OS configuration up to date.
func osConfigurationLoop(ctx context.Context, tunName, ipv6Prefix, dnsAddr, homePath string, clientCred daemon.ClientCred) error {
osConfigurator, err := newOSConfigurator(tunName, ipv6Prefix, dnsAddr, homePath, clientCred)
Expand All @@ -128,7 +127,7 @@ func osConfigurationLoop(ctx context.Context, tunName, ipv6Prefix, dnsAddr, home
}

defer func() {
// Shutting down, deconfigure OS. Pass context.Background because [ctx] has likely been canceled
// Shutting down, deconfigure OS. Pass context.Background because ctx has likely been canceled
// already but we still need to clean up.
if err := osConfigurator.deconfigureOS(context.Background()); err != nil {
log.ErrorContext(ctx, "Error deconfiguring host OS before shutting down.", "error", err)
Expand Down
40 changes: 20 additions & 20 deletions lib/vnet/socket_other.go → lib/vnet/admin_process_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,32 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

//go:build !darwin && !windows
// +build !darwin,!windows

package vnet

import (
"os"
"context"

"github.com/gravitational/trace"
"golang.zx2c4.com/wireguard/tun"
)

func createSocket() (*noSocket, string, error) {
return nil, "", trace.Wrap(ErrVnetNotImplemented)
}

func sendTUNNameAndFd(socketPath, tunName string, tunFile *os.File) error {
return trace.Wrap(ErrVnetNotImplemented)
}

func receiveTUNDevice(_ *noSocket) (tun.Device, error) {
return nil, trace.Wrap(ErrVnetNotImplemented)
}

type noSocket struct{}

func (_ noSocket) Close() error {
return trace.Wrap(ErrVnetNotImplemented)
// runWindowsAdminProcess must run as administrator. It creates and sets up a TUN
// device, runs the VNet networking stack, and handles OS configuration. It will
// continue to run until [ctx] is canceled or encountering an unrecoverable
// error.
func runWindowsAdminProcess(ctx context.Context) error {
device, err := tun.CreateTUN("TeleportVNet", mtu)
if err != nil {
return trace.Wrap(err, "creating TUN device")
}
defer device.Close()
tunName, err := device.Name()
if err != nil {
return trace.Wrap(err, "getting TUN device name")
}
log.InfoContext(ctx, "Created TUN interface", "tun", tunName)
// TODO(nklaassen): actually run VNet. For now, just stay alive until the
// context is canceled.
<-ctx.Done()
return trace.Wrap(ctx.Err())
}
2 changes: 1 addition & 1 deletion lib/vnet/escalate_daemon_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ func execAdminProcess(ctx context.Context, config daemon.Config) error {

// DaemonSubcommand runs the VNet daemon process.
func DaemonSubcommand(ctx context.Context) error {
return trace.Wrap(daemon.Start(ctx, RunAdminProcess))
return trace.Wrap(daemon.Start(ctx, RunDarwinAdminProcess))
}
40 changes: 0 additions & 40 deletions lib/vnet/escalate_other.go

This file was deleted.

40 changes: 0 additions & 40 deletions lib/vnet/escalate_windows.go

This file was deleted.

3 changes: 3 additions & 0 deletions lib/vnet/network_stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,12 @@ import (
"gvisor.dev/gvisor/pkg/waiter"

"github.com/gravitational/teleport"
logutils "github.com/gravitational/teleport/lib/utils/log"
"github.com/gravitational/teleport/lib/vnet/dns"
)

var log = logutils.NewPackageLogger(teleport.ComponentKey, "vnet")

const (
nicID = 1
mtu = 1500
Expand Down
5 changes: 5 additions & 0 deletions lib/vnet/osconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

// TODO(nklaassen): refactor OS configuration so this file isn't
// platform-specific.
//go:build darwin
// +build darwin

package vnet

import (
Expand Down
36 changes: 0 additions & 36 deletions lib/vnet/osconfig_windows.go

This file was deleted.

90 changes: 90 additions & 0 deletions lib/vnet/process_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Teleport
// Copyright (C) 2024 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package vnet

import (
"context"
"errors"
"fmt"
"sync"

"github.com/gravitational/trace"
"golang.org/x/sync/errgroup"
)

func newProcessManager() (*ProcessManager, context.Context) {
ctx, cancel := context.WithCancel(context.Background())
g, ctx := errgroup.WithContext(ctx)
pm := &ProcessManager{
g: g,
cancel: cancel,
closed: make(chan struct{}),
}
pm.closeOnce = sync.OnceFunc(func() {
close(pm.closed)
})
return pm, ctx
}

// ProcessManager handles background tasks needed to run VNet.
// Its semantics are similar to an error group with a context, but it cancels the context whenever
// any task returns prematurely, that is, a task exits while the context was not canceled.
type ProcessManager struct {
g *errgroup.Group
cancel context.CancelFunc
closed chan struct{}
closeOnce func()
}

// AddCriticalBackgroundTask adds a function to the error group. [task] is expected to block until
// the context returned by [newProcessManager] gets canceled. The context gets canceled either by
// calling Close on [ProcessManager] or if any task returns.
func (pm *ProcessManager) AddCriticalBackgroundTask(name string, task func() error) {
pm.g.Go(func() error {
err := task()
if err == nil {
// Make sure to always return an error so that the errgroup context is canceled.
err = fmt.Errorf("critical task %q exited prematurely", name)
}
return trace.Wrap(err)
})
}

// Wait blocks and waits for the background tasks to finish, which typically happens when another
// goroutine calls Close on the process manager.
func (pm *ProcessManager) Wait() error {
err := pm.g.Wait()
select {
case <-pm.closed:
// Errors are expected after the process manager has been closed,
// usually due to context cancellation, but other error types may be
// returned. Log unexpected errors at debug level but return nil.
if err != nil && !errors.Is(err, context.Canceled) {
log.DebugContext(context.Background(), "ProcessManager exited with error after being closed", "error", err)
}
return nil
default:
return trace.Wrap(err)
}
}

// Close stops any active background tasks by canceling the underlying context,
// and waits for all tasks to terminate.
func (pm *ProcessManager) Close() {
pm.closeOnce()
pm.cancel()
}
4 changes: 1 addition & 3 deletions lib/vnet/process_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@ func TestProcessManager_Close(t *testing.T) {
})

pm.Close()

err := pm.Wait()
require.ErrorIs(t, err, context.Canceled)
require.ErrorIs(t, err, context.Cause(pmCtx))
require.NoError(t, err)
}
Loading

0 comments on commit c5d4370

Please sign in to comment.