mirror of
https://github.com/nadoo/glider.git
synced 2025-02-23 01:15:41 +08:00
215 lines
5.4 KiB
Go
215 lines
5.4 KiB
Go
// Source code from:
|
|
// https://github.com/linuxkit/virtsock/tree/master/pkg/vsock
|
|
|
|
package vsock
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"syscall"
|
|
"time"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// Addr represents the address of a vsock end point.
|
|
type Addr struct {
|
|
CID uint32
|
|
Port uint32
|
|
}
|
|
|
|
// Network returns the network type for a Addr
|
|
func (a Addr) Network() string {
|
|
return "vsock"
|
|
}
|
|
|
|
// String returns a string representation of a Addr
|
|
func (a Addr) String() string {
|
|
return fmt.Sprintf("%d:%d", a.CID, a.Port)
|
|
}
|
|
|
|
// Conn is a vsock connection which supports half-close.
|
|
type Conn interface {
|
|
net.Conn
|
|
CloseRead() error
|
|
CloseWrite() error
|
|
File() (*os.File, error)
|
|
}
|
|
|
|
// SocketMode is a NOOP on Linux.
|
|
func SocketMode(m string) {}
|
|
|
|
// Convert a generic unix.Sockaddr to a Addr.
|
|
func sockaddrToVsock(sa unix.Sockaddr) *Addr {
|
|
switch sa := sa.(type) {
|
|
case *unix.SockaddrVM:
|
|
return &Addr{CID: sa.CID, Port: sa.Port}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Closes fd, retrying EINTR
|
|
func closeFD(fd int) error {
|
|
for {
|
|
if err := unix.Close(fd); err != nil {
|
|
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EINTR {
|
|
continue
|
|
}
|
|
return fmt.Errorf("failed to close() fd %d: %w", fd, err)
|
|
}
|
|
break
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Dial connects to the CID.Port via virtio sockets.
|
|
func Dial(cid, port uint32) (Conn, error) {
|
|
fd, err := syscall.Socket(unix.AF_VSOCK, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to create AF_VSOCK socket: %w", err)
|
|
}
|
|
sa := &unix.SockaddrVM{CID: cid, Port: port}
|
|
// Retry connect in a loop if EINTR is encountered.
|
|
for {
|
|
if err := unix.Connect(fd, sa); err != nil {
|
|
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EINTR {
|
|
continue
|
|
}
|
|
// Trying not to leak fd here
|
|
_ = closeFD(fd)
|
|
return nil, fmt.Errorf("failed connect() to %d:%d: %w", cid, port, err)
|
|
}
|
|
break
|
|
}
|
|
return newVsockConn(uintptr(fd), nil, &Addr{cid, port}), nil
|
|
}
|
|
|
|
// Listen returns a net.Listener which can accept connections on the given cid and port.
|
|
func Listen(cid, port uint32) (net.Listener, error) {
|
|
fd, err := syscall.Socket(unix.AF_VSOCK, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sa := &unix.SockaddrVM{CID: cid, Port: port}
|
|
if err = unix.Bind(fd, sa); err != nil {
|
|
return nil, fmt.Errorf("bind() to %d:%d failed: %w", cid, port, err)
|
|
}
|
|
|
|
err = syscall.Listen(fd, syscall.SOMAXCONN)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("listen() on %d:%d failed: %w", cid, port, err)
|
|
}
|
|
return &vsockListener{fd, Addr{cid, port}}, nil
|
|
}
|
|
|
|
// ContextID retrieves the local context ID for this system.
|
|
func ContextID() (uint32, error) {
|
|
f, err := os.Open("/dev/vsock")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer f.Close()
|
|
|
|
return unix.IoctlGetUint32(int(f.Fd()), unix.IOCTL_VM_SOCKETS_GET_LOCAL_CID)
|
|
}
|
|
|
|
type vsockListener struct {
|
|
fd int
|
|
local Addr
|
|
}
|
|
|
|
// Accept accepts an incoming call and returns the new connection.
|
|
func (v *vsockListener) Accept() (net.Conn, error) {
|
|
fd, sa, err := unix.Accept(v.fd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newVsockConn(uintptr(fd), &v.local, sockaddrToVsock(sa)), nil
|
|
}
|
|
|
|
// Close closes the listening connection
|
|
func (v *vsockListener) Close() error {
|
|
// Note this won't cause the Accept to unblock.
|
|
return unix.Close(v.fd)
|
|
}
|
|
|
|
// Addr returns the address the Listener is listening on
|
|
func (v *vsockListener) Addr() net.Addr {
|
|
return v.local
|
|
}
|
|
|
|
// a wrapper around FileConn which supports CloseRead and CloseWrite
|
|
type vsockConn struct {
|
|
vsock *os.File
|
|
fd uintptr
|
|
local *Addr
|
|
remote *Addr
|
|
}
|
|
|
|
func newVsockConn(fd uintptr, local, remote *Addr) *vsockConn {
|
|
vsock := os.NewFile(fd, fmt.Sprintf("vsock:%d", fd))
|
|
return &vsockConn{vsock: vsock, fd: fd, local: local, remote: remote}
|
|
}
|
|
|
|
// LocalAddr returns the local address of a connection
|
|
func (v *vsockConn) LocalAddr() net.Addr {
|
|
return v.local
|
|
}
|
|
|
|
// RemoteAddr returns the remote address of a connection
|
|
func (v *vsockConn) RemoteAddr() net.Addr {
|
|
return v.remote
|
|
}
|
|
|
|
// Close closes the connection
|
|
func (v *vsockConn) Close() error {
|
|
return v.vsock.Close()
|
|
}
|
|
|
|
// CloseRead shuts down the reading side of a vsock connection
|
|
func (v *vsockConn) CloseRead() error {
|
|
return syscall.Shutdown(int(v.fd), syscall.SHUT_RD)
|
|
}
|
|
|
|
// CloseWrite shuts down the writing side of a vsock connection
|
|
func (v *vsockConn) CloseWrite() error {
|
|
return syscall.Shutdown(int(v.fd), syscall.SHUT_WR)
|
|
}
|
|
|
|
// Read reads data from the connection
|
|
func (v *vsockConn) Read(buf []byte) (int, error) {
|
|
return v.vsock.Read(buf)
|
|
}
|
|
|
|
// Write writes data over the connection
|
|
func (v *vsockConn) Write(buf []byte) (int, error) {
|
|
return v.vsock.Write(buf)
|
|
}
|
|
|
|
// SetDeadline sets the read and write deadlines associated with the connection
|
|
func (v *vsockConn) SetDeadline(t time.Time) error {
|
|
return nil // FIXME
|
|
}
|
|
|
|
// SetReadDeadline sets the deadline for future Read calls.
|
|
func (v *vsockConn) SetReadDeadline(t time.Time) error {
|
|
return nil // FIXME
|
|
}
|
|
|
|
// SetWriteDeadline sets the deadline for future Write calls
|
|
func (v *vsockConn) SetWriteDeadline(t time.Time) error {
|
|
return nil // FIXME
|
|
}
|
|
|
|
// File duplicates the underlying socket descriptor and returns it.
|
|
func (v *vsockConn) File() (*os.File, error) {
|
|
// This is equivalent to dup(2) but creates the new fd with CLOEXEC already set.
|
|
r0, _, e1 := syscall.Syscall(syscall.SYS_FCNTL, uintptr(v.vsock.Fd()), syscall.F_DUPFD_CLOEXEC, 0)
|
|
if e1 != 0 {
|
|
return nil, os.NewSyscallError("fcntl", e1)
|
|
}
|
|
return os.NewFile(r0, v.vsock.Name()), nil
|
|
}
|