2024-07-05 17:04:27 +00:00
|
|
|
package netlink
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"syscall"
|
|
|
|
|
|
|
|
"github.com/vishvananda/netlink/nl"
|
|
|
|
"golang.org/x/sys/unix"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
sizeofXDPSocketRequest = 1 + 1 + 2 + 4 + 4 + 2*4
|
|
|
|
sizeofXDPSocket = 0x10
|
|
|
|
)
|
|
|
|
|
|
|
|
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L12
|
|
|
|
type xdpSocketRequest struct {
|
|
|
|
Family uint8
|
|
|
|
Protocol uint8
|
|
|
|
pad uint16
|
|
|
|
Ino uint32
|
|
|
|
Show uint32
|
|
|
|
Cookie [2]uint32
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *xdpSocketRequest) Serialize() []byte {
|
|
|
|
b := writeBuffer{Bytes: make([]byte, sizeofSocketRequest)}
|
|
|
|
b.Write(r.Family)
|
|
|
|
b.Write(r.Protocol)
|
|
|
|
native.PutUint16(b.Next(2), r.pad)
|
|
|
|
native.PutUint32(b.Next(4), r.Ino)
|
|
|
|
native.PutUint32(b.Next(4), r.Show)
|
|
|
|
native.PutUint32(b.Next(4), r.Cookie[0])
|
|
|
|
native.PutUint32(b.Next(4), r.Cookie[1])
|
|
|
|
return b.Bytes
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *xdpSocketRequest) Len() int { return sizeofXDPSocketRequest }
|
|
|
|
|
|
|
|
func (s *XDPSocket) deserialize(b []byte) error {
|
|
|
|
if len(b) < sizeofXDPSocket {
|
|
|
|
return fmt.Errorf("XDP socket data short read (%d); want %d", len(b), sizeofXDPSocket)
|
|
|
|
}
|
|
|
|
rb := readBuffer{Bytes: b}
|
|
|
|
s.Family = rb.Read()
|
|
|
|
s.Type = rb.Read()
|
|
|
|
s.pad = native.Uint16(rb.Next(2))
|
|
|
|
s.Ino = native.Uint32(rb.Next(4))
|
|
|
|
s.Cookie[0] = native.Uint32(rb.Next(4))
|
|
|
|
s.Cookie[1] = native.Uint32(rb.Next(4))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
Add ErrDumpInterrupted
Add a specific error to report that a netlink response had
NLM_F_DUMP_INTR set, indicating that the set of results may be
incomplete or inconsistent.
unix.EINTR was previously returned (with no results) when the
NLM_F_DUMP_INTR flag was set. Now, errors.Is(err, unix.EINTR) will
still work. But, this will be a breaking change for any code that's
checking for equality with unix.EINTR.
Return results with ErrDumpInterrupted. Results may be incomplete
or inconsistent, but give the caller the option of using them.
Look for NLM_F_DUMP_INTR in more places:
- linkSubscribeAt, neighSubscribeAt, routeSubscribeAt
- can do an initial dump, which may report inconsistent results
-> if there's an error callback, call it with ErrDumpInterrupted
- socketDiagXDPExecutor
- makes an NLM_F_DUMP request, without using Execute()
-> give it the same behaviour as functions that do use Execute()
Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-09-05 08:25:13 +00:00
|
|
|
// SocketXDPGetInfo returns the XDP socket identified by its inode number and/or
|
2024-07-05 17:04:27 +00:00
|
|
|
// socket cookie. Specify the cookie as SOCK_ANY_COOKIE if
|
Add ErrDumpInterrupted
Add a specific error to report that a netlink response had
NLM_F_DUMP_INTR set, indicating that the set of results may be
incomplete or inconsistent.
unix.EINTR was previously returned (with no results) when the
NLM_F_DUMP_INTR flag was set. Now, errors.Is(err, unix.EINTR) will
still work. But, this will be a breaking change for any code that's
checking for equality with unix.EINTR.
Return results with ErrDumpInterrupted. Results may be incomplete
or inconsistent, but give the caller the option of using them.
Look for NLM_F_DUMP_INTR in more places:
- linkSubscribeAt, neighSubscribeAt, routeSubscribeAt
- can do an initial dump, which may report inconsistent results
-> if there's an error callback, call it with ErrDumpInterrupted
- socketDiagXDPExecutor
- makes an NLM_F_DUMP request, without using Execute()
-> give it the same behaviour as functions that do use Execute()
Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-09-05 08:25:13 +00:00
|
|
|
//
|
|
|
|
// If the returned error is [ErrDumpInterrupted], the caller should retry.
|
2024-07-05 17:04:27 +00:00
|
|
|
func SocketXDPGetInfo(ino uint32, cookie uint64) (*XDPDiagInfoResp, error) {
|
|
|
|
// We have a problem here: dumping AF_XDP sockets currently does not support
|
|
|
|
// filtering. We thus need to dump all XSKs and then only filter afterwards
|
|
|
|
// :(
|
|
|
|
xsks, err := SocketDiagXDP()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
checkCookie := cookie != SOCK_ANY_COOKIE && cookie != 0
|
|
|
|
crumblingCookie := [2]uint32{uint32(cookie), uint32(cookie >> 32)}
|
|
|
|
checkIno := ino != 0
|
|
|
|
var xskinfo *XDPDiagInfoResp
|
|
|
|
for _, xsk := range xsks {
|
|
|
|
if checkIno && xsk.XDPDiagMsg.Ino != ino {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if checkCookie && xsk.XDPDiagMsg.Cookie != crumblingCookie {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if xskinfo != nil {
|
|
|
|
return nil, errors.New("multiple matching XDP sockets")
|
|
|
|
}
|
|
|
|
xskinfo = xsk
|
|
|
|
}
|
|
|
|
if xskinfo == nil {
|
|
|
|
return nil, errors.New("no matching XDP socket")
|
|
|
|
}
|
|
|
|
return xskinfo, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SocketDiagXDP requests XDP_DIAG_INFO for XDP family sockets.
|
Add ErrDumpInterrupted
Add a specific error to report that a netlink response had
NLM_F_DUMP_INTR set, indicating that the set of results may be
incomplete or inconsistent.
unix.EINTR was previously returned (with no results) when the
NLM_F_DUMP_INTR flag was set. Now, errors.Is(err, unix.EINTR) will
still work. But, this will be a breaking change for any code that's
checking for equality with unix.EINTR.
Return results with ErrDumpInterrupted. Results may be incomplete
or inconsistent, but give the caller the option of using them.
Look for NLM_F_DUMP_INTR in more places:
- linkSubscribeAt, neighSubscribeAt, routeSubscribeAt
- can do an initial dump, which may report inconsistent results
-> if there's an error callback, call it with ErrDumpInterrupted
- socketDiagXDPExecutor
- makes an NLM_F_DUMP request, without using Execute()
-> give it the same behaviour as functions that do use Execute()
Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-09-05 08:25:13 +00:00
|
|
|
//
|
|
|
|
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
|
|
|
|
// or incomplete.
|
2024-07-05 17:04:27 +00:00
|
|
|
func SocketDiagXDP() ([]*XDPDiagInfoResp, error) {
|
|
|
|
var result []*XDPDiagInfoResp
|
|
|
|
err := socketDiagXDPExecutor(func(m syscall.NetlinkMessage) error {
|
|
|
|
sockInfo := &XDPSocket{}
|
|
|
|
if err := sockInfo.deserialize(m.Data); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
attrs, err := nl.ParseRouteAttr(m.Data[sizeofXDPSocket:])
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
res, err := attrsToXDPDiagInfoResp(attrs, sockInfo)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
result = append(result, res)
|
|
|
|
return nil
|
|
|
|
})
|
Add ErrDumpInterrupted
Add a specific error to report that a netlink response had
NLM_F_DUMP_INTR set, indicating that the set of results may be
incomplete or inconsistent.
unix.EINTR was previously returned (with no results) when the
NLM_F_DUMP_INTR flag was set. Now, errors.Is(err, unix.EINTR) will
still work. But, this will be a breaking change for any code that's
checking for equality with unix.EINTR.
Return results with ErrDumpInterrupted. Results may be incomplete
or inconsistent, but give the caller the option of using them.
Look for NLM_F_DUMP_INTR in more places:
- linkSubscribeAt, neighSubscribeAt, routeSubscribeAt
- can do an initial dump, which may report inconsistent results
-> if there's an error callback, call it with ErrDumpInterrupted
- socketDiagXDPExecutor
- makes an NLM_F_DUMP request, without using Execute()
-> give it the same behaviour as functions that do use Execute()
Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-09-05 08:25:13 +00:00
|
|
|
if err != nil && !errors.Is(err, ErrDumpInterrupted) {
|
2024-07-05 17:04:27 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
Add ErrDumpInterrupted
Add a specific error to report that a netlink response had
NLM_F_DUMP_INTR set, indicating that the set of results may be
incomplete or inconsistent.
unix.EINTR was previously returned (with no results) when the
NLM_F_DUMP_INTR flag was set. Now, errors.Is(err, unix.EINTR) will
still work. But, this will be a breaking change for any code that's
checking for equality with unix.EINTR.
Return results with ErrDumpInterrupted. Results may be incomplete
or inconsistent, but give the caller the option of using them.
Look for NLM_F_DUMP_INTR in more places:
- linkSubscribeAt, neighSubscribeAt, routeSubscribeAt
- can do an initial dump, which may report inconsistent results
-> if there's an error callback, call it with ErrDumpInterrupted
- socketDiagXDPExecutor
- makes an NLM_F_DUMP request, without using Execute()
-> give it the same behaviour as functions that do use Execute()
Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-09-05 08:25:13 +00:00
|
|
|
return result, err
|
2024-07-05 17:04:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// socketDiagXDPExecutor requests XDP_DIAG_INFO for XDP family sockets.
|
|
|
|
func socketDiagXDPExecutor(receiver func(syscall.NetlinkMessage) error) error {
|
|
|
|
s, err := nl.Subscribe(unix.NETLINK_INET_DIAG)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer s.Close()
|
|
|
|
|
|
|
|
req := nl.NewNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP)
|
|
|
|
req.AddData(&xdpSocketRequest{
|
|
|
|
Family: unix.AF_XDP,
|
|
|
|
Show: XDP_SHOW_INFO | XDP_SHOW_RING_CFG | XDP_SHOW_UMEM | XDP_SHOW_STATS,
|
|
|
|
})
|
|
|
|
if err := s.Send(req); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
Add ErrDumpInterrupted
Add a specific error to report that a netlink response had
NLM_F_DUMP_INTR set, indicating that the set of results may be
incomplete or inconsistent.
unix.EINTR was previously returned (with no results) when the
NLM_F_DUMP_INTR flag was set. Now, errors.Is(err, unix.EINTR) will
still work. But, this will be a breaking change for any code that's
checking for equality with unix.EINTR.
Return results with ErrDumpInterrupted. Results may be incomplete
or inconsistent, but give the caller the option of using them.
Look for NLM_F_DUMP_INTR in more places:
- linkSubscribeAt, neighSubscribeAt, routeSubscribeAt
- can do an initial dump, which may report inconsistent results
-> if there's an error callback, call it with ErrDumpInterrupted
- socketDiagXDPExecutor
- makes an NLM_F_DUMP request, without using Execute()
-> give it the same behaviour as functions that do use Execute()
Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-09-05 08:25:13 +00:00
|
|
|
dumpIntr := false
|
2024-07-05 17:04:27 +00:00
|
|
|
loop:
|
|
|
|
for {
|
|
|
|
msgs, from, err := s.Receive()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if from.Pid != nl.PidKernel {
|
|
|
|
return fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, nl.PidKernel)
|
|
|
|
}
|
|
|
|
if len(msgs) == 0 {
|
|
|
|
return errors.New("no message nor error from netlink")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, m := range msgs {
|
Add ErrDumpInterrupted
Add a specific error to report that a netlink response had
NLM_F_DUMP_INTR set, indicating that the set of results may be
incomplete or inconsistent.
unix.EINTR was previously returned (with no results) when the
NLM_F_DUMP_INTR flag was set. Now, errors.Is(err, unix.EINTR) will
still work. But, this will be a breaking change for any code that's
checking for equality with unix.EINTR.
Return results with ErrDumpInterrupted. Results may be incomplete
or inconsistent, but give the caller the option of using them.
Look for NLM_F_DUMP_INTR in more places:
- linkSubscribeAt, neighSubscribeAt, routeSubscribeAt
- can do an initial dump, which may report inconsistent results
-> if there's an error callback, call it with ErrDumpInterrupted
- socketDiagXDPExecutor
- makes an NLM_F_DUMP request, without using Execute()
-> give it the same behaviour as functions that do use Execute()
Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-09-05 08:25:13 +00:00
|
|
|
if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 {
|
|
|
|
dumpIntr = true
|
|
|
|
}
|
2024-07-05 17:04:27 +00:00
|
|
|
switch m.Header.Type {
|
|
|
|
case unix.NLMSG_DONE:
|
|
|
|
break loop
|
|
|
|
case unix.NLMSG_ERROR:
|
|
|
|
error := int32(native.Uint32(m.Data[0:4]))
|
|
|
|
return syscall.Errno(-error)
|
|
|
|
}
|
|
|
|
if err := receiver(m); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
Add ErrDumpInterrupted
Add a specific error to report that a netlink response had
NLM_F_DUMP_INTR set, indicating that the set of results may be
incomplete or inconsistent.
unix.EINTR was previously returned (with no results) when the
NLM_F_DUMP_INTR flag was set. Now, errors.Is(err, unix.EINTR) will
still work. But, this will be a breaking change for any code that's
checking for equality with unix.EINTR.
Return results with ErrDumpInterrupted. Results may be incomplete
or inconsistent, but give the caller the option of using them.
Look for NLM_F_DUMP_INTR in more places:
- linkSubscribeAt, neighSubscribeAt, routeSubscribeAt
- can do an initial dump, which may report inconsistent results
-> if there's an error callback, call it with ErrDumpInterrupted
- socketDiagXDPExecutor
- makes an NLM_F_DUMP request, without using Execute()
-> give it the same behaviour as functions that do use Execute()
Signed-off-by: Rob Murray <rob.murray@docker.com>
2024-09-05 08:25:13 +00:00
|
|
|
if dumpIntr {
|
|
|
|
return ErrDumpInterrupted
|
|
|
|
}
|
2024-07-05 17:04:27 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func attrsToXDPDiagInfoResp(attrs []syscall.NetlinkRouteAttr, sockInfo *XDPSocket) (*XDPDiagInfoResp, error) {
|
|
|
|
resp := &XDPDiagInfoResp{
|
|
|
|
XDPDiagMsg: sockInfo,
|
|
|
|
XDPInfo: &XDPInfo{},
|
|
|
|
}
|
|
|
|
for _, a := range attrs {
|
|
|
|
switch a.Attr.Type {
|
|
|
|
case XDP_DIAG_INFO:
|
|
|
|
resp.XDPInfo.Ifindex = native.Uint32(a.Value[0:4])
|
|
|
|
resp.XDPInfo.QueueID = native.Uint32(a.Value[4:8])
|
|
|
|
case XDP_DIAG_UID:
|
|
|
|
resp.XDPInfo.UID = native.Uint32(a.Value[0:4])
|
|
|
|
case XDP_DIAG_RX_RING:
|
|
|
|
resp.XDPInfo.RxRingEntries = native.Uint32(a.Value[0:4])
|
|
|
|
case XDP_DIAG_TX_RING:
|
|
|
|
resp.XDPInfo.TxRingEntries = native.Uint32(a.Value[0:4])
|
|
|
|
case XDP_DIAG_UMEM_FILL_RING:
|
|
|
|
resp.XDPInfo.UmemFillRingEntries = native.Uint32(a.Value[0:4])
|
|
|
|
case XDP_DIAG_UMEM_COMPLETION_RING:
|
|
|
|
resp.XDPInfo.UmemCompletionRingEntries = native.Uint32(a.Value[0:4])
|
|
|
|
case XDP_DIAG_UMEM:
|
|
|
|
umem := &XDPDiagUmem{}
|
|
|
|
if err := umem.deserialize(a.Value); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
resp.XDPInfo.Umem = umem
|
|
|
|
case XDP_DIAG_STATS:
|
|
|
|
stats := &XDPDiagStats{}
|
|
|
|
if err := stats.deserialize(a.Value); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
resp.XDPInfo.Stats = stats
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return resp, nil
|
|
|
|
}
|