Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1#!/usr/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3
4import errno
5import subprocess
6import time
7from lib.py import ksft_run, ksft_exit, ksft_pr
8from lib.py import ksft_ge, ksft_eq, ksft_is, ksft_in, ksft_lt, ksft_true, ksft_raises
9from lib.py import KsftSkipEx, KsftXfailEx
10from lib.py import ksft_disruptive
11from lib.py import EthtoolFamily, NetdevFamily, RtnlFamily, NlError
12from lib.py import NetDrvEnv
13from lib.py import cmd, ip, defer
14
15ethnl = EthtoolFamily()
16netfam = NetdevFamily()
17rtnl = RtnlFamily()
18
19
20def check_pause(cfg) -> None:
21 global ethnl
22
23 try:
24 ethnl.pause_get({"header": {"dev-index": cfg.ifindex}})
25 except NlError as e:
26 if e.error == errno.EOPNOTSUPP:
27 raise KsftXfailEx("pause not supported by the device")
28 raise
29
30 data = ethnl.pause_get({"header": {"dev-index": cfg.ifindex,
31 "flags": {'stats'}}})
32 ksft_true(data['stats'], "driver does not report stats")
33
34
35def check_fec(cfg) -> None:
36 global ethnl
37
38 try:
39 ethnl.fec_get({"header": {"dev-index": cfg.ifindex}})
40 except NlError as e:
41 if e.error == errno.EOPNOTSUPP:
42 raise KsftXfailEx("FEC not supported by the device")
43 raise
44
45 data = ethnl.fec_get({"header": {"dev-index": cfg.ifindex,
46 "flags": {'stats'}}})
47 ksft_true(data['stats'], "driver does not report stats")
48
49
50def pkt_byte_sum(cfg) -> None:
51 global netfam, rtnl
52
53 def get_qstat(test):
54 global netfam
55 stats = netfam.qstats_get({}, dump=True)
56 if stats:
57 for qs in stats:
58 if qs["ifindex"]== test.ifindex:
59 return qs
60
61 qstat = get_qstat(cfg)
62 if qstat is None:
63 raise KsftSkipEx("qstats not supported by the device")
64
65 for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
66 ksft_in(key, qstat, "Drivers should always report basic keys")
67
68 # Compare stats, rtnl stats and qstats must match,
69 # but the interface may be up, so do a series of dumps
70 # each time the more "recent" stats must be higher or same.
71 def stat_cmp(rstat, qstat):
72 for key in ['tx-packets', 'tx-bytes', 'rx-packets', 'rx-bytes']:
73 if rstat[key] != qstat[key]:
74 return rstat[key] - qstat[key]
75 return 0
76
77 for _ in range(10):
78 rtstat = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
79 if stat_cmp(rtstat, qstat) < 0:
80 raise Exception("RTNL stats are lower, fetched later")
81 qstat = get_qstat(cfg)
82 if stat_cmp(rtstat, qstat) > 0:
83 raise Exception("Qstats are lower, fetched later")
84
85
86def qstat_by_ifindex(cfg) -> None:
87 global netfam
88 global rtnl
89
90 # Construct a map ifindex -> [dump, by-index, dump]
91 ifindexes = {}
92 stats = netfam.qstats_get({}, dump=True)
93 for entry in stats:
94 ifindexes[entry['ifindex']] = [entry, None, None]
95
96 for ifindex in ifindexes.keys():
97 entry = netfam.qstats_get({"ifindex": ifindex}, dump=True)
98 ksft_eq(len(entry), 1)
99 ifindexes[entry[0]['ifindex']][1] = entry[0]
100
101 stats = netfam.qstats_get({}, dump=True)
102 for entry in stats:
103 ifindexes[entry['ifindex']][2] = entry
104
105 if len(ifindexes) == 0:
106 raise KsftSkipEx("No ifindex supports qstats")
107
108 # Now make sure the stats match/make sense
109 for ifindex, triple in ifindexes.items():
110 all_keys = triple[0].keys() | triple[1].keys() | triple[2].keys()
111
112 for key in all_keys:
113 ksft_ge(triple[1][key], triple[0][key], comment="bad key: " + key)
114 ksft_ge(triple[2][key], triple[1][key], comment="bad key: " + key)
115
116 # Sanity check the dumps
117 queues = NetdevFamily(recv_size=4096).qstats_get({"scope": "queue"}, dump=True)
118 # Reformat the output into {ifindex: {rx: [id, id, ...], tx: [id, id, ...]}}
119 parsed = {}
120 for entry in queues:
121 ifindex = entry["ifindex"]
122 if ifindex not in parsed:
123 parsed[ifindex] = {"rx":[], "tx": []}
124 parsed[ifindex][entry["queue-type"]].append(entry['queue-id'])
125 # Now, validate
126 for ifindex, queues in parsed.items():
127 for qtype in ['rx', 'tx']:
128 ksft_eq(len(queues[qtype]), len(set(queues[qtype])),
129 comment="repeated queue keys")
130 ksft_eq(len(queues[qtype]), max(queues[qtype]) + 1,
131 comment="missing queue keys")
132
133 # Test invalid dumps
134 # 0 is invalid
135 with ksft_raises(NlError) as cm:
136 netfam.qstats_get({"ifindex": 0}, dump=True)
137 ksft_eq(cm.exception.nl_msg.error, -34)
138 ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
139
140 # loopback has no stats
141 with ksft_raises(NlError) as cm:
142 netfam.qstats_get({"ifindex": 1}, dump=True)
143 ksft_eq(cm.exception.nl_msg.error, -errno.EOPNOTSUPP)
144 ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
145
146 # Try to get stats for lowest unused ifindex but not 0
147 devs = rtnl.getlink({}, dump=True)
148 all_ifindexes = set([dev["ifi-index"] for dev in devs])
149 lowest = 2
150 while lowest in all_ifindexes:
151 lowest += 1
152
153 with ksft_raises(NlError) as cm:
154 netfam.qstats_get({"ifindex": lowest}, dump=True)
155 ksft_eq(cm.exception.nl_msg.error, -19)
156 ksft_eq(cm.exception.nl_msg.extack['bad-attr'], '.ifindex')
157
158
159@ksft_disruptive
160def check_down(cfg) -> None:
161 try:
162 qstat = netfam.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0]
163 except NlError as e:
164 if e.error == errno.EOPNOTSUPP:
165 raise KsftSkipEx("qstats not supported by the device")
166 raise
167
168 ip(f"link set dev {cfg.dev['ifname']} down")
169 defer(ip, f"link set dev {cfg.dev['ifname']} up")
170
171 qstat2 = netfam.qstats_get({"ifindex": cfg.ifindex}, dump=True)[0]
172 for k, v in qstat.items():
173 ksft_ge(qstat2[k], qstat[k], comment=f"{k} went backwards on device down")
174
175 # exercise per-queue API to make sure that "device down" state
176 # is handled correctly and doesn't crash
177 netfam.qstats_get({"ifindex": cfg.ifindex, "scope": "queue"}, dump=True)
178
179
180def __run_inf_loop(body):
181 body = body.strip()
182 if body[-1] != ';':
183 body += ';'
184
185 return subprocess.Popen(f"while true; do {body} done", shell=True,
186 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
187
188
189def __stats_increase_sanely(old, new) -> None:
190 for k in old.keys():
191 ksft_ge(new[k], old[k])
192 ksft_lt(new[k] - old[k], 1 << 31, comment="likely wrapping error")
193
194
195def procfs_hammer(cfg) -> None:
196 """
197 Reading stats via procfs only holds the RCU lock, which is not an exclusive
198 lock, make sure drivers can handle parallel reads of stats.
199 """
200 one = __run_inf_loop("cat /proc/net/dev")
201 defer(one.kill)
202 two = __run_inf_loop("cat /proc/net/dev")
203 defer(two.kill)
204
205 time.sleep(1)
206 # Make sure the processes are running
207 ksft_is(one.poll(), None)
208 ksft_is(two.poll(), None)
209
210 rtstat1 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
211 time.sleep(2)
212 rtstat2 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
213 __stats_increase_sanely(rtstat1, rtstat2)
214 # defers will kill the loops
215
216
217@ksft_disruptive
218def procfs_downup_hammer(cfg) -> None:
219 """
220 Reading stats via procfs only holds the RCU lock, drivers often try
221 to sleep when reading the stats, or don't protect against races.
222 """
223 # Max out the queues, we'll flip between max and 1
224 channels = ethnl.channels_get({'header': {'dev-index': cfg.ifindex}})
225 if channels['combined-count'] == 0:
226 rx_type = 'rx'
227 else:
228 rx_type = 'combined'
229 cur_queue_cnt = channels[f'{rx_type}-count']
230 max_queue_cnt = channels[f'{rx_type}-max']
231
232 cmd(f"ethtool -L {cfg.ifname} {rx_type} {max_queue_cnt}")
233 defer(cmd, f"ethtool -L {cfg.ifname} {rx_type} {cur_queue_cnt}")
234
235 # Real test stats
236 stats = __run_inf_loop("cat /proc/net/dev")
237 defer(stats.kill)
238
239 ipset = f"ip link set dev {cfg.ifname}"
240 defer(ip, f"link set dev {cfg.ifname} up")
241 # The "echo -n 1" lets us count iterations below
242 updown = f"{ipset} down; sleep 0.05; {ipset} up; sleep 0.05; " + \
243 f"ethtool -L {cfg.ifname} {rx_type} 1; " + \
244 f"ethtool -L {cfg.ifname} {rx_type} {max_queue_cnt}; " + \
245 "echo -n 1"
246 updown = __run_inf_loop(updown)
247 kill_updown = defer(updown.kill)
248
249 time.sleep(1)
250 # Make sure the processes are running
251 ksft_is(stats.poll(), None)
252 ksft_is(updown.poll(), None)
253
254 rtstat1 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
255 # We're looking for crashes, give it extra time
256 time.sleep(9)
257 rtstat2 = rtnl.getlink({"ifi-index": cfg.ifindex})['stats64']
258 __stats_increase_sanely(rtstat1, rtstat2)
259
260 kill_updown.exec()
261 stdout, _ = updown.communicate(timeout=5)
262 ksft_pr("completed up/down cycles:", len(stdout.decode('utf-8')))
263
264
265def main() -> None:
266 with NetDrvEnv(__file__, queue_count=100) as cfg:
267 ksft_run([check_pause, check_fec, pkt_byte_sum, qstat_by_ifindex,
268 check_down, procfs_hammer, procfs_downup_hammer],
269 args=(cfg, ))
270 ksft_exit()
271
272
273if __name__ == "__main__":
274 main()