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() 275