1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0 3 4"""Test suite for PSP capable drivers.""" 5 6import errno 7import fcntl 8import socket 9import struct 10import termios 11import time 12 13from lib.py import defer 14from lib.py import ksft_run, ksft_exit, ksft_pr 15from lib.py import ksft_true, ksft_eq, ksft_ne, ksft_gt, ksft_raises 16from lib.py import KsftSkipEx 17from lib.py import NetDrvEpEnv, PSPFamily, NlError 18from lib.py import bkg, rand_port, wait_port_listen 19 20 21def _get_outq(s): 22 one = b'\0' * 4 23 outq = fcntl.ioctl(s.fileno(), termios.TIOCOUTQ, one) 24 return struct.unpack("I", outq)[0] 25 26 27def _send_with_ack(cfg, msg): 28 cfg.comm_sock.send(msg) 29 response = cfg.comm_sock.recv(4) 30 if response != b'ack\0': 31 raise RuntimeError("Unexpected server response", response) 32 33 34def _remote_read_len(cfg): 35 cfg.comm_sock.send(b'read len\0') 36 return int(cfg.comm_sock.recv(1024)[:-1].decode('utf-8')) 37 38 39def _make_clr_conn(cfg, ipver=None): 40 _send_with_ack(cfg, b'conn clr\0') 41 remote_addr = cfg.remote_addr_v[ipver] if ipver else cfg.remote_addr 42 s = socket.create_connection((remote_addr, cfg.comm_port), ) 43 return s 44 45 46def _make_psp_conn(cfg, version=0, ipver=None): 47 _send_with_ack(cfg, b'conn psp\0' + struct.pack('BB', version, version)) 48 remote_addr = cfg.remote_addr_v[ipver] if ipver else cfg.remote_addr 49 s = socket.create_connection((remote_addr, cfg.comm_port), ) 50 return s 51 52 53def _close_conn(cfg, s): 54 _send_with_ack(cfg, b'data close\0') 55 s.close() 56 57 58def _close_psp_conn(cfg, s): 59 _close_conn(cfg, s) 60 61 62def _spi_xchg(s, rx): 63 s.send(struct.pack('I', rx['spi']) + rx['key']) 64 tx = s.recv(4 + len(rx['key'])) 65 return { 66 'spi': struct.unpack('I', tx[:4])[0], 67 'key': tx[4:] 68 } 69 70 71def _send_careful(cfg, s, rounds): 72 data = b'0123456789' * 200 73 for i in range(rounds): 74 n = 0 75 for _ in range(10): # allow 10 retries 76 try: 77 n += s.send(data[n:], socket.MSG_DONTWAIT) 78 if n == len(data): 79 break 80 except BlockingIOError: 81 time.sleep(0.05) 82 else: 83 rlen = _remote_read_len(cfg) 84 outq = _get_outq(s) 85 report = f'sent: {i * len(data) + n} remote len: {rlen} outq: {outq}' 86 raise RuntimeError(report) 87 88 return len(data) * rounds 89 90 91def _check_data_rx(cfg, exp_len): 92 read_len = -1 93 for _ in range(30): 94 cfg.comm_sock.send(b'read len\0') 95 read_len = int(cfg.comm_sock.recv(1024)[:-1].decode('utf-8')) 96 if read_len == exp_len: 97 break 98 time.sleep(0.01) 99 ksft_eq(read_len, exp_len) 100 101# 102# Test case boiler plate 103# 104 105def _init_psp_dev(cfg): 106 if not hasattr(cfg, 'psp_dev_id'): 107 # Figure out which local device we are testing against 108 for dev in cfg.pspnl.dev_get({}, dump=True): 109 if dev['ifindex'] == cfg.ifindex: 110 cfg.psp_info = dev 111 cfg.psp_dev_id = cfg.psp_info['id'] 112 break 113 else: 114 raise KsftSkipEx("No PSP devices found") 115 116 # Enable PSP if necessary 117 cap = cfg.psp_info['psp-versions-cap'] 118 ena = cfg.psp_info['psp-versions-ena'] 119 if cap != ena: 120 cfg.pspnl.dev_set({'id': cfg.psp_dev_id, 'psp-versions-ena': cap}) 121 defer(cfg.pspnl.dev_set, {'id': cfg.psp_dev_id, 122 'psp-versions-ena': ena }) 123 124# 125# Test cases 126# 127 128def dev_list_devices(cfg): 129 """ Dump all devices """ 130 _init_psp_dev(cfg) 131 132 devices = cfg.pspnl.dev_get({}, dump=True) 133 134 found = False 135 for dev in devices: 136 found |= dev['id'] == cfg.psp_dev_id 137 ksft_true(found) 138 139 140def dev_get_device(cfg): 141 """ Get the device we intend to use """ 142 _init_psp_dev(cfg) 143 144 dev = cfg.pspnl.dev_get({'id': cfg.psp_dev_id}) 145 ksft_eq(dev['id'], cfg.psp_dev_id) 146 147 148def dev_get_device_bad(cfg): 149 """ Test getting device which doesn't exist """ 150 raised = False 151 try: 152 cfg.pspnl.dev_get({'id': 1234567}) 153 except NlError as e: 154 ksft_eq(e.nl_msg.error, -errno.ENODEV) 155 raised = True 156 ksft_true(raised) 157 158 159def dev_rotate(cfg): 160 """ Test key rotation """ 161 _init_psp_dev(cfg) 162 163 rot = cfg.pspnl.key_rotate({"id": cfg.psp_dev_id}) 164 ksft_eq(rot['id'], cfg.psp_dev_id) 165 rot = cfg.pspnl.key_rotate({"id": cfg.psp_dev_id}) 166 ksft_eq(rot['id'], cfg.psp_dev_id) 167 168 169def dev_rotate_spi(cfg): 170 """ Test key rotation and SPI check """ 171 _init_psp_dev(cfg) 172 173 top_a = top_b = 0 174 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 175 assoc_a = cfg.pspnl.rx_assoc({"version": 0, 176 "dev-id": cfg.psp_dev_id, 177 "sock-fd": s.fileno()}) 178 top_a = assoc_a['rx-key']['spi'] >> 31 179 s.close() 180 rot = cfg.pspnl.key_rotate({"id": cfg.psp_dev_id}) 181 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 182 ksft_eq(rot['id'], cfg.psp_dev_id) 183 assoc_b = cfg.pspnl.rx_assoc({"version": 0, 184 "dev-id": cfg.psp_dev_id, 185 "sock-fd": s.fileno()}) 186 top_b = assoc_b['rx-key']['spi'] >> 31 187 s.close() 188 ksft_ne(top_a, top_b) 189 190 191def assoc_basic(cfg): 192 """ Test creating associations """ 193 _init_psp_dev(cfg) 194 195 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 196 assoc = cfg.pspnl.rx_assoc({"version": 0, 197 "dev-id": cfg.psp_dev_id, 198 "sock-fd": s.fileno()}) 199 ksft_eq(assoc['dev-id'], cfg.psp_dev_id) 200 ksft_gt(assoc['rx-key']['spi'], 0) 201 ksft_eq(len(assoc['rx-key']['key']), 16) 202 203 assoc = cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, 204 "version": 0, 205 "tx-key": assoc['rx-key'], 206 "sock-fd": s.fileno()}) 207 ksft_eq(len(assoc), 0) 208 s.close() 209 210 211def assoc_bad_dev(cfg): 212 """ Test creating associations with bad device ID """ 213 _init_psp_dev(cfg) 214 215 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 216 with ksft_raises(NlError) as cm: 217 cfg.pspnl.rx_assoc({"version": 0, 218 "dev-id": cfg.psp_dev_id + 1234567, 219 "sock-fd": s.fileno()}) 220 ksft_eq(cm.exception.nl_msg.error, -errno.ENODEV) 221 222 223def assoc_sk_only_conn(cfg): 224 """ Test creating associations based on socket """ 225 _init_psp_dev(cfg) 226 227 with _make_clr_conn(cfg) as s: 228 assoc = cfg.pspnl.rx_assoc({"version": 0, 229 "sock-fd": s.fileno()}) 230 ksft_eq(assoc['dev-id'], cfg.psp_dev_id) 231 cfg.pspnl.tx_assoc({"version": 0, 232 "tx-key": assoc['rx-key'], 233 "sock-fd": s.fileno()}) 234 _close_conn(cfg, s) 235 236 237def assoc_sk_only_mismatch(cfg): 238 """ Test creating associations based on socket (dev mismatch) """ 239 _init_psp_dev(cfg) 240 241 with _make_clr_conn(cfg) as s: 242 with ksft_raises(NlError) as cm: 243 cfg.pspnl.rx_assoc({"version": 0, 244 "dev-id": cfg.psp_dev_id + 1234567, 245 "sock-fd": s.fileno()}) 246 the_exception = cm.exception 247 ksft_eq(the_exception.nl_msg.extack['bad-attr'], ".dev-id") 248 ksft_eq(the_exception.nl_msg.error, -errno.EINVAL) 249 250 251def assoc_sk_only_mismatch_tx(cfg): 252 """ Test creating associations based on socket (dev mismatch) """ 253 _init_psp_dev(cfg) 254 255 with _make_clr_conn(cfg) as s: 256 with ksft_raises(NlError) as cm: 257 assoc = cfg.pspnl.rx_assoc({"version": 0, 258 "sock-fd": s.fileno()}) 259 cfg.pspnl.tx_assoc({"version": 0, 260 "tx-key": assoc['rx-key'], 261 "dev-id": cfg.psp_dev_id + 1234567, 262 "sock-fd": s.fileno()}) 263 the_exception = cm.exception 264 ksft_eq(the_exception.nl_msg.extack['bad-attr'], ".dev-id") 265 ksft_eq(the_exception.nl_msg.error, -errno.EINVAL) 266 267 268def assoc_sk_only_unconn(cfg): 269 """ Test creating associations based on socket (unconnected, should fail) """ 270 _init_psp_dev(cfg) 271 272 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 273 with ksft_raises(NlError) as cm: 274 cfg.pspnl.rx_assoc({"version": 0, 275 "sock-fd": s.fileno()}) 276 the_exception = cm.exception 277 ksft_eq(the_exception.nl_msg.extack['miss-type'], "dev-id") 278 ksft_eq(the_exception.nl_msg.error, -errno.EINVAL) 279 280 281def assoc_version_mismatch(cfg): 282 """ Test creating associations where Rx and Tx PSP versions do not match """ 283 _init_psp_dev(cfg) 284 285 versions = list(cfg.psp_info['psp-versions-cap']) 286 if len(versions) < 2: 287 raise KsftSkipEx("Not enough PSP versions supported by the device for the test") 288 289 # Translate versions to integers 290 versions = [cfg.pspnl.consts["version"].entries[v].value for v in versions] 291 292 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 293 rx = cfg.pspnl.rx_assoc({"version": versions[0], 294 "dev-id": cfg.psp_dev_id, 295 "sock-fd": s.fileno()}) 296 297 for version in versions[1:]: 298 with ksft_raises(NlError) as cm: 299 cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, 300 "version": version, 301 "tx-key": rx['rx-key'], 302 "sock-fd": s.fileno()}) 303 the_exception = cm.exception 304 ksft_eq(the_exception.nl_msg.error, -errno.EINVAL) 305 306 307def assoc_twice(cfg): 308 """ Test reusing Tx assoc for two sockets """ 309 _init_psp_dev(cfg) 310 311 def rx_assoc_check(s): 312 assoc = cfg.pspnl.rx_assoc({"version": 0, 313 "dev-id": cfg.psp_dev_id, 314 "sock-fd": s.fileno()}) 315 ksft_eq(assoc['dev-id'], cfg.psp_dev_id) 316 ksft_gt(assoc['rx-key']['spi'], 0) 317 ksft_eq(len(assoc['rx-key']['key']), 16) 318 319 return assoc 320 321 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 322 assoc = rx_assoc_check(s) 323 tx = cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, 324 "version": 0, 325 "tx-key": assoc['rx-key'], 326 "sock-fd": s.fileno()}) 327 ksft_eq(len(tx), 0) 328 329 # Use the same Tx assoc second time 330 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s2: 331 rx_assoc_check(s2) 332 tx = cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, 333 "version": 0, 334 "tx-key": assoc['rx-key'], 335 "sock-fd": s2.fileno()}) 336 ksft_eq(len(tx), 0) 337 338 s.close() 339 340 341def _data_basic_send(cfg, version, ipver): 342 """ Test basic data send """ 343 _init_psp_dev(cfg) 344 345 # Version 0 is required by spec, don't let it skip 346 if version: 347 name = cfg.pspnl.consts["version"].entries_by_val[version].name 348 if name not in cfg.psp_info['psp-versions-cap']: 349 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 350 with ksft_raises(NlError) as cm: 351 cfg.pspnl.rx_assoc({"version": version, 352 "dev-id": cfg.psp_dev_id, 353 "sock-fd": s.fileno()}) 354 ksft_eq(cm.exception.nl_msg.error, -errno.EOPNOTSUPP) 355 raise KsftSkipEx("PSP version not supported", name) 356 357 s = _make_psp_conn(cfg, version, ipver) 358 359 rx_assoc = cfg.pspnl.rx_assoc({"version": version, 360 "dev-id": cfg.psp_dev_id, 361 "sock-fd": s.fileno()}) 362 rx = rx_assoc['rx-key'] 363 tx = _spi_xchg(s, rx) 364 365 cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, 366 "version": version, 367 "tx-key": tx, 368 "sock-fd": s.fileno()}) 369 370 data_len = _send_careful(cfg, s, 100) 371 _check_data_rx(cfg, data_len) 372 _close_psp_conn(cfg, s) 373 374 375def psp_ip_ver_test_builder(name, test_func, psp_ver, ipver): 376 """Build test cases for each combo of PSP version and IP version""" 377 def test_case(cfg): 378 cfg.require_ipver(ipver) 379 test_case.__name__ = f"{name}_v{psp_ver}_ip{ipver}" 380 test_func(cfg, psp_ver, ipver) 381 return test_case 382 383 384def main() -> None: 385 """ Ksft boiler plate main """ 386 387 with NetDrvEpEnv(__file__) as cfg: 388 cfg.pspnl = PSPFamily() 389 390 # Set up responder and communication sock 391 responder = cfg.remote.deploy("psp_responder") 392 393 cfg.comm_port = rand_port() 394 srv = None 395 try: 396 with bkg(responder + f" -p {cfg.comm_port}", host=cfg.remote, 397 exit_wait=True) as srv: 398 wait_port_listen(cfg.comm_port, host=cfg.remote) 399 400 cfg.comm_sock = socket.create_connection((cfg.remote_addr, 401 cfg.comm_port), 402 timeout=1) 403 404 cases = [ 405 psp_ip_ver_test_builder( 406 "data_basic_send", _data_basic_send, version, ipver 407 ) 408 for version in range(0, 4) 409 for ipver in ("4", "6") 410 ] 411 412 ksft_run(cases=cases, globs=globals(), 413 case_pfx={"dev_", "assoc_"}, args=(cfg, )) 414 415 cfg.comm_sock.send(b"exit\0") 416 cfg.comm_sock.close() 417 finally: 418 if srv and (srv.stdout or srv.stderr): 419 ksft_pr("") 420 ksft_pr(f"Responder logs ({srv.ret}):") 421 if srv and srv.stdout: 422 ksft_pr("STDOUT:\n# " + srv.stdout.strip().replace("\n", "\n# ")) 423 if srv and srv.stderr: 424 ksft_pr("STDERR:\n# " + srv.stderr.strip().replace("\n", "\n# ")) 425 ksft_exit() 426 427 428if __name__ == "__main__": 429 main() 430