1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* XDP sockets monitoring support |
3 | * |
4 | * Copyright(c) 2019 Intel Corporation. |
5 | * |
6 | * Author: Björn Töpel <bjorn.topel@intel.com> |
7 | */ |
8 | |
9 | #include <linux/module.h> |
10 | #include <net/xdp_sock.h> |
11 | #include <linux/xdp_diag.h> |
12 | #include <linux/sock_diag.h> |
13 | |
14 | #include "xsk_queue.h" |
15 | #include "xsk.h" |
16 | |
17 | static int xsk_diag_put_info(const struct xdp_sock *xs, struct sk_buff *nlskb) |
18 | { |
19 | struct xdp_diag_info di = {}; |
20 | |
21 | di.ifindex = xs->dev ? xs->dev->ifindex : 0; |
22 | di.queue_id = xs->queue_id; |
23 | return nla_put(skb: nlskb, attrtype: XDP_DIAG_INFO, attrlen: sizeof(di), data: &di); |
24 | } |
25 | |
26 | static int xsk_diag_put_ring(const struct xsk_queue *queue, int nl_type, |
27 | struct sk_buff *nlskb) |
28 | { |
29 | struct xdp_diag_ring dr = {}; |
30 | |
31 | dr.entries = queue->nentries; |
32 | return nla_put(skb: nlskb, attrtype: nl_type, attrlen: sizeof(dr), data: &dr); |
33 | } |
34 | |
35 | static int xsk_diag_put_rings_cfg(const struct xdp_sock *xs, |
36 | struct sk_buff *nlskb) |
37 | { |
38 | int err = 0; |
39 | |
40 | if (xs->rx) |
41 | err = xsk_diag_put_ring(queue: xs->rx, nl_type: XDP_DIAG_RX_RING, nlskb); |
42 | if (!err && xs->tx) |
43 | err = xsk_diag_put_ring(queue: xs->tx, nl_type: XDP_DIAG_TX_RING, nlskb); |
44 | return err; |
45 | } |
46 | |
47 | static int xsk_diag_put_umem(const struct xdp_sock *xs, struct sk_buff *nlskb) |
48 | { |
49 | struct xsk_buff_pool *pool = xs->pool; |
50 | struct xdp_umem *umem = xs->umem; |
51 | struct xdp_diag_umem du = {}; |
52 | int err; |
53 | |
54 | if (!umem) |
55 | return 0; |
56 | |
57 | du.id = umem->id; |
58 | du.size = umem->size; |
59 | du.num_pages = umem->npgs; |
60 | du.chunk_size = umem->chunk_size; |
61 | du.headroom = umem->headroom; |
62 | du.ifindex = (pool && pool->netdev) ? pool->netdev->ifindex : 0; |
63 | du.queue_id = pool ? pool->queue_id : 0; |
64 | du.flags = 0; |
65 | if (umem->zc) |
66 | du.flags |= XDP_DU_F_ZEROCOPY; |
67 | du.refs = refcount_read(r: &umem->users); |
68 | |
69 | err = nla_put(skb: nlskb, attrtype: XDP_DIAG_UMEM, attrlen: sizeof(du), data: &du); |
70 | if (!err && pool && pool->fq) |
71 | err = xsk_diag_put_ring(queue: pool->fq, |
72 | nl_type: XDP_DIAG_UMEM_FILL_RING, nlskb); |
73 | if (!err && pool && pool->cq) |
74 | err = xsk_diag_put_ring(queue: pool->cq, |
75 | nl_type: XDP_DIAG_UMEM_COMPLETION_RING, nlskb); |
76 | return err; |
77 | } |
78 | |
79 | static int xsk_diag_put_stats(const struct xdp_sock *xs, struct sk_buff *nlskb) |
80 | { |
81 | struct xdp_diag_stats du = {}; |
82 | |
83 | du.n_rx_dropped = xs->rx_dropped; |
84 | du.n_rx_invalid = xskq_nb_invalid_descs(q: xs->rx); |
85 | du.n_rx_full = xs->rx_queue_full; |
86 | du.n_fill_ring_empty = xs->pool ? xskq_nb_queue_empty_descs(q: xs->pool->fq) : 0; |
87 | du.n_tx_invalid = xskq_nb_invalid_descs(q: xs->tx); |
88 | du.n_tx_ring_empty = xskq_nb_queue_empty_descs(q: xs->tx); |
89 | return nla_put(skb: nlskb, attrtype: XDP_DIAG_STATS, attrlen: sizeof(du), data: &du); |
90 | } |
91 | |
92 | static int xsk_diag_fill(struct sock *sk, struct sk_buff *nlskb, |
93 | struct xdp_diag_req *req, |
94 | struct user_namespace *user_ns, |
95 | u32 portid, u32 seq, u32 flags, int sk_ino) |
96 | { |
97 | struct xdp_sock *xs = xdp_sk(sk); |
98 | struct xdp_diag_msg *msg; |
99 | struct nlmsghdr *nlh; |
100 | |
101 | nlh = nlmsg_put(skb: nlskb, portid, seq, SOCK_DIAG_BY_FAMILY, payload: sizeof(*msg), |
102 | flags); |
103 | if (!nlh) |
104 | return -EMSGSIZE; |
105 | |
106 | msg = nlmsg_data(nlh); |
107 | memset(msg, 0, sizeof(*msg)); |
108 | msg->xdiag_family = AF_XDP; |
109 | msg->xdiag_type = sk->sk_type; |
110 | msg->xdiag_ino = sk_ino; |
111 | sock_diag_save_cookie(sk, cookie: msg->xdiag_cookie); |
112 | |
113 | mutex_lock(&xs->mutex); |
114 | if (READ_ONCE(xs->state) == XSK_UNBOUND) |
115 | goto out_nlmsg_trim; |
116 | |
117 | if ((req->xdiag_show & XDP_SHOW_INFO) && xsk_diag_put_info(xs, nlskb)) |
118 | goto out_nlmsg_trim; |
119 | |
120 | if ((req->xdiag_show & XDP_SHOW_INFO) && |
121 | nla_put_u32(skb: nlskb, attrtype: XDP_DIAG_UID, |
122 | value: from_kuid_munged(to: user_ns, uid: sock_i_uid(sk)))) |
123 | goto out_nlmsg_trim; |
124 | |
125 | if ((req->xdiag_show & XDP_SHOW_RING_CFG) && |
126 | xsk_diag_put_rings_cfg(xs, nlskb)) |
127 | goto out_nlmsg_trim; |
128 | |
129 | if ((req->xdiag_show & XDP_SHOW_UMEM) && |
130 | xsk_diag_put_umem(xs, nlskb)) |
131 | goto out_nlmsg_trim; |
132 | |
133 | if ((req->xdiag_show & XDP_SHOW_MEMINFO) && |
134 | sock_diag_put_meminfo(sk, skb: nlskb, attr: XDP_DIAG_MEMINFO)) |
135 | goto out_nlmsg_trim; |
136 | |
137 | if ((req->xdiag_show & XDP_SHOW_STATS) && |
138 | xsk_diag_put_stats(xs, nlskb)) |
139 | goto out_nlmsg_trim; |
140 | |
141 | mutex_unlock(lock: &xs->mutex); |
142 | nlmsg_end(skb: nlskb, nlh); |
143 | return 0; |
144 | |
145 | out_nlmsg_trim: |
146 | mutex_unlock(lock: &xs->mutex); |
147 | nlmsg_cancel(skb: nlskb, nlh); |
148 | return -EMSGSIZE; |
149 | } |
150 | |
151 | static int xsk_diag_dump(struct sk_buff *nlskb, struct netlink_callback *cb) |
152 | { |
153 | struct xdp_diag_req *req = nlmsg_data(nlh: cb->nlh); |
154 | struct net *net = sock_net(sk: nlskb->sk); |
155 | int num = 0, s_num = cb->args[0]; |
156 | struct sock *sk; |
157 | |
158 | mutex_lock(&net->xdp.lock); |
159 | |
160 | sk_for_each(sk, &net->xdp.list) { |
161 | if (!net_eq(net1: sock_net(sk), net2: net)) |
162 | continue; |
163 | if (num++ < s_num) |
164 | continue; |
165 | |
166 | if (xsk_diag_fill(sk, nlskb, req, |
167 | user_ns: sk_user_ns(NETLINK_CB(cb->skb).sk), |
168 | NETLINK_CB(cb->skb).portid, |
169 | seq: cb->nlh->nlmsg_seq, NLM_F_MULTI, |
170 | sk_ino: sock_i_ino(sk)) < 0) { |
171 | num--; |
172 | break; |
173 | } |
174 | } |
175 | |
176 | mutex_unlock(lock: &net->xdp.lock); |
177 | cb->args[0] = num; |
178 | return nlskb->len; |
179 | } |
180 | |
181 | static int xsk_diag_handler_dump(struct sk_buff *nlskb, struct nlmsghdr *hdr) |
182 | { |
183 | struct netlink_dump_control c = { .dump = xsk_diag_dump }; |
184 | int hdrlen = sizeof(struct xdp_diag_req); |
185 | struct net *net = sock_net(sk: nlskb->sk); |
186 | |
187 | if (nlmsg_len(nlh: hdr) < hdrlen) |
188 | return -EINVAL; |
189 | |
190 | if (!(hdr->nlmsg_flags & NLM_F_DUMP)) |
191 | return -EOPNOTSUPP; |
192 | |
193 | return netlink_dump_start(ssk: net->diag_nlsk, skb: nlskb, nlh: hdr, control: &c); |
194 | } |
195 | |
196 | static const struct sock_diag_handler xsk_diag_handler = { |
197 | .family = AF_XDP, |
198 | .dump = xsk_diag_handler_dump, |
199 | }; |
200 | |
201 | static int __init xsk_diag_init(void) |
202 | { |
203 | return sock_diag_register(h: &xsk_diag_handler); |
204 | } |
205 | |
206 | static void __exit xsk_diag_exit(void) |
207 | { |
208 | sock_diag_unregister(h: &xsk_diag_handler); |
209 | } |
210 | |
211 | module_init(xsk_diag_init); |
212 | module_exit(xsk_diag_exit); |
213 | MODULE_LICENSE("GPL" ); |
214 | MODULE_ALIAS_NET_PF_PROTO_TYPE(PF_NETLINK, NETLINK_SOCK_DIAG, AF_XDP); |
215 | |