Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
64 changes: 40 additions & 24 deletions cmd/gvproxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"slices"
"strings"

"github.com/containers/gvisor-tap-vsock/pkg/notification"
"github.com/containers/gvisor-tap-vsock/pkg/types"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v3"
Expand All @@ -26,25 +27,26 @@ const (
)

type GvproxyArgs struct {
config string
endpoints arrayFlags
debug bool
mtu int
sshPort int
vpnkitSocket string
qemuSocket string
bessSocket string
stdioSocket string
vfkitSocket string
forwardSocket arrayFlags
forwardDest arrayFlags
forwardUser arrayFlags
forwardIdentify arrayFlags
pidFile string
pcapFile string
logFile string
servicesEndpoint string
ec2MetadataAccess bool
config string
endpoints arrayFlags
debug bool
mtu int
sshPort int
vpnkitSocket string
qemuSocket string
bessSocket string
stdioSocket string
vfkitSocket string
notificationSocket string
forwardSocket arrayFlags
forwardDest arrayFlags
forwardUser arrayFlags
forwardIdentify arrayFlags
pidFile string
pcapFile string
logFile string
servicesEndpoint string
ec2MetadataAccess bool
}

type GvproxyConfig struct {
Expand All @@ -58,11 +60,13 @@ type GvproxyConfig struct {
Stdio string `yaml:"stdio,omitempty"`
Vfkit string `yaml:"vfkit,omitempty"`
} `yaml:"interfaces,omitempty"`
Forwards []GvproxyConfigForward `yaml:"forwards,omitempty"`
PIDFile string `yaml:"pid-file,omitempty"`
LogFile string `yaml:"log-file,omitempty"`
Services string `yaml:"services,omitempty"`
Ec2MetadataAccess bool `yaml:"ec2-metadata-access,omitempty"`
Forwards []GvproxyConfigForward `yaml:"forwards,omitempty"`
PIDFile string `yaml:"pid-file,omitempty"`
LogFile string `yaml:"log-file,omitempty"`
Services string `yaml:"services,omitempty"`
Ec2MetadataAccess bool `yaml:"ec2-metadata-access,omitempty"`
NotificationSocket string `yaml:"notification,omitempty"`
NotificationSender *notification.NotificationSender `yaml:"-"`
}

