mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-03-09 17:09:29 +00:00
Several packages are only used while running the e2e suite. These packages are less important to update, as the they can not influence the final executable that is part of the Ceph-CSI container-image. By moving these dependencies out of the main Ceph-CSI go.mod, it is easier to identify if a reported CVE affects Ceph-CSI, or only the testing (like most of the Kubernetes CVEs). Signed-off-by: Niels de Vos <ndevos@ibm.com>
583 lines
16 KiB
Go
583 lines
16 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
package winio
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
|
|
"github.com/Microsoft/go-winio/internal/socket"
|
|
"github.com/Microsoft/go-winio/pkg/guid"
|
|
)
|
|
|
|
const afHVSock = 34 // AF_HYPERV
|
|
|
|
// Well known Service and VM IDs
|
|
// https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service#vmid-wildcards
|
|
|
|
// HvsockGUIDWildcard is the wildcard VmId for accepting connections from all partitions.
|
|
func HvsockGUIDWildcard() guid.GUID { // 00000000-0000-0000-0000-000000000000
|
|
return guid.GUID{}
|
|
}
|
|
|
|
// HvsockGUIDBroadcast is the wildcard VmId for broadcasting sends to all partitions.
|
|
func HvsockGUIDBroadcast() guid.GUID { // ffffffff-ffff-ffff-ffff-ffffffffffff
|
|
return guid.GUID{
|
|
Data1: 0xffffffff,
|
|
Data2: 0xffff,
|
|
Data3: 0xffff,
|
|
Data4: [8]uint8{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
|
}
|
|
}
|
|
|
|
// HvsockGUIDLoopback is the Loopback VmId for accepting connections to the same partition as the connector.
|
|
func HvsockGUIDLoopback() guid.GUID { // e0e16197-dd56-4a10-9195-5ee7a155a838
|
|
return guid.GUID{
|
|
Data1: 0xe0e16197,
|
|
Data2: 0xdd56,
|
|
Data3: 0x4a10,
|
|
Data4: [8]uint8{0x91, 0x95, 0x5e, 0xe7, 0xa1, 0x55, 0xa8, 0x38},
|
|
}
|
|
}
|
|
|
|
// HvsockGUIDSiloHost is the address of a silo's host partition:
|
|
// - The silo host of a hosted silo is the utility VM.
|
|
// - The silo host of a silo on a physical host is the physical host.
|
|
func HvsockGUIDSiloHost() guid.GUID { // 36bd0c5c-7276-4223-88ba-7d03b654c568
|
|
return guid.GUID{
|
|
Data1: 0x36bd0c5c,
|
|
Data2: 0x7276,
|
|
Data3: 0x4223,
|
|
Data4: [8]byte{0x88, 0xba, 0x7d, 0x03, 0xb6, 0x54, 0xc5, 0x68},
|
|
}
|
|
}
|
|
|
|
// HvsockGUIDChildren is the wildcard VmId for accepting connections from the connector's child partitions.
|
|
func HvsockGUIDChildren() guid.GUID { // 90db8b89-0d35-4f79-8ce9-49ea0ac8b7cd
|
|
return guid.GUID{
|
|
Data1: 0x90db8b89,
|
|
Data2: 0xd35,
|
|
Data3: 0x4f79,
|
|
Data4: [8]uint8{0x8c, 0xe9, 0x49, 0xea, 0xa, 0xc8, 0xb7, 0xcd},
|
|
}
|
|
}
|
|
|
|
// HvsockGUIDParent is the wildcard VmId for accepting connections from the connector's parent partition.
|
|
// Listening on this VmId accepts connection from:
|
|
// - Inside silos: silo host partition.
|
|
// - Inside hosted silo: host of the VM.
|
|
// - Inside VM: VM host.
|
|
// - Physical host: Not supported.
|
|
func HvsockGUIDParent() guid.GUID { // a42e7cda-d03f-480c-9cc2-a4de20abb878
|
|
return guid.GUID{
|
|
Data1: 0xa42e7cda,
|
|
Data2: 0xd03f,
|
|
Data3: 0x480c,
|
|
Data4: [8]uint8{0x9c, 0xc2, 0xa4, 0xde, 0x20, 0xab, 0xb8, 0x78},
|
|
}
|
|
}
|
|
|
|
// hvsockVsockServiceTemplate is the Service GUID used for the VSOCK protocol.
|
|
func hvsockVsockServiceTemplate() guid.GUID { // 00000000-facb-11e6-bd58-64006a7986d3
|
|
return guid.GUID{
|
|
Data2: 0xfacb,
|
|
Data3: 0x11e6,
|
|
Data4: [8]uint8{0xbd, 0x58, 0x64, 0x00, 0x6a, 0x79, 0x86, 0xd3},
|
|
}
|
|
}
|
|
|
|
// An HvsockAddr is an address for a AF_HYPERV socket.
|
|
type HvsockAddr struct {
|
|
VMID guid.GUID
|
|
ServiceID guid.GUID
|
|
}
|
|
|
|
type rawHvsockAddr struct {
|
|
Family uint16
|
|
_ uint16
|
|
VMID guid.GUID
|
|
ServiceID guid.GUID
|
|
}
|
|
|
|
var _ socket.RawSockaddr = &rawHvsockAddr{}
|
|
|
|
// Network returns the address's network name, "hvsock".
|
|
func (*HvsockAddr) Network() string {
|
|
return "hvsock"
|
|
}
|
|
|
|
func (addr *HvsockAddr) String() string {
|
|
return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID)
|
|
}
|
|
|
|
// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port.
|
|
func VsockServiceID(port uint32) guid.GUID {
|
|
g := hvsockVsockServiceTemplate() // make a copy
|
|
g.Data1 = port
|
|
return g
|
|
}
|
|
|
|
func (addr *HvsockAddr) raw() rawHvsockAddr {
|
|
return rawHvsockAddr{
|
|
Family: afHVSock,
|
|
VMID: addr.VMID,
|
|
ServiceID: addr.ServiceID,
|
|
}
|
|
}
|
|
|
|
func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) {
|
|
addr.VMID = raw.VMID
|
|
addr.ServiceID = raw.ServiceID
|
|
}
|
|
|
|
// Sockaddr returns a pointer to and the size of this struct.
|
|
//
|
|
// Implements the [socket.RawSockaddr] interface, and allows use in
|
|
// [socket.Bind] and [socket.ConnectEx].
|
|
func (r *rawHvsockAddr) Sockaddr() (unsafe.Pointer, int32, error) {
|
|
return unsafe.Pointer(r), int32(unsafe.Sizeof(rawHvsockAddr{})), nil
|
|
}
|
|
|
|
// Sockaddr interface allows use with `sockets.Bind()` and `.ConnectEx()`.
|
|
func (r *rawHvsockAddr) FromBytes(b []byte) error {
|
|
n := int(unsafe.Sizeof(rawHvsockAddr{}))
|
|
|
|
if len(b) < n {
|
|
return fmt.Errorf("got %d, want %d: %w", len(b), n, socket.ErrBufferSize)
|
|
}
|
|
|
|
copy(unsafe.Slice((*byte)(unsafe.Pointer(r)), n), b[:n])
|
|
if r.Family != afHVSock {
|
|
return fmt.Errorf("got %d, want %d: %w", r.Family, afHVSock, socket.ErrAddrFamily)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HvsockListener is a socket listener for the AF_HYPERV address family.
|
|
type HvsockListener struct {
|
|
sock *win32File
|
|
addr HvsockAddr
|
|
}
|
|
|
|
var _ net.Listener = &HvsockListener{}
|
|
|
|
// HvsockConn is a connected socket of the AF_HYPERV address family.
|
|
type HvsockConn struct {
|
|
sock *win32File
|
|
local, remote HvsockAddr
|
|
}
|
|
|
|
var _ net.Conn = &HvsockConn{}
|
|
|
|
func newHVSocket() (*win32File, error) {
|
|
fd, err := windows.Socket(afHVSock, windows.SOCK_STREAM, 1)
|
|
if err != nil {
|
|
return nil, os.NewSyscallError("socket", err)
|
|
}
|
|
f, err := makeWin32File(fd)
|
|
if err != nil {
|
|
windows.Close(fd)
|
|
return nil, err
|
|
}
|
|
f.socket = true
|
|
return f, nil
|
|
}
|
|
|
|
// ListenHvsock listens for connections on the specified hvsock address.
|
|
func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) {
|
|
l := &HvsockListener{addr: *addr}
|
|
|
|
var sock *win32File
|
|
sock, err = newHVSocket()
|
|
if err != nil {
|
|
return nil, l.opErr("listen", err)
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
_ = sock.Close()
|
|
}
|
|
}()
|
|
|
|
sa := addr.raw()
|
|
err = socket.Bind(sock.handle, &sa)
|
|
if err != nil {
|
|
return nil, l.opErr("listen", os.NewSyscallError("socket", err))
|
|
}
|
|
err = windows.Listen(sock.handle, 16)
|
|
if err != nil {
|
|
return nil, l.opErr("listen", os.NewSyscallError("listen", err))
|
|
}
|
|
return &HvsockListener{sock: sock, addr: *addr}, nil
|
|
}
|
|
|
|
func (l *HvsockListener) opErr(op string, err error) error {
|
|
return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err}
|
|
}
|
|
|
|
// Addr returns the listener's network address.
|
|
func (l *HvsockListener) Addr() net.Addr {
|
|
return &l.addr
|
|
}
|
|
|
|
// Accept waits for the next connection and returns it.
|
|
func (l *HvsockListener) Accept() (_ net.Conn, err error) {
|
|
sock, err := newHVSocket()
|
|
if err != nil {
|
|
return nil, l.opErr("accept", err)
|
|
}
|
|
defer func() {
|
|
if sock != nil {
|
|
sock.Close()
|
|
}
|
|
}()
|
|
c, err := l.sock.prepareIO()
|
|
if err != nil {
|
|
return nil, l.opErr("accept", err)
|
|
}
|
|
defer l.sock.wg.Done()
|
|
|
|
// AcceptEx, per documentation, requires an extra 16 bytes per address.
|
|
//
|
|
// https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-acceptex
|
|
const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{}))
|
|
var addrbuf [addrlen * 2]byte
|
|
|
|
var bytes uint32
|
|
err = windows.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0 /* rxdatalen */, addrlen, addrlen, &bytes, &c.o)
|
|
if _, err = l.sock.asyncIO(c, nil, bytes, err); err != nil {
|
|
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
|
|
}
|
|
|
|
conn := &HvsockConn{
|
|
sock: sock,
|
|
}
|
|
// The local address returned in the AcceptEx buffer is the same as the Listener socket's
|
|
// address. However, the service GUID reported by GetSockName is different from the Listeners
|
|
// socket, and is sometimes the same as the local address of the socket that dialed the
|
|
// address, with the service GUID.Data1 incremented, but othertimes is different.
|
|
// todo: does the local address matter? is the listener's address or the actual address appropriate?
|
|
conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0])))
|
|
conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen])))
|
|
|
|
// initialize the accepted socket and update its properties with those of the listening socket
|
|
if err = windows.Setsockopt(sock.handle,
|
|
windows.SOL_SOCKET, windows.SO_UPDATE_ACCEPT_CONTEXT,
|
|
(*byte)(unsafe.Pointer(&l.sock.handle)), int32(unsafe.Sizeof(l.sock.handle))); err != nil {
|
|
return nil, conn.opErr("accept", os.NewSyscallError("setsockopt", err))
|
|
}
|
|
|
|
sock = nil
|
|
return conn, nil
|
|
}
|
|
|
|
// Close closes the listener, causing any pending Accept calls to fail.
|
|
func (l *HvsockListener) Close() error {
|
|
return l.sock.Close()
|
|
}
|
|
|
|
// HvsockDialer configures and dials a Hyper-V Socket (ie, [HvsockConn]).
|
|
type HvsockDialer struct {
|
|
// Deadline is the time the Dial operation must connect before erroring.
|
|
Deadline time.Time
|
|
|
|
// Retries is the number of additional connects to try if the connection times out, is refused,
|
|
// or the host is unreachable
|
|
Retries uint
|
|
|
|
// RetryWait is the time to wait after a connection error to retry
|
|
RetryWait time.Duration
|
|
|
|
rt *time.Timer // redial wait timer
|
|
}
|
|
|
|
// Dial the Hyper-V socket at addr.
|
|
//
|
|
// See [HvsockDialer.Dial] for more information.
|
|
func Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) {
|
|
return (&HvsockDialer{}).Dial(ctx, addr)
|
|
}
|
|
|
|
// Dial attempts to connect to the Hyper-V socket at addr, and returns a connection if successful.
|
|
// Will attempt (HvsockDialer).Retries if dialing fails, waiting (HvsockDialer).RetryWait between
|
|
// retries.
|
|
//
|
|
// Dialing can be cancelled either by providing (HvsockDialer).Deadline, or cancelling ctx.
|
|
func (d *HvsockDialer) Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) {
|
|
op := "dial"
|
|
// create the conn early to use opErr()
|
|
conn = &HvsockConn{
|
|
remote: *addr,
|
|
}
|
|
|
|
if !d.Deadline.IsZero() {
|
|
var cancel context.CancelFunc
|
|
ctx, cancel = context.WithDeadline(ctx, d.Deadline)
|
|
defer cancel()
|
|
}
|
|
|
|
// preemptive timeout/cancellation check
|
|
if err = ctx.Err(); err != nil {
|
|
return nil, conn.opErr(op, err)
|
|
}
|
|
|
|
sock, err := newHVSocket()
|
|
if err != nil {
|
|
return nil, conn.opErr(op, err)
|
|
}
|
|
defer func() {
|
|
if sock != nil {
|
|
sock.Close()
|
|
}
|
|
}()
|
|
|
|
sa := addr.raw()
|
|
err = socket.Bind(sock.handle, &sa)
|
|
if err != nil {
|
|
return nil, conn.opErr(op, os.NewSyscallError("bind", err))
|
|
}
|
|
|
|
c, err := sock.prepareIO()
|
|
if err != nil {
|
|
return nil, conn.opErr(op, err)
|
|
}
|
|
defer sock.wg.Done()
|
|
var bytes uint32
|
|
for i := uint(0); i <= d.Retries; i++ {
|
|
err = socket.ConnectEx(
|
|
sock.handle,
|
|
&sa,
|
|
nil, // sendBuf
|
|
0, // sendDataLen
|
|
&bytes,
|
|
(*windows.Overlapped)(unsafe.Pointer(&c.o)))
|
|
_, err = sock.asyncIO(c, nil, bytes, err)
|
|
if i < d.Retries && canRedial(err) {
|
|
if err = d.redialWait(ctx); err == nil {
|
|
continue
|
|
}
|
|
}
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, conn.opErr(op, os.NewSyscallError("connectex", err))
|
|
}
|
|
|
|
// update the connection properties, so shutdown can be used
|
|
if err = windows.Setsockopt(
|
|
sock.handle,
|
|
windows.SOL_SOCKET,
|
|
windows.SO_UPDATE_CONNECT_CONTEXT,
|
|
nil, // optvalue
|
|
0, // optlen
|
|
); err != nil {
|
|
return nil, conn.opErr(op, os.NewSyscallError("setsockopt", err))
|
|
}
|
|
|
|
// get the local name
|
|
var sal rawHvsockAddr
|
|
err = socket.GetSockName(sock.handle, &sal)
|
|
if err != nil {
|
|
return nil, conn.opErr(op, os.NewSyscallError("getsockname", err))
|
|
}
|
|
conn.local.fromRaw(&sal)
|
|
|
|
// one last check for timeout, since asyncIO doesn't check the context
|
|
if err = ctx.Err(); err != nil {
|
|
return nil, conn.opErr(op, err)
|
|
}
|
|
|
|
conn.sock = sock
|
|
sock = nil
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
// redialWait waits before attempting to redial, resetting the timer as appropriate.
|
|
func (d *HvsockDialer) redialWait(ctx context.Context) (err error) {
|
|
if d.RetryWait == 0 {
|
|
return nil
|
|
}
|
|
|
|
if d.rt == nil {
|
|
d.rt = time.NewTimer(d.RetryWait)
|
|
} else {
|
|
// should already be stopped and drained
|
|
d.rt.Reset(d.RetryWait)
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
case <-d.rt.C:
|
|
return nil
|
|
}
|
|
|
|
// stop and drain the timer
|
|
if !d.rt.Stop() {
|
|
<-d.rt.C
|
|
}
|
|
return ctx.Err()
|
|
}
|
|
|
|
// assumes error is a plain, unwrapped windows.Errno provided by direct syscall.
|
|
func canRedial(err error) bool {
|
|
//nolint:errorlint // guaranteed to be an Errno
|
|
switch err {
|
|
case windows.WSAECONNREFUSED, windows.WSAENETUNREACH, windows.WSAETIMEDOUT,
|
|
windows.ERROR_CONNECTION_REFUSED, windows.ERROR_CONNECTION_UNAVAIL:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (conn *HvsockConn) opErr(op string, err error) error {
|
|
// translate from "file closed" to "socket closed"
|
|
if errors.Is(err, ErrFileClosed) {
|
|
err = socket.ErrSocketClosed
|
|
}
|
|
return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err}
|
|
}
|
|
|
|
func (conn *HvsockConn) Read(b []byte) (int, error) {
|
|
c, err := conn.sock.prepareIO()
|
|
if err != nil {
|
|
return 0, conn.opErr("read", err)
|
|
}
|
|
defer conn.sock.wg.Done()
|
|
buf := windows.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
|
var flags, bytes uint32
|
|
err = windows.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil)
|
|
n, err := conn.sock.asyncIO(c, &conn.sock.readDeadline, bytes, err)
|
|
if err != nil {
|
|
var eno windows.Errno
|
|
if errors.As(err, &eno) {
|
|
err = os.NewSyscallError("wsarecv", eno)
|
|
}
|
|
return 0, conn.opErr("read", err)
|
|
} else if n == 0 {
|
|
err = io.EOF
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
func (conn *HvsockConn) Write(b []byte) (int, error) {
|
|
t := 0
|
|
for len(b) != 0 {
|
|
n, err := conn.write(b)
|
|
if err != nil {
|
|
return t + n, err
|
|
}
|
|
t += n
|
|
b = b[n:]
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
func (conn *HvsockConn) write(b []byte) (int, error) {
|
|
c, err := conn.sock.prepareIO()
|
|
if err != nil {
|
|
return 0, conn.opErr("write", err)
|
|
}
|
|
defer conn.sock.wg.Done()
|
|
buf := windows.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
|
var bytes uint32
|
|
err = windows.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil)
|
|
n, err := conn.sock.asyncIO(c, &conn.sock.writeDeadline, bytes, err)
|
|
if err != nil {
|
|
var eno windows.Errno
|
|
if errors.As(err, &eno) {
|
|
err = os.NewSyscallError("wsasend", eno)
|
|
}
|
|
return 0, conn.opErr("write", err)
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
// Close closes the socket connection, failing any pending read or write calls.
|
|
func (conn *HvsockConn) Close() error {
|
|
return conn.sock.Close()
|
|
}
|
|
|
|
func (conn *HvsockConn) IsClosed() bool {
|
|
return conn.sock.IsClosed()
|
|
}
|
|
|
|
// shutdown disables sending or receiving on a socket.
|
|
func (conn *HvsockConn) shutdown(how int) error {
|
|
if conn.IsClosed() {
|
|
return socket.ErrSocketClosed
|
|
}
|
|
|
|
err := windows.Shutdown(conn.sock.handle, how)
|
|
if err != nil {
|
|
// If the connection was closed, shutdowns fail with "not connected"
|
|
if errors.Is(err, windows.WSAENOTCONN) ||
|
|
errors.Is(err, windows.WSAESHUTDOWN) {
|
|
err = socket.ErrSocketClosed
|
|
}
|
|
return os.NewSyscallError("shutdown", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CloseRead shuts down the read end of the socket, preventing future read operations.
|
|
func (conn *HvsockConn) CloseRead() error {
|
|
err := conn.shutdown(windows.SHUT_RD)
|
|
if err != nil {
|
|
return conn.opErr("closeread", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CloseWrite shuts down the write end of the socket, preventing future write operations and
|
|
// notifying the other endpoint that no more data will be written.
|
|
func (conn *HvsockConn) CloseWrite() error {
|
|
err := conn.shutdown(windows.SHUT_WR)
|
|
if err != nil {
|
|
return conn.opErr("closewrite", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// LocalAddr returns the local address of the connection.
|
|
func (conn *HvsockConn) LocalAddr() net.Addr {
|
|
return &conn.local
|
|
}
|
|
|
|
// RemoteAddr returns the remote address of the connection.
|
|
func (conn *HvsockConn) RemoteAddr() net.Addr {
|
|
return &conn.remote
|
|
}
|
|
|
|
// SetDeadline implements the net.Conn SetDeadline method.
|
|
func (conn *HvsockConn) SetDeadline(t time.Time) error {
|
|
// todo: implement `SetDeadline` for `win32File`
|
|
if err := conn.SetReadDeadline(t); err != nil {
|
|
return fmt.Errorf("set read deadline: %w", err)
|
|
}
|
|
if err := conn.SetWriteDeadline(t); err != nil {
|
|
return fmt.Errorf("set write deadline: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetReadDeadline implements the net.Conn SetReadDeadline method.
|
|
func (conn *HvsockConn) SetReadDeadline(t time.Time) error {
|
|
return conn.sock.SetReadDeadline(t)
|
|
}
|
|
|
|
// SetWriteDeadline implements the net.Conn SetWriteDeadline method.
|
|
func (conn *HvsockConn) SetWriteDeadline(t time.Time) error {
|
|
return conn.sock.SetWriteDeadline(t)
|
|
}
|