From 7ad7eb05d4dbdf0e751b675f06a9c922a0220852 Mon Sep 17 00:00:00 2001 From: Daniel Nagy Date: Thu, 2 Jan 2025 21:45:00 +0100 Subject: [PATCH] Introduce VSOCK link type This address family is used in communication between virtual machines and their hosts. Advantages include that no virtual ethernet adapter and their respective address configuration and routing need to be setup. Rather, with this new link type, only a single yggdrasil interface can exist inside of the virtual machine. It can also be used inside of containers. There, the advantage over existing link types like unix sockets include, that no mount point need to be shared with the host and container. This provides more isolation. More information: https://man7.org/linux/man-pages/man7/vsock.7.html https://gist.github.com/nrdmn/7971be650919b112343b1cb2757a3fe6 --- CHANGELOG.md | 7 +++++ go.mod | 2 ++ go.sum | 4 +++ src/core/link.go | 6 ++++ src/core/link_vsock.go | 69 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+) create mode 100644 src/core/link_vsock.go diff --git a/CHANGELOG.md b/CHANGELOG.md index c7ac1d80..2f7fef1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.5.xx] - 2025-xx-xx + +### Added + +* VSOCK support for peerings, by using the new `vsock://` scheme in `Listen` and `Peers`. + * Use e.g. `vsock://local:1234`. + ## [0.5.12] - 2024-12-18 * Go 1.22 is now required to build Yggdrasil diff --git a/go.mod b/go.mod index 672edd6e..4f30db00 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/hashicorp/go-syslog v1.0.0 github.com/hjson/hjson-go/v4 v4.4.0 github.com/kardianos/minwinsvc v1.0.2 + github.com/mdlayher/vsock v1.2.1 github.com/quic-go/quic-go v0.48.2 github.com/vishvananda/netlink v1.3.0 github.com/wlynxg/anet v0.0.5 @@ -29,6 +30,7 @@ require ( github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mdlayher/socket v0.4.1 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/rivo/uniseg v0.2.0 // indirect go.uber.org/mock v0.5.0 // indirect diff --git a/go.sum b/go.sum index d843af40..c6ea0fbc 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,10 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= +github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= diff --git a/src/core/link.go b/src/core/link.go index f30016f9..02908c8a 100644 --- a/src/core/link.go +++ b/src/core/link.go @@ -40,6 +40,7 @@ type links struct { quic *linkQUIC // QUIC interface support ws *linkWS // WS interface support wss *linkWSS // WSS interface support + vsock *linkVSOCK // VSOCK interface support // _links can only be modified safely from within the links actor _links map[linkInfo]*link // *link is nil if connection in progress _listeners map[*Listener]context.CancelFunc @@ -96,6 +97,7 @@ func (l *links) init(c *Core) error { l.quic = l.newLinkQUIC() l.ws = l.newLinkWS() l.wss = l.newLinkWSS() + l.vsock = l.newLinkVSOCK() l._links = make(map[linkInfo]*link) l._listeners = make(map[*Listener]context.CancelFunc) @@ -444,6 +446,8 @@ func (l *links) listen(u *url.URL, sintf string, local bool) (*Listener, error) protocol = l.ws case "wss": protocol = l.wss + case "vsock": + protocol = l.vsock default: ctxcancel() return nil, ErrLinkUnrecognisedSchema @@ -595,6 +599,8 @@ func (l *links) connect(ctx context.Context, u *url.URL, info linkInfo, options dialer = l.ws case "wss": dialer = l.wss + case "vsock": + dialer = l.vsock default: return nil, ErrLinkUnrecognisedSchema } diff --git a/src/core/link_vsock.go b/src/core/link_vsock.go new file mode 100644 index 00000000..1c619870 --- /dev/null +++ b/src/core/link_vsock.go @@ -0,0 +1,69 @@ +package core + +import ( + "context" + "fmt" + "net" + "net/url" + "strconv" + "strings" + + "github.com/Arceliar/phony" + "github.com/mdlayher/vsock" +) + +type linkVSOCK struct { + phony.Inbox + *links +} + +func (l *links) newLinkVSOCK() *linkVSOCK { + lt := &linkVSOCK{ + links: l, + } + return lt +} + +func (l *linkVSOCK) dial(ctx context.Context, url *url.URL, info linkInfo, options linkOptions) (net.Conn, error) { + localPort, err := strconv.ParseUint(url.Port(), 10, 32) + if err != nil { + return nil, fmt.Errorf("no VSOCK port specified: %w", err) + } + contextID, err := urlParseContextID(url) + if err != nil { + return nil, fmt.Errorf("Unknown VSOCK host and cannot parse as numerical contextID: %w", err) + } + return vsock.Dial(contextID, uint32(localPort), nil) +} + +func (l *linkVSOCK) listen(ctx context.Context, url *url.URL, _ string) (net.Listener, error) { + localPort, err := strconv.ParseUint(url.Port(), 10, 32) + if err != nil { + return nil, fmt.Errorf("no VSOCK port specified: %w", err) + } + contextID, err := urlParseContextID(url) + if err != nil { + return nil, fmt.Errorf("Unknown VSOCK host and cannot parse as numerical contextID: %w", err) + } + return vsock.ListenContextID(contextID, uint32(localPort), nil) +} + +func urlParseContextID(u *url.URL) (uint32, error) { + var contextID uint32 + + switch strings.ToLower(u.Hostname()) { + case "hypervisor": + contextID = vsock.Hypervisor + case "local": + contextID = vsock.Local + case "host": + contextID = vsock.Host + default: + parsedHost, err := strconv.ParseUint(u.Hostname(), 10, 32) + if err != nil { + return 0, err + } + contextID = uint32(parsedHost) + } + return contextID, nil +}