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