diff --git a/CHANGELOG.md b/CHANGELOG.md index c7ac1d80b..2f7fef1a6 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 222cb1ef6..2b96a7c52 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.4.0 // indirect diff --git a/go.sum b/go.sum index 04026a436..85d473ce5 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 f30016f96..02908c8a6 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 000000000..1c6198709 --- /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 +}