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 102def _check_data_outq(s, exp_len, force_wait=False): 103 outq = 0 104 for _ in range(10): 105 outq = _get_outq(s) 106 if not force_wait and outq == exp_len: 107 break 108 time.sleep(0.01) 109 ksft_eq(outq, exp_len) 110 111# 112# Test case boiler plate 113# 114 115def _init_psp_dev(cfg): 116 if not hasattr(cfg, 'psp_dev_id'): 117 # Figure out which local device we are testing against 118 for dev in cfg.pspnl.dev_get({}, dump=True): 119 if dev['ifindex'] == cfg.ifindex: 120 cfg.psp_info = dev 121 cfg.psp_dev_id = cfg.psp_info['id'] 122 break 123 else: 124 raise KsftSkipEx("No PSP devices found") 125 126 # Enable PSP if necessary 127 cap = cfg.psp_info['psp-versions-cap'] 128 ena = cfg.psp_info['psp-versions-ena'] 129 if cap != ena: 130 cfg.pspnl.dev_set({'id': cfg.psp_dev_id, 'psp-versions-ena': cap}) 131 defer(cfg.pspnl.dev_set, {'id': cfg.psp_dev_id, 132 'psp-versions-ena': ena }) 133 134# 135# Test cases 136# 137 138def dev_list_devices(cfg): 139 """ Dump all devices """ 140 _init_psp_dev(cfg) 141 142 devices = cfg.pspnl.dev_get({}, dump=True) 143 144 found = False 145 for dev in devices: 146 found |= dev['id'] == cfg.psp_dev_id 147 ksft_true(found) 148 149 150def dev_get_device(cfg): 151 """ Get the device we intend to use """ 152 _init_psp_dev(cfg) 153 154 dev = cfg.pspnl.dev_get({'id': cfg.psp_dev_id}) 155 ksft_eq(dev['id'], cfg.psp_dev_id) 156 157 158def dev_get_device_bad(cfg): 159 """ Test getting device which doesn't exist """ 160 raised = False 161 try: 162 cfg.pspnl.dev_get({'id': 1234567}) 163 except NlError as e: 164 ksft_eq(e.nl_msg.error, -errno.ENODEV) 165 raised = True 166 ksft_true(raised) 167 168 169def dev_rotate(cfg): 170 """ Test key rotation """ 171 _init_psp_dev(cfg) 172 173 rot = cfg.pspnl.key_rotate({"id": cfg.psp_dev_id}) 174 ksft_eq(rot['id'], cfg.psp_dev_id) 175 rot = cfg.pspnl.key_rotate({"id": cfg.psp_dev_id}) 176 ksft_eq(rot['id'], cfg.psp_dev_id) 177 178 179def dev_rotate_spi(cfg): 180 """ Test key rotation and SPI check """ 181 _init_psp_dev(cfg) 182 183 top_a = top_b = 0 184 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 185 assoc_a = cfg.pspnl.rx_assoc({"version": 0, 186 "dev-id": cfg.psp_dev_id, 187 "sock-fd": s.fileno()}) 188 top_a = assoc_a['rx-key']['spi'] >> 31 189 s.close() 190 rot = cfg.pspnl.key_rotate({"id": cfg.psp_dev_id}) 191 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 192 ksft_eq(rot['id'], cfg.psp_dev_id) 193 assoc_b = cfg.pspnl.rx_assoc({"version": 0, 194 "dev-id": cfg.psp_dev_id, 195 "sock-fd": s.fileno()}) 196 top_b = assoc_b['rx-key']['spi'] >> 31 197 s.close() 198 ksft_ne(top_a, top_b) 199 200 201def assoc_basic(cfg): 202 """ Test creating associations """ 203 _init_psp_dev(cfg) 204 205 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 206 assoc = cfg.pspnl.rx_assoc({"version": 0, 207 "dev-id": cfg.psp_dev_id, 208 "sock-fd": s.fileno()}) 209 ksft_eq(assoc['dev-id'], cfg.psp_dev_id) 210 ksft_gt(assoc['rx-key']['spi'], 0) 211 ksft_eq(len(assoc['rx-key']['key']), 16) 212 213 assoc = cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, 214 "version": 0, 215 "tx-key": assoc['rx-key'], 216 "sock-fd": s.fileno()}) 217 ksft_eq(len(assoc), 0) 218 s.close() 219 220 221def assoc_bad_dev(cfg): 222 """ Test creating associations with bad device ID """ 223 _init_psp_dev(cfg) 224 225 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 226 with ksft_raises(NlError) as cm: 227 cfg.pspnl.rx_assoc({"version": 0, 228 "dev-id": cfg.psp_dev_id + 1234567, 229 "sock-fd": s.fileno()}) 230 ksft_eq(cm.exception.nl_msg.error, -errno.ENODEV) 231 232 233def assoc_sk_only_conn(cfg): 234 """ Test creating associations based on socket """ 235 _init_psp_dev(cfg) 236 237 with _make_clr_conn(cfg) as s: 238 assoc = cfg.pspnl.rx_assoc({"version": 0, 239 "sock-fd": s.fileno()}) 240 ksft_eq(assoc['dev-id'], cfg.psp_dev_id) 241 cfg.pspnl.tx_assoc({"version": 0, 242 "tx-key": assoc['rx-key'], 243 "sock-fd": s.fileno()}) 244 _close_conn(cfg, s) 245 246 247def assoc_sk_only_mismatch(cfg): 248 """ Test creating associations based on socket (dev mismatch) """ 249 _init_psp_dev(cfg) 250 251 with _make_clr_conn(cfg) as s: 252 with ksft_raises(NlError) as cm: 253 cfg.pspnl.rx_assoc({"version": 0, 254 "dev-id": cfg.psp_dev_id + 1234567, 255 "sock-fd": s.fileno()}) 256 the_exception = cm.exception 257 ksft_eq(the_exception.nl_msg.extack['bad-attr'], ".dev-id") 258 ksft_eq(the_exception.nl_msg.error, -errno.EINVAL) 259 260 261def assoc_sk_only_mismatch_tx(cfg): 262 """ Test creating associations based on socket (dev mismatch) """ 263 _init_psp_dev(cfg) 264 265 with _make_clr_conn(cfg) as s: 266 with ksft_raises(NlError) as cm: 267 assoc = cfg.pspnl.rx_assoc({"version": 0, 268 "sock-fd": s.fileno()}) 269 cfg.pspnl.tx_assoc({"version": 0, 270 "tx-key": assoc['rx-key'], 271 "dev-id": cfg.psp_dev_id + 1234567, 272 "sock-fd": s.fileno()}) 273 the_exception = cm.exception 274 ksft_eq(the_exception.nl_msg.extack['bad-attr'], ".dev-id") 275 ksft_eq(the_exception.nl_msg.error, -errno.EINVAL) 276 277 278def assoc_sk_only_unconn(cfg): 279 """ Test creating associations based on socket (unconnected, should fail) """ 280 _init_psp_dev(cfg) 281 282 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 283 with ksft_raises(NlError) as cm: 284 cfg.pspnl.rx_assoc({"version": 0, 285 "sock-fd": s.fileno()}) 286 the_exception = cm.exception 287 ksft_eq(the_exception.nl_msg.extack['miss-type'], "dev-id") 288 ksft_eq(the_exception.nl_msg.error, -errno.EINVAL) 289 290 291def assoc_version_mismatch(cfg): 292 """ Test creating associations where Rx and Tx PSP versions do not match """ 293 _init_psp_dev(cfg) 294 295 versions = list(cfg.psp_info['psp-versions-cap']) 296 if len(versions) < 2: 297 raise KsftSkipEx("Not enough PSP versions supported by the device for the test") 298 299 # Translate versions to integers 300 versions = [cfg.pspnl.consts["version"].entries[v].value for v in versions] 301 302 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 303 rx = cfg.pspnl.rx_assoc({"version": versions[0], 304 "dev-id": cfg.psp_dev_id, 305 "sock-fd": s.fileno()}) 306 307 for version in versions[1:]: 308 with ksft_raises(NlError) as cm: 309 cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, 310 "version": version, 311 "tx-key": rx['rx-key'], 312 "sock-fd": s.fileno()}) 313 the_exception = cm.exception 314 ksft_eq(the_exception.nl_msg.error, -errno.EINVAL) 315 316 317def assoc_twice(cfg): 318 """ Test reusing Tx assoc for two sockets """ 319 _init_psp_dev(cfg) 320 321 def rx_assoc_check(s): 322 assoc = cfg.pspnl.rx_assoc({"version": 0, 323 "dev-id": cfg.psp_dev_id, 324 "sock-fd": s.fileno()}) 325 ksft_eq(assoc['dev-id'], cfg.psp_dev_id) 326 ksft_gt(assoc['rx-key']['spi'], 0) 327 ksft_eq(len(assoc['rx-key']['key']), 16) 328 329 return assoc 330 331 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 332 assoc = rx_assoc_check(s) 333 tx = cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, 334 "version": 0, 335 "tx-key": assoc['rx-key'], 336 "sock-fd": s.fileno()}) 337 ksft_eq(len(tx), 0) 338 339 # Use the same Tx assoc second time 340 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s2: 341 rx_assoc_check(s2) 342 tx = cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, 343 "version": 0, 344 "tx-key": assoc['rx-key'], 345 "sock-fd": s2.fileno()}) 346 ksft_eq(len(tx), 0) 347 348 s.close() 349 350 351def _data_basic_send(cfg, version, ipver): 352 """ Test basic data send """ 353 _init_psp_dev(cfg) 354 355 # Version 0 is required by spec, don't let it skip 356 if version: 357 name = cfg.pspnl.consts["version"].entries_by_val[version].name 358 if name not in cfg.psp_info['psp-versions-cap']: 359 with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: 360 with ksft_raises(NlError) as cm: 361 cfg.pspnl.rx_assoc({"version": version, 362 "dev-id": cfg.psp_dev_id, 363 "sock-fd": s.fileno()}) 364 ksft_eq(cm.exception.nl_msg.error, -errno.EOPNOTSUPP) 365 raise KsftSkipEx("PSP version not supported", name) 366 367 s = _make_psp_conn(cfg, version, ipver) 368 369 rx_assoc = cfg.pspnl.rx_assoc({"version": version, 370 "dev-id": cfg.psp_dev_id, 371 "sock-fd": s.fileno()}) 372 rx = rx_assoc['rx-key'] 373 tx = _spi_xchg(s, rx) 374 375 cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, 376 "version": version, 377 "tx-key": tx, 378 "sock-fd": s.fileno()}) 379 380 data_len = _send_careful(cfg, s, 100) 381 _check_data_rx(cfg, data_len) 382 _close_psp_conn(cfg, s) 383 384 385def __bad_xfer_do(cfg, s, tx, version='hdr0-aes-gcm-128'): 386 # Make sure we accept the ACK for the SPI before we seal with the bad assoc 387 _check_data_outq(s, 0) 388 389 cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, 390 "version": version, 391 "tx-key": tx, 392 "sock-fd": s.fileno()}) 393 394 data_len = _send_careful(cfg, s, 20) 395 _check_data_outq(s, data_len, force_wait=True) 396 _check_data_rx(cfg, 0) 397 _close_psp_conn(cfg, s) 398 399 400def data_send_bad_key(cfg): 401 """ Test send data with bad key """ 402 _init_psp_dev(cfg) 403 404 s = _make_psp_conn(cfg) 405 406 rx_assoc = cfg.pspnl.rx_assoc({"version": 0, 407 "dev-id": cfg.psp_dev_id, 408 "sock-fd": s.fileno()}) 409 rx = rx_assoc['rx-key'] 410 tx = _spi_xchg(s, rx) 411 tx['key'] = (tx['key'][0] ^ 0xff).to_bytes(1, 'little') + tx['key'][1:] 412 __bad_xfer_do(cfg, s, tx) 413 414 415def data_send_disconnect(cfg): 416 """ Test socket close after sending data """ 417 _init_psp_dev(cfg) 418 419 with _make_psp_conn(cfg) as s: 420 assoc = cfg.pspnl.rx_assoc({"version": 0, 421 "sock-fd": s.fileno()}) 422 tx = _spi_xchg(s, assoc['rx-key']) 423 cfg.pspnl.tx_assoc({"version": 0, 424 "tx-key": tx, 425 "sock-fd": s.fileno()}) 426 427 data_len = _send_careful(cfg, s, 100) 428 _check_data_rx(cfg, data_len) 429 430 s.shutdown(socket.SHUT_RDWR) 431 s.close() 432 433 434def data_stale_key(cfg): 435 """ Test send on a double-rotated key """ 436 _init_psp_dev(cfg) 437 438 s = _make_psp_conn(cfg) 439 try: 440 rx_assoc = cfg.pspnl.rx_assoc({"version": 0, 441 "dev-id": cfg.psp_dev_id, 442 "sock-fd": s.fileno()}) 443 rx = rx_assoc['rx-key'] 444 tx = _spi_xchg(s, rx) 445 446 cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id, 447 "version": 0, 448 "tx-key": tx, 449 "sock-fd": s.fileno()}) 450 451 data_len = _send_careful(cfg, s, 100) 452 _check_data_rx(cfg, data_len) 453 _check_data_outq(s, 0) 454 455 cfg.pspnl.key_rotate({"id": cfg.psp_dev_id}) 456 cfg.pspnl.key_rotate({"id": cfg.psp_dev_id}) 457 458 s.send(b'0123456789' * 200) 459 _check_data_outq(s, 2000, force_wait=True) 460 finally: 461 _close_psp_conn(cfg, s) 462 463 464def psp_ip_ver_test_builder(name, test_func, psp_ver, ipver): 465 """Build test cases for each combo of PSP version and IP version""" 466 def test_case(cfg): 467 cfg.require_ipver(ipver) 468 test_case.__name__ = f"{name}_v{psp_ver}_ip{ipver}" 469 test_func(cfg, psp_ver, ipver) 470 return test_case 471 472 473def main() -> None: 474 """ Ksft boiler plate main """ 475 476 with NetDrvEpEnv(__file__) as cfg: 477 cfg.pspnl = PSPFamily() 478 479 # Set up responder and communication sock 480 responder = cfg.remote.deploy("psp_responder") 481 482 cfg.comm_port = rand_port() 483 srv = None 484 try: 485 with bkg(responder + f" -p {cfg.comm_port}", host=cfg.remote, 486 exit_wait=True) as srv: 487 wait_port_listen(cfg.comm_port, host=cfg.remote) 488 489 cfg.comm_sock = socket.create_connection((cfg.remote_addr, 490 cfg.comm_port), 491 timeout=1) 492 493 cases = [ 494 psp_ip_ver_test_builder( 495 "data_basic_send", _data_basic_send, version, ipver 496 ) 497 for version in range(0, 4) 498 for ipver in ("4", "6") 499 ] 500 501 ksft_run(cases=cases, globs=globals(), 502 case_pfx={"dev_", "data_", "assoc_"}, 503 args=(cfg, )) 504 505 cfg.comm_sock.send(b"exit\0") 506 cfg.comm_sock.close() 507 finally: 508 if srv and (srv.stdout or srv.stderr): 509 ksft_pr("") 510 ksft_pr(f"Responder logs ({srv.ret}):") 511 if srv and srv.stdout: 512 ksft_pr("STDOUT:\n# " + srv.stdout.strip().replace("\n", "\n# ")) 513 if srv and srv.stderr: 514 ksft_pr("STDERR:\n# " + srv.stderr.strip().replace("\n", "\n# ")) 515 ksft_exit() 516 517 518if __name__ == "__main__": 519 main() 520