Skip to content

Commit d13535d

Browse files
thediveoaboch
authored andcommitted
supports AF_XDP socket diagnosis; skip XSK diag test if kernel doesn't support XSK diag
1 parent aed23db commit d13535d

File tree

5 files changed

+391
-0
lines changed

5 files changed

+391
-0
lines changed

socket.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,70 @@ type UnixSocket struct {
3535
INode uint32
3636
Cookie [2]uint32
3737
}
38+
39+
// XDPSocket represents an XDP socket (and the common diagnosis part in
40+
// particular). Please note that in contrast to [UnixSocket] the XDPSocket type
41+
// does not feature “State” information.
42+
type XDPSocket struct {
43+
// xdp_diag_msg
44+
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L21
45+
Family uint8
46+
Type uint8
47+
pad uint16
48+
Ino uint32
49+
Cookie [2]uint32
50+
}
51+
52+
type XDPInfo struct {
53+
// XDP_DIAG_INFO/xdp_diag_info
54+
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L51
55+
Ifindex uint32
56+
QueueID uint32
57+
58+
// XDP_DIAG_UID
59+
UID uint32
60+
61+
// XDP_RX_RING
62+
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L56
63+
RxRingEntries uint32
64+
TxRingEntries uint32
65+
UmemFillRingEntries uint32
66+
UmemCompletionRingEntries uint32
67+
68+
// XDR_DIAG_UMEM
69+
Umem *XDPDiagUmem
70+
71+
// XDR_DIAG_STATS
72+
Stats *XDPDiagStats
73+
}
74+
75+
const (
76+
XDP_DU_F_ZEROCOPY = 1 << iota
77+
)
78+
79+
// XDPDiagUmem describes the umem attached to an XDP socket.
80+
//
81+
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L62
82+
type XDPDiagUmem struct {
83+
Size uint64
84+
ID uint32
85+
NumPages uint32
86+
ChunkSize uint32
87+
Headroom uint32
88+
Ifindex uint32
89+
QueueID uint32
90+
Flags uint32
91+
Refs uint32
92+
}
93+
94+
// XDPDiagStats contains ring statistics for an XDP socket.
95+
//
96+
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L74
97+
type XDPDiagStats struct {
98+
RxDropped uint64
99+
RxInvalid uint64
100+
RxFull uint64
101+
FillRingEmpty uint64
102+
TxInvalid uint64
103+
TxRingEmpty uint64
104+
}

