xref: /linux/tools/testing/selftests/drivers/net/psp.py (revision 2748087cf12d800db142eaefd2b0f4eccee8c943)
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