type GvproxyConfigForward struct {
Expand Down Expand Up @@ -130,6 +134,7 @@ func GvproxyArgParse(flagSet *flag.FlagSet, args *GvproxyArgs, argv []string) (*
flagSet.StringVar(&args.logFile, "log-file", "", "Output log messages (logrus) to a given file path")
flagSet.StringVar(&args.servicesEndpoint, "services", "", "Exposes the same HTTP API as the --listen flag, without the /connect endpoint")
flagSet.BoolVar(&args.ec2MetadataAccess, "ec2-metadata-access", false, "Permits access to EC2 Metadata Service (TCP only)")
flagSet.StringVar(&args.notificationSocket, "notification", "", "Socket to be used to send network-ready notifications")
if err := flagSet.Parse(argv); err != nil {
return nil, err
}
Expand Down Expand Up @@ -237,6 +242,17 @@ func GvproxyConfigure(config *GvproxyConfig, args *GvproxyArgs, version string)
if args.pidFile != "" {
config.PIDFile = args.pidFile
}
if args.notificationSocket != "" {
log.Debugf("notification socket: %s", args.notificationSocket)
uri, err := url.Parse(args.notificationSocket)
if err != nil {
return config, fmt.Errorf("invalid value for notification listen address: %w", err)
}
if uri.Scheme != "unix" {
return config, errors.New("notification listen address must be unix:// address")
}
config.NotificationSocket = uri.Path
}
if len(args.endpoints) > 0 {
config.Listen = args.endpoints
}
Expand Down
27 changes: 26 additions & 1 deletion cmd/gvproxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ import (
"time"

"github.com/containers/gvisor-tap-vsock/pkg/net/stdio"
"github.com/containers/gvisor-tap-vsock/pkg/notification"
"github.com/containers/gvisor-tap-vsock/pkg/sshclient"
"github.com/containers/gvisor-tap-vsock/pkg/transport"
"github.com/containers/gvisor-tap-vsock/pkg/types"
"github.com/containers/gvisor-tap-vsock/pkg/virtualnetwork"
"github.com/containers/winquit/pkg/winquit"
humanize "github.com/dustin/go-humanize"
Expand Down Expand Up @@ -126,6 +128,15 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
}
log.Info("waiting for clients...")

// Start the notification sender in a goroutine
notificationSender := notification.NewNotificationSender(config.NotificationSocket)
Copy link
Member Author

Choose a reason for hiding this comment

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

Initializing notificationSender here because NewNotificationSender always returns a valid object (a no-op sender when socket is empty), removing the need for nil checks at every Send() call.

if config.NotificationSocket != "" {
g.Go(func() error {
notificationSender.Start(ctx)
return nil
})
}
vn.SetNotificationSender(notificationSender)
for _, endpoint := range config.Listen {
log.Infof("listening %s", endpoint)
ln, err := transport.Listen(endpoint)
Expand All @@ -134,6 +145,7 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
}
httpServe(ctx, g, ln, withProfiler(vn))
}
notificationSender.Send(types.NotificationMessage{NotificationType: types.Ready})

if config.Services != "" {
log.Infof("enabling services API. Listening %s", config.Services)
Expand Down Expand Up @@ -172,6 +184,7 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
if config.Interfaces.VPNKit != "" {
vpnkitListener, err := transport.Listen(config.Interfaces.VPNKit)
if err != nil {
notificationSender.Send(types.NotificationMessage{NotificationType: types.HypervisorError})
return fmt.Errorf("vpnkit listen error: %w", err)
}
g.Go(func() error {
Expand All @@ -185,6 +198,7 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
}
conn, err := vpnkitListener.Accept()
if err != nil {
notificationSender.Send(types.NotificationMessage{NotificationType: types.HypervisorError})
log.Errorf("vpnkit accept error: %s", err)
continue
}
Expand All @@ -199,6 +213,7 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
if config.Interfaces.Qemu != "" {
qemuListener, err := transport.Listen(config.Interfaces.Qemu)
if err != nil {
notificationSender.Send(types.NotificationMessage{NotificationType: types.HypervisorError})
return fmt.Errorf("qemu listen error: %w", err)
}

Expand All @@ -213,6 +228,7 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
g.Go(func() error {
conn, err := qemuListener.Accept()
if err != nil {
notificationSender.Send(types.NotificationMessage{NotificationType: types.HypervisorError})
return fmt.Errorf("qemu accept error: %w", err)
}
return vn.AcceptQemu(ctx, conn)
Expand All @@ -222,6 +238,7 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
if config.Interfaces.Bess != "" {
bessListener, err := transport.Listen(config.Interfaces.Bess)
if err != nil {
notificationSender.Send(types.NotificationMessage{NotificationType: types.HypervisorError})
return fmt.Errorf("bess listen error: %w", err)
}

Expand All @@ -236,6 +253,7 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
g.Go(func() error {
conn, err := bessListener.Accept()
if err != nil {
notificationSender.Send(types.NotificationMessage{NotificationType: types.HypervisorError})
return fmt.Errorf("bess accept error: %w", err)
}
return vn.AcceptBess(ctx, conn)
Expand All @@ -245,6 +263,7 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
if config.Interfaces.Vfkit != "" {
conn, err := transport.ListenUnixgram(config.Interfaces.Vfkit)
if err != nil {
notificationSender.Send(types.NotificationMessage{NotificationType: types.HypervisorError})
return fmt.Errorf("vfkit listen error: %w", err)
}

Expand All @@ -260,16 +279,22 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
g.Go(func() error {
vfkitConn, err := transport.AcceptVfkit(conn)
if err != nil {
notificationSender.Send(types.NotificationMessage{NotificationType: types.HypervisorError})
return fmt.Errorf("vfkit accept error: %w", err)
}

return vn.AcceptVfkit(ctx, vfkitConn)
})
}

if config.Interfaces.Stdio != "" {
g.Go(func() error {
conn := stdio.GetStdioConn()
return vn.AcceptStdio(ctx, conn)
err := vn.AcceptStdio(ctx, conn)
if err != nil {
notificationSender.Send(types.NotificationMessage{NotificationType: types.HypervisorError})
}
return err
})
}

Expand Down
75 changes: 75 additions & 0 deletions pkg/notification/sender.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package notification

import (
"context"
"encoding/json"
"fmt"
"net"

"github.com/containers/gvisor-tap-vsock/pkg/types"
log "github.com/sirupsen/logrus"
)

type NotificationSender struct {
notificationCh chan types.NotificationMessage
socket string
}

func NewNotificationSender(socket string) *NotificationSender {
if socket == "" {
return &NotificationSender{
socket: "",
notificationCh: nil,
}
}

return &NotificationSender{
socket: socket,
notificationCh: make(chan types.NotificationMessage, 100),
}
}

func (s *NotificationSender) Send(notification types.NotificationMessage) {
if s.notificationCh == nil {
return
}
select {
case s.notificationCh <- notification:
default:
log.Warn("unable to send notification")
}
}

func (s *NotificationSender) Start(ctx context.Context) {
if s.notificationCh == nil {
return
}

for {
select {
case <-ctx.Done():
return
case notification := <-s.notificationCh:
if err := s.sendToSocket(notification); err != nil {
log.Errorf("failed to send notification: %v", err)
continue
}
}
}
}

func (s *NotificationSender) sendToSocket(notification types.NotificationMessage) error {
if s.socket == "" {
return nil
}
conn, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: s.socket, Net: "unix"})
if err != nil {
return fmt.Errorf("cannot dial notification socket: %w", err)
}
defer conn.Close()
enc := json.NewEncoder(conn)
if err := enc.Encode(notification); err != nil {
return fmt.Errorf("failed to encode notification: %w", err)
}
return nil
}
21 changes: 21 additions & 0 deletions pkg/tap/switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"sync/atomic"
"syscall"

"github.com/containers/gvisor-tap-vsock/pkg/notification"
"github.com/containers/gvisor-tap-vsock/pkg/types"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
Expand Down Expand Up @@ -48,6 +49,8 @@ type Switch struct {
writeLock sync.Mutex

gateway VirtualDevice

notificationSender *notification.NotificationSender
}

func NewSwitch(debug bool, mtu int) *Switch {
Expand Down Expand Up @@ -197,6 +200,12 @@ func (e *Switch) disconnect(id int, conn net.Conn) {

for address, targetConn := range e.cam {
if targetConn == id {
if e.notificationSender != nil {
e.notificationSender.Send(types.NotificationMessage{
NotificationType: types.ConnectionClosed,
MacAddress: address.String(),
})
}
delete(e.cam, address)
}
}
Expand Down Expand Up @@ -267,9 +276,17 @@ func (e *Switch) rxBuf(_ context.Context, id int, buf []byte) {
eth := header.Ethernet(buf)

e.camLock.Lock()
_, exists := e.cam[eth.SourceAddress()]
e.cam[eth.SourceAddress()] = id
e.camLock.Unlock()

if !exists && e.notificationSender != nil {
e.notificationSender.Send(types.NotificationMessage{
NotificationType: types.ConnectionEstablished,
MacAddress: eth.SourceAddress().String(),
})
}

if eth.DestinationAddress() != e.gateway.LinkAddress() {
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithData(buf),
Expand Down Expand Up @@ -304,3 +321,7 @@ func protocolImplementation(protocol types.Protocol) protocol {
return &hyperkitProtocol{}
}
}

func (e *Switch) SetNotificationSender(notificationSender *notification.NotificationSender) {
e.notificationSender = notificationSender
}
15 changes: 15 additions & 0 deletions pkg/types/handshake.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,18 @@ type UnexposeRequest struct {
Local string `json:"local"`
Protocol TransportProtocol `json:"protocol"`
}

type NotificationMessage struct {
NotificationType NotificationType `json:"notification_type"`
MacAddress string `json:"mac_address,omitempty"`
}

type NotificationType string

const (
Ready NotificationType = "ready"
ConnectionEstablished NotificationType = "connection_established"
HypervisorWarning NotificationType = "hypervisor_warning"
HypervisorError NotificationType = "hypervisor_error"
ConnectionClosed NotificationType = "connection_closed"
)
5 changes: 5 additions & 0 deletions pkg/virtualnetwork/virtualnetwork.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"os"

"github.com/containers/gvisor-tap-vsock/pkg/notification"
"github.com/containers/gvisor-tap-vsock/pkg/tap"
"github.com/containers/gvisor-tap-vsock/pkg/types"
"gvisor.dev/gvisor/pkg/tcpip"
Expand All @@ -28,6 +29,10 @@ type VirtualNetwork struct {
ipPool *tap.IPPool
}

func (n *VirtualNetwork) SetNotificationSender(notificationSender *notification.NotificationSender) {
n.networkSwitch.SetNotificationSender(notificationSender)
}

func New(configuration *types.Configuration) (*VirtualNetwork, error) {
_, subnet, err := net.ParseCIDR(configuration.Subnet)
if err != nil {
Expand Down
Loading