socket_xdp_linux.go

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package netlink
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"syscall"
7+
8+
"github.com/vishvananda/netlink/nl"
9+
"golang.org/x/sys/unix"
10+
)
11+
12+
const (
13+
sizeofXDPSocketRequest = 1 + 1 + 2 + 4 + 4 + 2*4
14+
sizeofXDPSocket = 0x10
15+
)
16+
17+
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L12
18+
type xdpSocketRequest struct {
19+
Family uint8
20+
Protocol uint8
21+
pad uint16
22+
Ino uint32
23+
Show uint32
24+
Cookie [2]uint32
25+
}
26+
27+
func (r *xdpSocketRequest) Serialize() []byte {
28+
b := writeBuffer{Bytes: make([]byte, sizeofSocketRequest)}
29+
b.Write(r.Family)
30+
b.Write(r.Protocol)
31+
native.PutUint16(b.Next(2), r.pad)
32+
native.PutUint32(b.Next(4), r.Ino)
33+
native.PutUint32(b.Next(4), r.Show)
34+
native.PutUint32(b.Next(4), r.Cookie[0])
35+
native.PutUint32(b.Next(4), r.Cookie[1])
36+
return b.Bytes
37+
}
38+
39+
func (r *xdpSocketRequest) Len() int { return sizeofXDPSocketRequest }
40+
41+
func (s *XDPSocket) deserialize(b []byte) error {
42+
if len(b) < sizeofXDPSocket {
43+
return fmt.Errorf("XDP socket data short read (%d); want %d", len(b), sizeofXDPSocket)
44+
}
45+
rb := readBuffer{Bytes: b}
46+
s.Family = rb.Read()
47+
s.Type = rb.Read()
48+
s.pad = native.Uint16(rb.Next(2))
49+
s.Ino = native.Uint32(rb.Next(4))
50+
s.Cookie[0] = native.Uint32(rb.Next(4))
51+
s.Cookie[1] = native.Uint32(rb.Next(4))
52+
return nil
53+
}
54+
55+
// XDPSocketGet returns the XDP socket identified by its inode number and/or
56+
// socket cookie. Specify the cookie as SOCK_ANY_COOKIE if
57+
func SocketXDPGetInfo(ino uint32, cookie uint64) (*XDPDiagInfoResp, error) {
58+
// We have a problem here: dumping AF_XDP sockets currently does not support
59+
// filtering. We thus need to dump all XSKs and then only filter afterwards
60+
// :(
61+
xsks, err := SocketDiagXDP()
62+
if err != nil {
63+
return nil, err
64+
}
65+
checkCookie := cookie != SOCK_ANY_COOKIE && cookie != 0
66+
crumblingCookie := [2]uint32{uint32(cookie), uint32(cookie >> 32)}
67+
checkIno := ino != 0
68+
var xskinfo *XDPDiagInfoResp
69+
for _, xsk := range xsks {
70+
if checkIno && xsk.XDPDiagMsg.Ino != ino {
71+
continue
72+
}
73+
if checkCookie && xsk.XDPDiagMsg.Cookie != crumblingCookie {
74+
continue
75+
}
76+
if xskinfo != nil {
77+
return nil, errors.New("multiple matching XDP sockets")
78+
}
79+
xskinfo = xsk
80+
}
81+
if xskinfo == nil {
82+
return nil, errors.New("no matching XDP socket")
83+
}
84+
return xskinfo, nil
85+
}
86+
87+
// SocketDiagXDP requests XDP_DIAG_INFO for XDP family sockets.
88+
func SocketDiagXDP() ([]*XDPDiagInfoResp, error) {
89+
var result []*XDPDiagInfoResp
90+
err := socketDiagXDPExecutor(func(m syscall.NetlinkMessage) error {
91+
sockInfo := &XDPSocket{}
92+
if err := sockInfo.deserialize(m.Data); err != nil {
93+
return err
94+
}
95+
attrs, err := nl.ParseRouteAttr(m.Data[sizeofXDPSocket:])
96+
if err != nil {
97+
return err
98+
}
99+
100+
res, err := attrsToXDPDiagInfoResp(attrs, sockInfo)
101+
if err != nil {
102+
return err
103+
}
104+
105+
result = append(result, res)
106+
return nil
107+
})
108+
if err != nil {
109+
return nil, err
110+
}
111+
return result, nil
112+
}
113+
114+
// socketDiagXDPExecutor requests XDP_DIAG_INFO for XDP family sockets.
115+
func socketDiagXDPExecutor(receiver func(syscall.NetlinkMessage) error) error {
116+
s, err := nl.Subscribe(unix.NETLINK_INET_DIAG)
117+
if err != nil {
118+
return err
119+
}
120+
defer s.Close()
121+
122+
req := nl.NewNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, unix.NLM_F_DUMP)
123+
req.AddData(&xdpSocketRequest{
124+
Family: unix.AF_XDP,
125+
Show: XDP_SHOW_INFO | XDP_SHOW_RING_CFG | XDP_SHOW_UMEM | XDP_SHOW_STATS,
126+
})
127+
if err := s.Send(req); err != nil {
128+
return err
129+
}
130+
131+
loop:
132+
for {
133+
msgs, from, err := s.Receive()
134+
if err != nil {
135+
return err
136+
}
137+
if from.Pid != nl.PidKernel {
138+
return fmt.Errorf("Wrong sender portid %d, expected %d", from.Pid, nl.PidKernel)
139+
}
140+
if len(msgs) == 0 {
141+
return errors.New("no message nor error from netlink")
142+
}
143+
144+
for _, m := range msgs {
145+
switch m.Header.Type {
146+
case unix.NLMSG_DONE:
147+
break loop
148+
case unix.NLMSG_ERROR:
149+
error := int32(native.Uint32(m.Data[0:4]))
150+
return syscall.Errno(-error)
151+
}
152+
if err := receiver(m); err != nil {
153+
return err
154+
}
155+
}
156+
}
157+
return nil
158+
}
159+
160+
func attrsToXDPDiagInfoResp(attrs []syscall.NetlinkRouteAttr, sockInfo *XDPSocket) (*XDPDiagInfoResp, error) {
161+
resp := &XDPDiagInfoResp{
162+
XDPDiagMsg: sockInfo,
163+
XDPInfo: &XDPInfo{},
164+
}
165+
for _, a := range attrs {
166+
switch a.Attr.Type {
167+
case XDP_DIAG_INFO:
168+
resp.XDPInfo.Ifindex = native.Uint32(a.Value[0:4])
169+
resp.XDPInfo.QueueID = native.Uint32(a.Value[4:8])
170+
case XDP_DIAG_UID:
171+
resp.XDPInfo.UID = native.Uint32(a.Value[0:4])
172+
case XDP_DIAG_RX_RING:
173+
resp.XDPInfo.RxRingEntries = native.Uint32(a.Value[0:4])
174+
case XDP_DIAG_TX_RING:
175+
resp.XDPInfo.TxRingEntries = native.Uint32(a.Value[0:4])
176+
case XDP_DIAG_UMEM_FILL_RING:
177+
resp.XDPInfo.UmemFillRingEntries = native.Uint32(a.Value[0:4])
178+
case XDP_DIAG_UMEM_COMPLETION_RING:
179+
resp.XDPInfo.UmemCompletionRingEntries = native.Uint32(a.Value[0:4])
180+
case XDP_DIAG_UMEM:
181+
umem := &XDPDiagUmem{}
182+
if err := umem.deserialize(a.Value); err != nil {
183+
return nil, err
184+
}
185+
resp.XDPInfo.Umem = umem
186+
case XDP_DIAG_STATS:
187+
stats := &XDPDiagStats{}
188+
if err := stats.deserialize(a.Value); err != nil {
189+
return nil, err
190+
}
191+
resp.XDPInfo.Stats = stats
192+
}
193+
}
194+
return resp, nil
195+
}

