mirror of https://github.com/containers/podman.git
278 lines
7.4 KiB
Go
278 lines
7.4 KiB
Go
package hvsock
|
|
|
|
// On Linux we have to deal with two different implementations. The
|
|
// "legacy" implementation never made it into the kernel, but several
|
|
// kernels, including the LinuxKit one carried patches for it for
|
|
// quite a while. The legacy version defined a new address family
|
|
// while the new version sits on top of the existing VMware/virtio
|
|
// socket implementation.
|
|
//
|
|
// We try to determine at init if we are on a kernel with the legacy
|
|
// implementation or the new version and set "legacyMode" accordingly.
|
|
//
|
|
// We can't just reuse the vsock implementation as we still need to
|
|
// emulated CloseRead()/CloseWrite() as not all Windows builds support
|
|
// it.
|
|
|
|
/*
|
|
#include <sys/socket.h>
|
|
|
|
struct sockaddr_hv {
|
|
unsigned short shv_family;
|
|
unsigned short reserved;
|
|
unsigned char shv_vm_id[16];
|
|
unsigned char shv_service_id[16];
|
|
};
|
|
int bind_sockaddr_hv(int fd, const struct sockaddr_hv *sa_hv) {
|
|
return bind(fd, (const struct sockaddr*)sa_hv, sizeof(*sa_hv));
|
|
}
|
|
int connect_sockaddr_hv(int fd, const struct sockaddr_hv *sa_hv) {
|
|
return connect(fd, (const struct sockaddr*)sa_hv, sizeof(*sa_hv));
|
|
}
|
|
int accept_hv(int fd, struct sockaddr_hv *sa_hv, socklen_t *sa_hv_len) {
|
|
return accept(fd, (struct sockaddr *)sa_hv, sa_hv_len);
|
|
}
|
|
int getsockname_hv(int fd, struct sockaddr_hv *sa_hv, socklen_t *sa_hv_len) {
|
|
return getsockname(fd, (struct sockaddr *)sa_hv, sa_hv_len);
|
|
}
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
const (
|
|
hvsockAF = 43 //SHV_PROTO_RAW
|
|
hvsockRaw = 1 // SHV_PROTO_RAW
|
|
)
|
|
|
|
// Supported returns if hvsocks are supported on your platform
|
|
func Supported() bool {
|
|
var sa C.struct_sockaddr_hv
|
|
var sa_len C.socklen_t
|
|
|
|
// Try opening a hvsockAF socket. If it works we are on older, i.e. 4.9.x kernels.
|
|
// 4.11 defines AF_SMC as 43 but it doesn't support protocol 1 so the
|
|
// socket() call should fail.
|
|
fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
// 4.16 defines SMCPROTO_SMC6 as 1 but its socket name size doesn't match
|
|
// size of sockaddr_hv so corresponding check should fail.
|
|
sa_len = C.sizeof_struct_sockaddr_hv
|
|
ret, _ := C.getsockname_hv(C.int(fd), &sa, &sa_len)
|
|
syscall.Close(fd)
|
|
if ret < 0 || sa_len != C.sizeof_struct_sockaddr_hv {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Dial a Hyper-V socket address
|
|
func Dial(raddr Addr) (Conn, error) {
|
|
fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sa := C.struct_sockaddr_hv{}
|
|
sa.shv_family = hvsockAF
|
|
sa.reserved = 0
|
|
|
|
for i := 0; i < 16; i++ {
|
|
sa.shv_vm_id[i] = C.uchar(raddr.VMID[i])
|
|
}
|
|
for i := 0; i < 16; i++ {
|
|
sa.shv_service_id[i] = C.uchar(raddr.ServiceID[i])
|
|
}
|
|
|
|
// Retry connect in a loop if EINTR is encountered.
|
|
for {
|
|
if ret, errno := C.connect_sockaddr_hv(C.int(fd), &sa); ret != 0 {
|
|
if errno == syscall.EINTR {
|
|
continue
|
|
}
|
|
return nil, fmt.Errorf("connect(%s) failed with %d, errno=%d", raddr, ret, errno)
|
|
}
|
|
break
|
|
}
|
|
|
|
return newHVsockConn(uintptr(fd), &Addr{VMID: GUIDZero, ServiceID: GUIDZero}, &raddr), nil
|
|
}
|
|
|
|
// Listen returns a net.Listener which can accept connections on the given port
|
|
func Listen(addr Addr) (net.Listener, error) {
|
|
fd, err := syscall.Socket(hvsockAF, syscall.SOCK_STREAM, hvsockRaw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sa := C.struct_sockaddr_hv{}
|
|
sa.shv_family = hvsockAF
|
|
sa.reserved = 0
|
|
|
|
for i := 0; i < 16; i++ {
|
|
sa.shv_vm_id[i] = C.uchar(addr.VMID[i])
|
|
}
|
|
for i := 0; i < 16; i++ {
|
|
sa.shv_service_id[i] = C.uchar(addr.ServiceID[i])
|
|
}
|
|
|
|
if ret, errno := C.bind_sockaddr_hv(C.int(fd), &sa); ret != 0 {
|
|
return nil, fmt.Errorf("listen(%s) failed with %d, errno=%d", addr, ret, errno)
|
|
}
|
|
|
|
err = syscall.Listen(fd, syscall.SOMAXCONN)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "listen(%s) failed", addr)
|
|
}
|
|
return &hvsockListener{fd, addr}, nil
|
|
}
|
|
|
|
//
|
|
// Hyper-v sockets Listener implementation
|
|
//
|
|
|
|
type hvsockListener struct {
|
|
fd int
|
|
local Addr
|
|
}
|
|
|
|
// Accept accepts an incoming call and returns the new connection.
|
|
func (v *hvsockListener) Accept() (net.Conn, error) {
|
|
var acceptSA C.struct_sockaddr_hv
|
|
var acceptSALen C.socklen_t
|
|
|
|
acceptSALen = C.sizeof_struct_sockaddr_hv
|
|
fd, err := C.accept_hv(C.int(v.fd), &acceptSA, &acceptSALen)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "accept(%s) failed", v.local)
|
|
}
|
|
|
|
remote := &Addr{VMID: guidFromC(acceptSA.shv_vm_id), ServiceID: guidFromC(acceptSA.shv_service_id)}
|
|
return newHVsockConn(uintptr(fd), &v.local, remote), nil
|
|
}
|
|
|
|
// Close closes the listening connection
|
|
func (v *hvsockListener) 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 *hvsockListener) Addr() net.Addr {
|
|
return v.local
|
|
}
|
|
|
|
//
|
|
// Hyper-V socket connection implementation
|
|
//
|
|
|
|
// hvsockConn represents a connection over a Hyper-V socket
|
|
type hvsockConn struct {
|
|
hvsock *os.File
|
|
fd uintptr
|
|
local *Addr
|
|
remote *Addr
|
|
}
|
|
|
|
func newHVsockConn(fd uintptr, local, remote *Addr) *hvsockConn {
|
|
hvsock := os.NewFile(fd, fmt.Sprintf("hvsock:%d", fd))
|
|
return &hvsockConn{hvsock: hvsock, fd: fd, local: local, remote: remote}
|
|
}
|
|
|
|
// LocalAddr returns the local address of a connection
|
|
func (v *hvsockConn) LocalAddr() net.Addr {
|
|
return v.local
|
|
}
|
|
|
|
// RemoteAddr returns the remote address of a connection
|
|
func (v *hvsockConn) RemoteAddr() net.Addr {
|
|
return v.remote
|
|
}
|
|
|
|
// Close closes the connection
|
|
func (v *hvsockConn) Close() error {
|
|
return v.hvsock.Close()
|
|
}
|
|
|
|
// CloseRead shuts down the reading side of a hvsock connection
|
|
func (v *hvsockConn) CloseRead() error {
|
|
return syscall.Shutdown(int(v.fd), syscall.SHUT_RD)
|
|
}
|
|
|
|
// CloseWrite shuts down the writing side of a hvsock connection
|
|
func (v *hvsockConn) CloseWrite() error {
|
|
return syscall.Shutdown(int(v.fd), syscall.SHUT_WR)
|
|
}
|
|
|
|
// Read reads data from the connection
|
|
func (v *hvsockConn) Read(buf []byte) (int, error) {
|
|
return v.hvsock.Read(buf)
|
|
}
|
|
|
|
// Write writes data over the connection
|
|
// TODO(rn): replace with a straight call to v.hvsock.Write() once 4.9.x support is deprecated
|
|
func (v *hvsockConn) Write(buf []byte) (int, error) {
|
|
written := 0
|
|
toWrite := len(buf)
|
|
for toWrite > 0 {
|
|
thisBatch := min(toWrite, maxMsgSize)
|
|
n, err := v.hvsock.Write(buf[written : written+thisBatch])
|
|
if err != nil {
|
|
return written, err
|
|
}
|
|
if n != thisBatch {
|
|
return written, fmt.Errorf("short write %d != %d", n, thisBatch)
|
|
}
|
|
toWrite -= n
|
|
written += n
|
|
}
|
|
|
|
return written, nil
|
|
}
|
|
|
|
// SetDeadline sets the read and write deadlines associated with the connection
|
|
func (v *hvsockConn) SetDeadline(t time.Time) error {
|
|
return nil // FIXME
|
|
}
|
|
|
|
// SetReadDeadline sets the deadline for future Read calls.
|
|
func (v *hvsockConn) SetReadDeadline(t time.Time) error {
|
|
return nil // FIXME
|
|
}
|
|
|
|
// SetWriteDeadline sets the deadline for future Write calls
|
|
func (v *hvsockConn) SetWriteDeadline(t time.Time) error {
|
|
return nil // FIXME
|
|
}
|
|
|
|
// File duplicates the underlying socket descriptor and returns it.
|
|
func (v *hvsockConn) 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.hvsock.Fd()), syscall.F_DUPFD_CLOEXEC, 0)
|
|
if e1 != 0 {
|
|
return nil, os.NewSyscallError("fcntl", e1)
|
|
}
|
|
return os.NewFile(r0, v.hvsock.Name()), nil
|
|
}
|
|
|
|
func guidFromC(cg [16]C.uchar) GUID {
|
|
var g GUID
|
|
for i := 0; i < 16; i++ {
|
|
g[i] = byte(cg[i])
|
|
}
|
|
return g
|
|
}
|