-
Notifications
You must be signed in to change notification settings - Fork 749
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
supports AF_XDP socket diagnosis; skip XSK diag test if kernel doesn'…
…t support XSK diag
- Loading branch information
Showing
5 changed files
with
391 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
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 | ||
} | ||
|
||
// XDPSocketGet returns the XDP socket identified by its inode number and/or | ||
// socket cookie. Specify the cookie as SOCK_ANY_COOKIE if | ||
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. | ||
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 { | ||
return nil, err | ||
} | ||
return result, nil | ||
} | ||
|
||
// 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 | ||
} | ||
|
||
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 { | ||
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 | ||
} | ||
} | ||
} | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
//go:build linux | ||
// +build linux | ||
|
||
package netlink | ||
|
||
import ( | ||
"os" | ||
"testing" | ||
|
||
"golang.org/x/sys/unix" | ||
) | ||
|
||
func TestSocketXDPGetInfo(t *testing.T) { | ||
xdpsockfd, err := unix.Socket(unix.AF_XDP, unix.SOCK_RAW, 0) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer unix.Close(xdpsockfd) | ||
|
||
wantFamily := unix.AF_XDP | ||
|
||
var xdpsockstat unix.Stat_t | ||
err = unix.Fstat(xdpsockfd, &xdpsockstat) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
wantIno := xdpsockstat.Ino | ||
|
||
result, err := SocketXDPGetInfo(uint32(wantIno), SOCK_ANY_COOKIE) | ||
if err != nil { | ||
if os.IsNotExist(err) { | ||
t.Skip("kernel lacks support for AF_XDP socket diagnosis") | ||
} | ||
t.Fatal(err) | ||
} | ||
|
||
if got := result.XDPDiagMsg.Family; got != uint8(wantFamily) { | ||
t.Fatalf("protocol family = %v, want %v", got, wantFamily) | ||
} | ||
if got := result.XDPDiagMsg.Ino; got != uint32(wantIno) { | ||
t.Fatalf("protocol ino = %v, want %v", got, wantIno) | ||
} | ||
if result.XDPInfo == nil { | ||
t.Fatalf("want non-nil XDPInfo, got nil") | ||
} | ||
if got := result.XDPInfo.Ifindex; got != 0 { | ||
t.Fatalf("ifindex = %v, want 0", got) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package netlink | ||
|
||
import "github.com/vishvananda/netlink/nl" | ||
|
||
const SOCK_ANY_COOKIE = uint64(nl.TCPDIAG_NOCOOKIE)<<32 + uint64(nl.TCPDIAG_NOCOOKIE) | ||
|
||
// XDP diagnosis show flag constants to request particular information elements. | ||
const ( | ||
XDP_SHOW_INFO = 1 << iota | ||
XDP_SHOW_RING_CFG | ||
XDP_SHOW_UMEM | ||
XDP_SHOW_MEMINFO | ||
XDP_SHOW_STATS | ||
) | ||
|
||
// XDP diag element constants | ||
const ( | ||
XDP_DIAG_NONE = iota | ||
XDP_DIAG_INFO // when using XDP_SHOW_INFO | ||
XDP_DIAG_UID // when using XDP_SHOW_INFO | ||
XDP_DIAG_RX_RING // when using XDP_SHOW_RING_CFG | ||
XDP_DIAG_TX_RING // when using XDP_SHOW_RING_CFG | ||
XDP_DIAG_UMEM // when using XDP_SHOW_UMEM | ||
XDP_DIAG_UMEM_FILL_RING // when using XDP_SHOW_UMEM | ||
XDP_DIAG_UMEM_COMPLETION_RING // when using XDP_SHOW_UMEM | ||
XDP_DIAG_MEMINFO // when using XDP_SHOW_MEMINFO | ||
XDP_DIAG_STATS // when using XDP_SHOW_STATS | ||
) | ||
|
||
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L21 | ||
type XDPDiagInfoResp struct { | ||
XDPDiagMsg *XDPSocket | ||
XDPInfo *XDPInfo | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package netlink | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
) | ||
|
||
const ( | ||
xdrDiagUmemLen = 8 + 8*4 | ||
xdrDiagStatsLen = 6 * 8 | ||
) | ||
|
||
func (x *XDPDiagUmem) deserialize(b []byte) error { | ||
if len(b) < xdrDiagUmemLen { | ||
return fmt.Errorf("XDP umem diagnosis data short read (%d); want %d", len(b), xdrDiagUmemLen) | ||
} | ||
|
||
rb := bytes.NewBuffer(b) | ||
x.Size = native.Uint64(rb.Next(8)) | ||
x.ID = native.Uint32(rb.Next(4)) | ||
x.NumPages = native.Uint32(rb.Next(4)) | ||
x.ChunkSize = native.Uint32(rb.Next(4)) | ||
x.Headroom = native.Uint32(rb.Next(4)) | ||
x.Ifindex = native.Uint32(rb.Next(4)) | ||
x.QueueID = native.Uint32(rb.Next(4)) | ||
x.Flags = native.Uint32(rb.Next(4)) | ||
x.Refs = native.Uint32(rb.Next(4)) | ||
|
||
return nil | ||
} | ||
|
||
func (x *XDPDiagStats) deserialize(b []byte) error { | ||
if len(b) < xdrDiagStatsLen { | ||
return fmt.Errorf("XDP diagnosis statistics short read (%d); want %d", len(b), xdrDiagStatsLen) | ||
} | ||
|
||
rb := bytes.NewBuffer(b) | ||
x.RxDropped = native.Uint64(rb.Next(8)) | ||
x.RxInvalid = native.Uint64(rb.Next(8)) | ||
x.RxFull = native.Uint64(rb.Next(8)) | ||
x.FillRingEmpty = native.Uint64(rb.Next(8)) | ||
x.TxInvalid = native.Uint64(rb.Next(8)) | ||
x.TxRingEmpty = native.Uint64(rb.Next(8)) | ||
|
||
return nil | ||
} |