socket_xdp_linux_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//go:build linux
2+
// +build linux
3+
4+
package netlink
5+
6+
import (
7+
"os"
8+
"testing"
9+
10+
"golang.org/x/sys/unix"
11+
)
12+
13+
func TestSocketXDPGetInfo(t *testing.T) {
14+
xdpsockfd, err := unix.Socket(unix.AF_XDP, unix.SOCK_RAW, 0)
15+
if err != nil {
16+
t.Fatal(err)
17+
}
18+
defer unix.Close(xdpsockfd)
19+
20+
wantFamily := unix.AF_XDP
21+
22+
var xdpsockstat unix.Stat_t
23+
err = unix.Fstat(xdpsockfd, &xdpsockstat)
24+
if err != nil {
25+
t.Fatal(err)
26+
}
27+
wantIno := xdpsockstat.Ino
28+
29+
result, err := SocketXDPGetInfo(uint32(wantIno), SOCK_ANY_COOKIE)
30+
if err != nil {
31+
if os.IsNotExist(err) {
32+
t.Skip("kernel lacks support for AF_XDP socket diagnosis")
33+
}
34+
t.Fatal(err)
35+
}
36+
37+
if got := result.XDPDiagMsg.Family; got != uint8(wantFamily) {
38+
t.Fatalf("protocol family = %v, want %v", got, wantFamily)
39+
}
40+
if got := result.XDPDiagMsg.Ino; got != uint32(wantIno) {
41+
t.Fatalf("protocol ino = %v, want %v", got, wantIno)
42+
}
43+
if result.XDPInfo == nil {
44+
t.Fatalf("want non-nil XDPInfo, got nil")
45+
}
46+
if got := result.XDPInfo.Ifindex; got != 0 {
47+
t.Fatalf("ifindex = %v, want 0", got)
48+
}
49+
}

