//go:build linux package udevproxy import ( "fmt" "os" "runtime" "github.com/hashicorp/go-multierror" "golang.org/x/sys/unix" "edge-infra.dev/pkg/lib/kernel/udev" ) type UdevProxy interface { Send(path string, uevents []*udev.UEvent) error } type udevProxy struct{} func NewUdevProxy() UdevProxy { return &udevProxy{} } type nsHandle int func (ns *nsHandle) Close() error { if err := unix.Close(int(*ns)); err != nil { return err } *ns = -1 return nil } func (ns *nsHandle) Set() error { return unix.Setns(int(*ns), unix.CLONE_NEWNET) } // sendUEventsToContainer takes the path to container network namespace // and replays the udev events to netlink. func (up udevProxy) Send(path string, uevents []*udev.UEvent) (err error) { runtime.LockOSThread() defer runtime.UnlockOSThread() currentNs, err := getCurrentNetworkNamespace() if err != nil { return fmt.Errorf("error getting current network namespace: %w", err) } containerNs, err := getContainerNetworkNamespace(path) if err != nil { return fmt.Errorf("error fetching container file-descriptor: %w", err) } defer func() { if ctrNsErr := containerNs.Close(); err != nil { err = multierror.Append(err, ctrNsErr) } if currentNsErr := currentNs.Set(); err != nil { err = multierror.Append(err, currentNsErr) } }() if nsSetErr := containerNs.Set(); err != nil { return multierror.Append(err, fmt.Errorf("error setting container network namespace: %w", nsSetErr)) } addr := unix.SockaddrNetlink{ Family: unix.AF_NETLINK, Groups: uint32(2), } fd, netlinkSocketErr := unix.Socket(unix.AF_NETLINK, unix.SOCK_RAW, unix.NETLINK_KOBJECT_UEVENT) if netlinkSocketErr != nil { return multierror.Append(err, fmt.Errorf("error creating af_netlink socket: %w", netlinkSocketErr)) } defer unix.Close(fd) if bindErr := unix.Bind(fd, &addr); err != nil { return multierror.Append(err, fmt.Errorf("error binding af_netlink file-descriptor to netlink socket: %w", bindErr)) } for _, ue := range uevents { if sendErr := unix.Sendto(fd, *ue.ToBytes(), 0, &addr); err != nil { err = multierror.Append(err, fmt.Errorf("error sending uevent to container network ns: %w", sendErr)) } } return err } // readFileDescriptor will attempt to read the file-descriptor given a path func readFileDescriptor(path string) (int, error) { fd, err := unix.Open(path, unix.O_RDONLY|unix.O_CLOEXEC, 0) if err != nil { return -1, err } return fd, nil } func getCurrentNetworkNamespace() (nsHandle, error) { pid, tid := os.Getpid(), unix.Gettid() taskPath := fmt.Sprintf("/proc/%d/task/%d/ns/net", pid, tid) fd, err := readFileDescriptor(taskPath) return nsHandle(fd), err } func getContainerNetworkNamespace(path string) (nsHandle, error) { fd, err := readFileDescriptor(path) return nsHandle(fd), err }