mirror of https://github.com/vishvananda/netlink
208 lines
5.4 KiB
Go
208 lines
5.4 KiB
Go
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
|
|
}
|
|
|
|
// SocketXDPGetInfo returns the XDP socket identified by its inode number and/or
|
|
// socket cookie. Specify the cookie as SOCK_ANY_COOKIE if
|
|
//
|
|
// If the returned error is [ErrDumpInterrupted], the caller should retry.
|
|
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.
|
|
//
|
|
// If the returned error is [ErrDumpInterrupted], results may be inconsistent
|
|
// or incomplete.
|
|
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
|
|
})
|
|
if err != nil && !errors.Is(err, ErrDumpInterrupted) {
|
|
return nil, err
|
|
}
|
|
return result, err
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
dumpIntr := false
|
|
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 {
|
|
if m.Header.Flags&unix.NLM_F_DUMP_INTR != 0 {
|
|
dumpIntr = true
|
|
}
|
|
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
|
|
}
|
|
}
|
|
}
|
|
if dumpIntr {
|
|
return ErrDumpInterrupted
|
|
}
|
|
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
|
|
}
|