Skip to content

Commit 40ef47c

Browse files
committed
feat: add notification mechanism for network lifecycle events
Introduces a channel-based notification sender that sends JSON messages over Unix domain sockets for key events: - Ready: network stack is ready for connections - ConnectionEstablished: VM connection accepted - HypervisorError: error during VM connection setup Signed-off-by: Gunjan Vyas <[email protected]>
1 parent bafcbdb commit 40ef47c

File tree

4 files changed

+146
-24
lines changed

4 files changed

+146
-24
lines changed

cmd/gvproxy/config.go

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,25 +26,26 @@ const (
2626
)
2727

2828
type GvproxyArgs struct {
29-
config string
30-
endpoints arrayFlags
31-
debug bool
32-
mtu int
33-
sshPort int
34-
vpnkitSocket string
35-
qemuSocket string
36-
bessSocket string
37-
stdioSocket string
38-
vfkitSocket string
39-
forwardSocket arrayFlags
40-
forwardDest arrayFlags
41-
forwardUser arrayFlags
42-
forwardIdentify arrayFlags
43-
pidFile string
44-
pcapFile string
45-
logFile string
46-
servicesEndpoint string
47-
ec2MetadataAccess bool
29+
config string
30+
endpoints arrayFlags
31+
debug bool
32+
mtu int
33+
sshPort int
34+
vpnkitSocket string
35+
qemuSocket string
36+
bessSocket string
37+
stdioSocket string
38+
vfkitSocket string
39+
notificationSocket string
40+
forwardSocket arrayFlags
41+
forwardDest arrayFlags
42+
forwardUser arrayFlags
43+
forwardIdentify arrayFlags
44+
pidFile string
45+
pcapFile string
46+
logFile string
47+
servicesEndpoint string
48+
ec2MetadataAccess bool
4849
}
4950

5051
type GvproxyConfig struct {
@@ -58,11 +59,12 @@ type GvproxyConfig struct {
5859
Stdio string `yaml:"stdio,omitempty"`
5960
Vfkit string `yaml:"vfkit,omitempty"`
6061
} `yaml:"interfaces,omitempty"`
61-
Forwards []GvproxyConfigForward `yaml:"forwards,omitempty"`
62-
PIDFile string `yaml:"pid-file,omitempty"`
63-
LogFile string `yaml:"log-file,omitempty"`
64-
Services string `yaml:"services,omitempty"`
65-
Ec2MetadataAccess bool `yaml:"ec2-metadata-access,omitempty"`
62+
Forwards []GvproxyConfigForward `yaml:"forwards,omitempty"`
63+
PIDFile string `yaml:"pid-file,omitempty"`
64+
LogFile string `yaml:"log-file,omitempty"`
65+
Services string `yaml:"services,omitempty"`
66+
Ec2MetadataAccess bool `yaml:"ec2-metadata-access,omitempty"`
67+
NotificationSocket string `yaml:"notification,omitempty"`
6668
}
6769

6870
type GvproxyConfigForward struct {
@@ -130,6 +132,7 @@ func GvproxyArgParse(flagSet *flag.FlagSet, args *GvproxyArgs, argv []string) (*
130132
flagSet.StringVar(&args.logFile, "log-file", "", "Output log messages (logrus) to a given file path")
131133
flagSet.StringVar(&args.servicesEndpoint, "services", "", "Exposes the same HTTP API as the --listen flag, without the /connect endpoint")
132134
flagSet.BoolVar(&args.ec2MetadataAccess, "ec2-metadata-access", false, "Permits access to EC2 Metadata Service (TCP only)")
135+
flagSet.StringVar(&args.notificationSocket, "notification", "", "Socket to be used to send network-ready notifications")
133136
if err := flagSet.Parse(argv); err != nil {
134137
return nil, err
135138
}
@@ -237,6 +240,16 @@ func GvproxyConfigure(config *GvproxyConfig, args *GvproxyArgs, version string)
237240
if args.pidFile != "" {
238241
config.PIDFile = args.pidFile
239242
}
243+
if args.notificationSocket != "" {
244+
uri, err := url.Parse(args.notificationSocket)
245+
if err != nil {
246+
return config, fmt.Errorf("invalid value for notification listen address: %w", err)
247+
}
248+
if uri.Scheme != "unix" {
249+
return config, errors.New("notification listen address must be unix:// address")
250+
}
251+
config.NotificationSocket = uri.Path
252+
}
240253
if len(args.endpoints) > 0 {
241254
config.Listen = args.endpoints
242255
}

cmd/gvproxy/main.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ import (
1818
"time"
1919

2020
"github.com/containers/gvisor-tap-vsock/pkg/net/stdio"
21+
"github.com/containers/gvisor-tap-vsock/pkg/notification"
2122
"github.com/containers/gvisor-tap-vsock/pkg/sshclient"
2223
"github.com/containers/gvisor-tap-vsock/pkg/transport"
24+
"github.com/containers/gvisor-tap-vsock/pkg/types"
2325
"github.com/containers/gvisor-tap-vsock/pkg/virtualnetwork"
2426
"github.com/containers/winquit/pkg/winquit"
2527
humanize "github.com/dustin/go-humanize"
@@ -126,6 +128,14 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
126128
}
127129
log.Info("waiting for clients...")
128130

131+
var notificationSender *notification.NotificationSender
132+
if config.NotificationSocket != "" {
133+
notificationSender = notification.NewNotificationSender(config.NotificationSocket)
134+
g.Go(func() error {
135+
notificationSender.Start(ctx)
136+
return nil
137+
})
138+
}
129139
for _, endpoint := range config.Listen {
130140
log.Infof("listening %s", endpoint)
131141
ln, err := transport.Listen(endpoint)
@@ -134,6 +144,7 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
134144
}
135145
httpServe(ctx, g, ln, withProfiler(vn))
136146
}
147+
notificationSender.Send(types.NotificationMessage{NotificationType: types.Ready})
137148

138149
if config.Services != "" {
139150
log.Infof("enabling services API. Listening %s", config.Services)
@@ -188,6 +199,7 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
188199
log.Errorf("vpnkit accept error: %s", err)
189200
continue
190201
}
202+
notificationSender.Send(types.NotificationMessage{NotificationType: types.ConnectionEstablished})
191203
g.Go(func() error {
192204
return vn.AcceptVpnKit(conn)
193205
})
@@ -213,8 +225,10 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
213225
g.Go(func() error {
214226
conn, err := qemuListener.Accept()
215227
if err != nil {
228+
notificationSender.Send(types.NotificationMessage{NotificationType: types.HypervisorError})
216229
return fmt.Errorf("qemu accept error: %w", err)
217230
}
231+
notificationSender.Send(types.NotificationMessage{NotificationType: types.ConnectionEstablished})
218232
return vn.AcceptQemu(ctx, conn)
219233
})
220234
}
@@ -236,15 +250,18 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
236250
g.Go(func() error {
237251
conn, err := bessListener.Accept()
238252
if err != nil {
253+
notificationSender.Send(types.NotificationMessage{NotificationType: types.HypervisorError})
239254
return fmt.Errorf("bess accept error: %w", err)
240255
}
256+
notificationSender.Send(types.NotificationMessage{NotificationType: types.ConnectionEstablished})
241257
return vn.AcceptBess(ctx, conn)
242258
})
243259
}
244260