xdp_diag.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package netlink
2+
3+
import "github.com/vishvananda/netlink/nl"
4+
5+
const SOCK_ANY_COOKIE = uint64(nl.TCPDIAG_NOCOOKIE)<<32 + uint64(nl.TCPDIAG_NOCOOKIE)
6+
7+
// XDP diagnosis show flag constants to request particular information elements.
8+
const (
9+
XDP_SHOW_INFO = 1 << iota
10+
XDP_SHOW_RING_CFG
11+
XDP_SHOW_UMEM
12+
XDP_SHOW_MEMINFO
13+
XDP_SHOW_STATS
14+
)
15+
16+
// XDP diag element constants
17+
const (
18+
XDP_DIAG_NONE = iota
19+
XDP_DIAG_INFO // when using XDP_SHOW_INFO
20+
XDP_DIAG_UID // when using XDP_SHOW_INFO
21+
XDP_DIAG_RX_RING // when using XDP_SHOW_RING_CFG
22+
XDP_DIAG_TX_RING // when using XDP_SHOW_RING_CFG
23+
XDP_DIAG_UMEM // when using XDP_SHOW_UMEM
24+
XDP_DIAG_UMEM_FILL_RING // when using XDP_SHOW_UMEM
25+
XDP_DIAG_UMEM_COMPLETION_RING // when using XDP_SHOW_UMEM
26+
XDP_DIAG_MEMINFO // when using XDP_SHOW_MEMINFO
27+
XDP_DIAG_STATS // when using XDP_SHOW_STATS
28+
)
29+
30+
// https://elixir.bootlin.com/linux/v6.2/source/include/uapi/linux/xdp_diag.h#L21
31+
type XDPDiagInfoResp struct {
32+
XDPDiagMsg *XDPSocket
33+
XDPInfo *XDPInfo
34+
}

xdp_linux.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package netlink
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
)
7+
8+
const (
9+
xdrDiagUmemLen = 8 + 8*4
10+
xdrDiagStatsLen = 6 * 8
11+
)
12+
13+
func (x *XDPDiagUmem) deserialize(b []byte) error {
14+
if len(b) < xdrDiagUmemLen {
15+
return fmt.Errorf("XDP umem diagnosis data short read (%d); want %d", len(b), xdrDiagUmemLen)
16+
}
17+
18+
rb := bytes.NewBuffer(b)
19+
x.Size = native.Uint64(rb.Next(8))
20+
x.ID = native.Uint32(rb.Next(4))
21+
x.NumPages = native.Uint32(rb.Next(4))
22+
x.ChunkSize = native.Uint32(rb.Next(4))
23+
x.Headroom = native.Uint32(rb.Next(4))
24+
x.Ifindex = native.Uint32(rb.Next(4))
25+
x.QueueID = native.Uint32(rb.Next(4))
26+
x.Flags = native.Uint32(rb.Next(4))
27+
x.Refs = native.Uint32(rb.Next(4))
28+
29+
return nil
30+
}
31+
32+
func (x *XDPDiagStats) deserialize(b []byte) error {
33+
if len(b) < xdrDiagStatsLen {
34+
return fmt.Errorf("XDP diagnosis statistics short read (%d); want %d", len(b), xdrDiagStatsLen)
35+
}
36+
37+
rb := bytes.NewBuffer(b)
38+
x.RxDropped = native.Uint64(rb.Next(8))
39+
x.RxInvalid = native.Uint64(rb.Next(8))
40+
x.RxFull = native.Uint64(rb.Next(8))
41+
x.FillRingEmpty = native.Uint64(rb.Next(8))
42+
x.TxInvalid = native.Uint64(rb.Next(8))
43+
x.TxRingEmpty = native.Uint64(rb.Next(8))
44+
45+
return nil
46+
}

0 commit comments

Comments
 (0)