...

Source file src/edge-infra.dev/pkg/sds/devices/agent/udevproxy/udevproxy.go

Documentation: edge-infra.dev/pkg/sds/devices/agent/udevproxy

     1  //go:build linux
     2  
     3  package udevproxy
     4  
     5  import (
     6  	"fmt"
     7  	"os"
     8  	"runtime"
     9  
    10  	"github.com/hashicorp/go-multierror"
    11  	"golang.org/x/sys/unix"
    12  
    13  	"edge-infra.dev/pkg/lib/kernel/udev"
    14  )
    15  
    16  type UdevProxy interface {
    17  	Send(path string, uevents []*udev.UEvent) error
    18  }
    19  
    20  type udevProxy struct{}
    21  
    22  func NewUdevProxy() UdevProxy {
    23  	return &udevProxy{}
    24  }
    25  
    26  type nsHandle int
    27  
    28  func (ns *nsHandle) Close() error {
    29  	if err := unix.Close(int(*ns)); err != nil {
    30  		return err
    31  	}
    32  	*ns = -1
    33  	return nil
    34  }
    35  
    36  func (ns *nsHandle) Set() error {
    37  	return unix.Setns(int(*ns), unix.CLONE_NEWNET)
    38  }
    39  
    40  // sendUEventsToContainer takes the path to container network namespace
    41  // and replays the udev events to netlink.
    42  func (up udevProxy) Send(path string, uevents []*udev.UEvent) (err error) {
    43  	runtime.LockOSThread()
    44  	defer runtime.UnlockOSThread()
    45  
    46  	currentNs, err := getCurrentNetworkNamespace()
    47  	if err != nil {
    48  		return fmt.Errorf("error getting current network namespace: %w", err)
    49  	}
    50  
    51  	containerNs, err := getContainerNetworkNamespace(path)
    52  	if err != nil {
    53  		return fmt.Errorf("error fetching container file-descriptor: %w", err)
    54  	}
    55  	defer func() {
    56  		if ctrNsErr := containerNs.Close(); err != nil {
    57  			err = multierror.Append(err, ctrNsErr)
    58  		}
    59  		if currentNsErr := currentNs.Set(); err != nil {
    60  			err = multierror.Append(err, currentNsErr)
    61  		}
    62  	}()
    63  
    64  	if nsSetErr := containerNs.Set(); err != nil {
    65  		return multierror.Append(err, fmt.Errorf("error setting container network namespace: %w", nsSetErr))
    66  	}
    67  
    68  	addr := unix.SockaddrNetlink{
    69  		Family: unix.AF_NETLINK,
    70  		Groups: uint32(2),
    71  	}
    72  
    73  	fd, netlinkSocketErr := unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW, unix.NETLINK_KOBJECT_UEVENT)
    74  	if netlinkSocketErr != nil {
    75  		return multierror.Append(err, fmt.Errorf("error creating af_netlink socket: %w", netlinkSocketErr))
    76  	}
    77  	defer unix.Close(fd)
    78  
    79  	if bindErr := unix.Bind(fd, &addr); err != nil {
    80  		return multierror.Append(err, fmt.Errorf("error binding af_netlink file-descriptor to netlink socket: %w", bindErr))
    81  	}
    82  
    83  	for _, ue := range uevents {
    84  		if sendErr := unix.Sendto(fd, *ue.ToBytes(), 0, &addr); err != nil {
    85  			err = multierror.Append(err, fmt.Errorf("error sending uevent to container network ns: %w", sendErr))
    86  		}
    87  	}
    88  	return err
    89  }
    90  
    91  // readFileDescriptor will attempt to read the file-descriptor given a path
    92  func readFileDescriptor(path string) (int, error) {
    93  	fd, err := unix.Open(path, unix.O_RDONLY|unix.O_CLOEXEC, 0)
    94  	if err != nil {
    95  		return -1, err
    96  	}
    97  	return fd, nil
    98  }
    99  
   100  func getCurrentNetworkNamespace() (nsHandle, error) {
   101  	pid, tid := os.Getpid(), unix.Gettid()
   102  	taskPath := fmt.Sprintf("/proc/%d/task/%d/ns/net", pid, tid)
   103  	fd, err := readFileDescriptor(taskPath)
   104  	return nsHandle(fd), err
   105  }
   106  
   107  func getContainerNetworkNamespace(path string) (nsHandle, error) {
   108  	fd, err := readFileDescriptor(path)
   109  	return nsHandle(fd), err
   110  }
   111  

View as plain text