245261
if config.Interfaces.Vfkit != "" {
246262
conn, err := transport.ListenUnixgram(config.Interfaces.Vfkit)
247263
if err != nil {
264+
notificationSender.Send(types.NotificationMessage{NotificationType: types.HypervisorError})
248265
return fmt.Errorf("vfkit listen error: %w", err)
249266
}
250267

@@ -260,15 +277,19 @@ func run(ctx context.Context, g *errgroup.Group, config *GvproxyConfig) error {
260277
g.Go(func() error {
261278
vfkitConn, err := transport.AcceptVfkit(conn)
262279
if err != nil {
280+
notificationSender.Send(types.NotificationMessage{NotificationType: types.HypervisorError})
263281
return fmt.Errorf("vfkit accept error: %w", err)
264282
}
283+
284+
notificationSender.Send(types.NotificationMessage{NotificationType: types.ConnectionEstablished})
265285
return vn.AcceptVfkit(ctx, vfkitConn)
266286
})
267287
}
268288

269289
if config.Interfaces.Stdio != "" {
270290
g.Go(func() error {
271291
conn := stdio.GetStdioConn()
292+
notificationSender.Send(types.NotificationMessage{NotificationType: types.ConnectionEstablished})
272293
return vn.AcceptStdio(ctx, conn)
273294
})
274295
}

pkg/notification/sender.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package notification
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net"
8+
9+
"github.com/containers/gvisor-tap-vsock/pkg/types"
10+
log "github.com/sirupsen/logrus"
11+
)
12+
13+
type NotificationSender struct {
14+
notificationCh chan types.NotificationMessage
15+
socket string
16+
}
17+
18+
func NewNotificationSender(socket string) *NotificationSender {
19+
if socket == "" {
20+
return &NotificationSender{
21+
socket: "",
22+
notificationCh: nil,
23+
}
24+
}
25+
26+
return &NotificationSender{
27+
socket: socket,
28+
notificationCh: make(chan types.NotificationMessage, 100),
29+
}
30+
}
31+
32+
func (s *NotificationSender) Send(notification types.NotificationMessage) {
33+
if s.notificationCh == nil {
34+
return
35+
}
36+
select {
37+
case s.notificationCh <- notification:
38+
default:
39+
log.Warn("unable to send notification")
40+
}
41+
}
42+
43+
func (s *NotificationSender) Start(ctx context.Context) {
44+
if s.notificationCh == nil {
45+
return
46+
}
47+
48+
for {
49+
select {
50+
case <-ctx.Done():
51+
return
52+
case notification := <-s.notificationCh:
53+
if err := s.sendToSocket(notification); err != nil {
54+
log.Errorf("failed to send notification: %v", err)
55+
continue
56+
}
57+
}
58+
}
59+
}
60+
61+
func (s *NotificationSender) sendToSocket(notification types.NotificationMessage) error {
62+
if s.socket == "" {
63+
return nil
64+
}
65+
conn, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: s.socket, Net: "unix"})
66+
if err != nil {
67+
return fmt.Errorf("cannot dial notification socket: %w", err)
68+
}
69+
defer conn.Close()
70+
enc := json.NewEncoder(conn)
71+
if err := enc.Encode(notification); err != nil {
72+
return fmt.Errorf("failed to encode notification: %w", err)
73+
}
74+
return nil
75+
}

pkg/types/handshake.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,16 @@ type UnexposeRequest struct {
1919
Local string `json:"local"`
2020
Protocol TransportProtocol `json:"protocol"`
2121
}
22+
23+
type NotificationMessage struct {
24+
NotificationType NotificationType `json:"notification_type"`
25+
}
26+
27+
type NotificationType string
28+
29+
const (
30+
Ready NotificationType = "ready"
31+
ConnectionEstablished NotificationType = "connection_established"
32+
HypervisorWarning NotificationType = "hypervisor_warning"
33+
HypervisorError NotificationType = "hypervisor_error"
34+
)

0 commit comments

Comments
 (0)