xref: /linux/tools/testing/selftests/drivers/net/psp.py (revision b5a051f6b840d48f159166ef073d3021989bfb50)
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 os
9import socket
10import struct
11import termios
12import time
13
14from lib.py import defer
15from lib.py import ksft_run, ksft_exit, ksft_pr
16from lib.py import ksft_true, ksft_eq, ksft_ne, ksft_gt, ksft_raises
17from lib.py import ksft_not_none
18from lib.py import ksft_variants, KsftNamedVariant
19from lib.py import KsftSkipEx, KsftFailEx
20from lib.py import NetDrvEpEnv, NetDrvContEnv
21from lib.py import Netlink, NlError, PSPFamily, RtnlFamily
22from lib.py import NetNSEnter
23from lib.py import bkg, rand_port, wait_port_listen
24from lib.py import ip
25
26TCP_ULP = 31
27
28
29def _get_outq(s):
30    one = b'\0' * 4
31    outq = fcntl.ioctl(s.fileno(), termios.TIOCOUTQ, one)
32    return struct.unpack("I", outq)[0]
33
34
35def _send_with_ack(cfg, msg):
36    cfg.comm_sock.send(msg)
37    response = cfg.comm_sock.recv(4)
38    if response != b'ack\0':
39        raise RuntimeError("Unexpected server response", response)
40
41
42def _remote_read_len(cfg):
43    cfg.comm_sock.send(b'read len\0')
44    return int(cfg.comm_sock.recv(1024)[:-1].decode('utf-8'))
45
46
47def _make_clr_conn(cfg, ipver=None):
48    _send_with_ack(cfg, b'conn clr\0')
49    remote_addr = cfg.remote_addr_v[ipver] if ipver else cfg.remote_addr
50    s = socket.create_connection((remote_addr, cfg.comm_port), )
51    return s
52
53
54def _make_psp_conn(cfg, version=0, ipver=None):
55    _send_with_ack(cfg, b'conn psp\0' + struct.pack('BB', version, version))
56    remote_addr = cfg.remote_addr_v[ipver] if ipver else cfg.remote_addr
57    s = socket.create_connection((remote_addr, cfg.comm_port), )
58    return s
59
60
61def _close_conn(cfg, s):
62    _send_with_ack(cfg, b'data close\0')
63    s.close()
64
65
66def _close_psp_conn(cfg, s):
67    _close_conn(cfg, s)
68
69
70def _spi_xchg(s, rx):
71    s.send(struct.pack('I', rx['spi']) + rx['key'])
72    tx = s.recv(4 + len(rx['key']))
73    return {
74        'spi': struct.unpack('I', tx[:4])[0],
75        'key': tx[4:]
76    }
77
78
79def _send_careful(cfg, s, rounds):
80    data = b'0123456789' * 200
81    for i in range(rounds):
82        n = 0
83        for _ in range(10): # allow 10 retries
84            try:
85                n += s.send(data[n:], socket.MSG_DONTWAIT)
86                if n == len(data):
87                    break
88            except BlockingIOError:
89                time.sleep(0.05)
90        else:
91            rlen = _remote_read_len(cfg)
92            outq = _get_outq(s)
93            report = f'sent: {i * len(data) + n} remote len: {rlen} outq: {outq}'
94            raise RuntimeError(report)
95
96    return len(data) * rounds
97
98
99def _check_data_rx(cfg, exp_len):
100    read_len = -1
101    for _ in range(30):
102        cfg.comm_sock.send(b'read len\0')
103        read_len = int(cfg.comm_sock.recv(1024)[:-1].decode('utf-8'))
104        if read_len == exp_len:
105            break
106        time.sleep(0.01)
107    ksft_eq(read_len, exp_len)
108
109
110def _check_data_outq(s, exp_len, force_wait=False):
111    outq = 0
112    for _ in range(10):
113        outq = _get_outq(s)
114        if not force_wait and outq == exp_len:
115            break
116        time.sleep(0.01)
117    ksft_eq(outq, exp_len)
118
119
120def _get_stat(cfg, key):
121    return cfg.pspnl.get_stats({'dev-id': cfg.psp_dev_id})[key]
122
123#
124# Test case boiler plate
125#
126
127def _init_psp_dev(cfg, use_psp_ifindex=False):
128    if not hasattr(cfg, 'psp_dev_id'):
129        # Figure out which local device we are testing against
130        # For NetDrvContEnv: use psp_ifindex instead of ifindex
131        target_ifindex = cfg.psp_ifindex if use_psp_ifindex else cfg.ifindex
132        for dev in cfg.pspnl.dev_get({}, dump=True):
133            if dev['ifindex'] == target_ifindex:
134                cfg.psp_info = dev
135                cfg.psp_dev_id = cfg.psp_info['id']
136                break
137        else:
138            raise KsftSkipEx("No PSP devices found")
139
140    # Enable PSP if necessary
141    cap = cfg.psp_info['psp-versions-cap']
142    ena = cfg.psp_info['psp-versions-ena']
143    if cap != ena:
144        cfg.pspnl.dev_set({'id': cfg.psp_dev_id, 'psp-versions-ena': cap})
145        defer(cfg.pspnl.dev_set, {'id': cfg.psp_dev_id,
146                                  'psp-versions-ena': ena })
147
148#
149# Test cases
150#
151
152def dev_list_devices(cfg):
153    """ Dump all devices """
154    _init_psp_dev(cfg)
155
156    devices = cfg.pspnl.dev_get({}, dump=True)
157
158    found = False
159    for dev in devices:
160        found |= dev['id'] == cfg.psp_dev_id
161    ksft_true(found)
162
163
164def dev_get_device(cfg):
165    """ Get the device we intend to use """
166    _init_psp_dev(cfg)
167
168    dev = cfg.pspnl.dev_get({'id': cfg.psp_dev_id})
169    ksft_eq(dev['id'], cfg.psp_dev_id)
170
171
172def dev_get_device_bad(cfg):
173    """ Test getting device which doesn't exist """
174    raised = False
175    try:
176        cfg.pspnl.dev_get({'id': 1234567})
177    except NlError as e:
178        ksft_eq(e.nl_msg.error, -errno.ENODEV)
179        raised = True
180    ksft_true(raised)
181
182
183def dev_rotate(cfg):
184    """ Test key rotation """
185    _init_psp_dev(cfg)
186
187    prev_rotations = _get_stat(cfg, 'key-rotations')
188
189    rot = cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
190    ksft_eq(rot['id'], cfg.psp_dev_id)
191    rot = cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
192    ksft_eq(rot['id'], cfg.psp_dev_id)
193
194    cur_rotations = _get_stat(cfg, 'key-rotations')
195    ksft_eq(cur_rotations, prev_rotations + 2)
196
197
198def dev_rotate_spi(cfg):
199    """ Test key rotation and SPI check """
200    _init_psp_dev(cfg)
201
202    top_a = top_b = 0
203    with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
204        assoc_a = cfg.pspnl.rx_assoc({"version": 0,
205                                     "dev-id": cfg.psp_dev_id,
206                                     "sock-fd": s.fileno()})
207        top_a = assoc_a['rx-key']['spi'] >> 31
208        s.close()
209    rot = cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
210    with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
211        ksft_eq(rot['id'], cfg.psp_dev_id)
212        assoc_b = cfg.pspnl.rx_assoc({"version": 0,
213                                    "dev-id": cfg.psp_dev_id,
214                                    "sock-fd": s.fileno()})
215        top_b = assoc_b['rx-key']['spi'] >> 31
216        s.close()
217    ksft_ne(top_a, top_b)
218
219
220def assoc_basic(cfg):
221    """ Test creating associations """
222    _init_psp_dev(cfg)
223
224    with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
225        assoc = cfg.pspnl.rx_assoc({"version": 0,
226                                  "dev-id": cfg.psp_dev_id,
227                                  "sock-fd": s.fileno()})
228        ksft_eq(assoc['dev-id'], cfg.psp_dev_id)
229        ksft_gt(assoc['rx-key']['spi'], 0)
230        ksft_eq(len(assoc['rx-key']['key']), 16)
231
232        assoc = cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
233                                  "version": 0,
234                                  "tx-key": assoc['rx-key'],
235                                  "sock-fd": s.fileno()})
236        ksft_eq(len(assoc), 0)
237        s.close()
238
239
240def assoc_bad_dev(cfg):
241    """ Test creating associations with bad device ID """
242    _init_psp_dev(cfg)
243
244    with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
245        with ksft_raises(NlError) as cm:
246            cfg.pspnl.rx_assoc({"version": 0,
247                              "dev-id": cfg.psp_dev_id + 1234567,
248                              "sock-fd": s.fileno()})
249        ksft_eq(cm.exception.nl_msg.error, -errno.ENODEV)
250
251
252def assoc_sk_only_conn(cfg):
253    """ Test creating associations based on socket """
254    _init_psp_dev(cfg)
255
256    with _make_clr_conn(cfg) as s:
257        assoc = cfg.pspnl.rx_assoc({"version": 0,
258                                  "sock-fd": s.fileno()})
259        ksft_eq(assoc['dev-id'], cfg.psp_dev_id)
260        cfg.pspnl.tx_assoc({"version": 0,
261                          "tx-key": assoc['rx-key'],
262                          "sock-fd": s.fileno()})
263        _close_conn(cfg, s)
264
265
266def assoc_sk_only_mismatch(cfg):
267    """ Test creating associations based on socket (dev mismatch) """
268    _init_psp_dev(cfg)
269
270    with _make_clr_conn(cfg) as s:
271        with ksft_raises(NlError) as cm:
272            cfg.pspnl.rx_assoc({"version": 0,
273                              "dev-id": cfg.psp_dev_id + 1234567,
274                              "sock-fd": s.fileno()})
275        the_exception = cm.exception
276        ksft_eq(the_exception.nl_msg.extack['bad-attr'], ".dev-id")
277        ksft_eq(the_exception.nl_msg.error, -errno.EINVAL)
278        _close_conn(cfg, s)
279
280
281def assoc_sk_only_mismatch_tx(cfg):
282    """ Test creating associations based on socket (dev mismatch) """
283    _init_psp_dev(cfg)
284
285    with _make_clr_conn(cfg) as s:
286        with ksft_raises(NlError) as cm:
287            assoc = cfg.pspnl.rx_assoc({"version": 0,
288                                      "sock-fd": s.fileno()})
289            cfg.pspnl.tx_assoc({"version": 0,
290                              "tx-key": assoc['rx-key'],
291                              "dev-id": cfg.psp_dev_id + 1234567,
292                              "sock-fd": s.fileno()})
293        the_exception = cm.exception
294        ksft_eq(the_exception.nl_msg.extack['bad-attr'], ".dev-id")
295        ksft_eq(the_exception.nl_msg.error, -errno.EINVAL)
296        _close_conn(cfg, s)
297
298
299def assoc_sk_only_unconn(cfg):
300    """ Test creating associations based on socket (unconnected, should fail) """
301    _init_psp_dev(cfg)
302
303    with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
304        with ksft_raises(NlError) as cm:
305            cfg.pspnl.rx_assoc({"version": 0,
306                              "sock-fd": s.fileno()})
307        the_exception = cm.exception
308        ksft_eq(the_exception.nl_msg.extack['miss-type'], "dev-id")
309        ksft_eq(the_exception.nl_msg.error, -errno.EINVAL)
310
311
312def assoc_version_mismatch(cfg):
313    """ Test creating associations where Rx and Tx PSP versions do not match """
314    _init_psp_dev(cfg)
315
316    versions = list(cfg.psp_info['psp-versions-cap'])
317    if len(versions) < 2:
318        raise KsftSkipEx("Not enough PSP versions supported by the device for the test")
319
320    # Translate versions to integers
321    versions = [cfg.pspnl.consts["version"].entries[v].value for v in versions]
322
323    with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
324        rx = cfg.pspnl.rx_assoc({"version": versions[0],
325                                 "dev-id": cfg.psp_dev_id,
326                                 "sock-fd": s.fileno()})
327
328        for version in versions[1:]:
329            with ksft_raises(NlError) as cm:
330                cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
331                                    "version": version,
332                                    "tx-key": rx['rx-key'],
333                                    "sock-fd": s.fileno()})
334            the_exception = cm.exception
335            ksft_eq(the_exception.nl_msg.error, -errno.EINVAL)
336
337
338def _require_tls_ulp():
339    with socket.create_server(("localhost", 0)) as srv, \
340         socket.create_connection(srv.getsockname()) as s:
341        try:
342            s.setsockopt(socket.SOL_TCP, TCP_ULP, b"tls")
343        except OSError as exc:
344            raise KsftSkipEx("kTLS not available") from exc
345
346
347def assoc_psp_ulp_exclusive(cfg):
348    """ Test that a TCP ULP cannot be attached to a PSP socket """
349    _init_psp_dev(cfg)
350    _require_tls_ulp()
351
352    with _make_clr_conn(cfg) as s:
353        try:
354            cfg.pspnl.rx_assoc({"version": 0,
355                                "dev-id": cfg.psp_dev_id,
356                                "sock-fd": s.fileno()})
357            with ksft_raises(OSError) as cm:
358                s.setsockopt(socket.SOL_TCP, TCP_ULP, b"tls")
359            ksft_eq(cm.exception.errno, errno.EINVAL)
360        finally:
361            _close_conn(cfg, s)
362
363
364def assoc_ulp_psp_exclusive(cfg):
365    """ Test that a PSP assoc cannot be added to a socket with a TCP ULP """
366    _init_psp_dev(cfg)
367    _require_tls_ulp()
368
369    with _make_clr_conn(cfg) as s:
370        try:
371            s.setsockopt(socket.SOL_TCP, TCP_ULP, b"tls")
372            with ksft_raises(NlError) as cm:
373                cfg.pspnl.rx_assoc({"version": 0,
374                                    "dev-id": cfg.psp_dev_id,
375                                    "sock-fd": s.fileno()})
376            ksft_eq(cm.exception.nl_msg.error, -errno.EINVAL)
377            ksft_eq(cm.exception.nl_msg.extack['bad-attr'], ".sock-fd")
378        finally:
379            _close_conn(cfg, s)
380
381
382def assoc_twice(cfg):
383    """ Test reusing Tx assoc for two sockets """
384    _init_psp_dev(cfg)
385
386    def rx_assoc_check(s):
387        assoc = cfg.pspnl.rx_assoc({"version": 0,
388                                  "dev-id": cfg.psp_dev_id,
389                                  "sock-fd": s.fileno()})
390        ksft_eq(assoc['dev-id'], cfg.psp_dev_id)
391        ksft_gt(assoc['rx-key']['spi'], 0)
392        ksft_eq(len(assoc['rx-key']['key']), 16)
393
394        return assoc
395
396    with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
397        assoc = rx_assoc_check(s)
398        tx = cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
399                               "version": 0,
400                               "tx-key": assoc['rx-key'],
401                               "sock-fd": s.fileno()})
402        ksft_eq(len(tx), 0)
403
404        # Use the same Tx assoc second time
405        with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s2:
406            rx_assoc_check(s2)
407            tx = cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
408                                   "version": 0,
409                                   "tx-key": assoc['rx-key'],
410                                   "sock-fd": s2.fileno()})
411            ksft_eq(len(tx), 0)
412
413        s.close()
414
415
416def _data_basic_send(cfg, version, ipver):
417    """ Test basic data send """
418    _init_psp_dev(cfg)
419
420    # Version 0 is required by spec, don't let it skip
421    if version:
422        name = cfg.pspnl.consts["version"].entries_by_val[version].name
423        if name not in cfg.psp_info['psp-versions-cap']:
424            with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
425                with ksft_raises(NlError) as cm:
426                    cfg.pspnl.rx_assoc({"version": version,
427                                        "dev-id": cfg.psp_dev_id,
428                                        "sock-fd": s.fileno()})
429                ksft_eq(cm.exception.nl_msg.error, -errno.EOPNOTSUPP)
430            raise KsftSkipEx("PSP version not supported", name)
431
432    s = _make_psp_conn(cfg, version, ipver)
433
434    rx_assoc = cfg.pspnl.rx_assoc({"version": version,
435                                   "dev-id": cfg.psp_dev_id,
436                                   "sock-fd": s.fileno()})
437    rx = rx_assoc['rx-key']
438    tx = _spi_xchg(s, rx)
439
440    cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
441                        "version": version,
442                        "tx-key": tx,
443                        "sock-fd": s.fileno()})
444
445    data_len = _send_careful(cfg, s, 100)
446    _check_data_rx(cfg, data_len)
447    _close_psp_conn(cfg, s)
448
449
450def __bad_xfer_do(cfg, s, tx, version='hdr0-aes-gcm-128'):
451    # Make sure we accept the ACK for the SPI before we seal with the bad assoc
452    _check_data_outq(s, 0)
453
454    cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
455                        "version": version,
456                        "tx-key": tx,
457                        "sock-fd": s.fileno()})
458
459    data_len = _send_careful(cfg, s, 20)
460    _check_data_outq(s, data_len, force_wait=True)
461    _check_data_rx(cfg, 0)
462    _close_psp_conn(cfg, s)
463
464
465def data_send_bad_key(cfg):
466    """ Test send data with bad key """
467    _init_psp_dev(cfg)
468
469    s = _make_psp_conn(cfg)
470
471    rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
472                                   "dev-id": cfg.psp_dev_id,
473                                   "sock-fd": s.fileno()})
474    rx = rx_assoc['rx-key']
475    tx = _spi_xchg(s, rx)
476    tx['key'] = (tx['key'][0] ^ 0xff).to_bytes(1, 'little') + tx['key'][1:]
477    __bad_xfer_do(cfg, s, tx)
478
479
480def data_send_disconnect(cfg):
481    """ Test socket close after sending data """
482    _init_psp_dev(cfg)
483
484    with _make_psp_conn(cfg) as s:
485        assoc = cfg.pspnl.rx_assoc({"version": 0,
486                                  "sock-fd": s.fileno()})
487        tx = _spi_xchg(s, assoc['rx-key'])
488        cfg.pspnl.tx_assoc({"version": 0,
489                          "tx-key": tx,
490                          "sock-fd": s.fileno()})
491
492        data_len = _send_careful(cfg, s, 100)
493        _check_data_rx(cfg, data_len)
494
495        s.shutdown(socket.SHUT_RDWR)
496        s.close()
497
498
499def _data_mss_adjust(cfg, ipver):
500    _init_psp_dev(cfg)
501
502    # First figure out what the MSS would be without any adjustments
503    s = _make_clr_conn(cfg, ipver)
504    s.send(b"0123456789abcdef" * 1024)
505    _check_data_rx(cfg, 16 * 1024)
506    mss = s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG)
507    _close_conn(cfg, s)
508
509    s = _make_psp_conn(cfg, 0, ipver)
510    try:
511        rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
512                                     "dev-id": cfg.psp_dev_id,
513                                     "sock-fd": s.fileno()})
514        rx = rx_assoc['rx-key']
515        tx = _spi_xchg(s, rx)
516
517        rxmss = s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG)
518        ksft_eq(mss, rxmss)
519
520        cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
521                          "version": 0,
522                          "tx-key": tx,
523                          "sock-fd": s.fileno()})
524
525        txmss = s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG)
526        ksft_eq(mss, txmss + 40)
527
528        data_len = _send_careful(cfg, s, 100)
529        _check_data_rx(cfg, data_len)
530        _check_data_outq(s, 0)
531
532        txmss = s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG)
533        ksft_eq(mss, txmss + 40)
534    finally:
535        _close_psp_conn(cfg, s)
536
537
538def data_stale_key(cfg):
539    """ Test send on a double-rotated key """
540    _init_psp_dev(cfg)
541
542    prev_stale = _get_stat(cfg, 'stale-events')
543    s = _make_psp_conn(cfg)
544    try:
545        rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
546                                     "dev-id": cfg.psp_dev_id,
547                                     "sock-fd": s.fileno()})
548        rx = rx_assoc['rx-key']
549        tx = _spi_xchg(s, rx)
550
551        cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
552                          "version": 0,
553                          "tx-key": tx,
554                          "sock-fd": s.fileno()})
555
556        data_len = _send_careful(cfg, s, 100)
557        _check_data_rx(cfg, data_len)
558        _check_data_outq(s, 0)
559
560        cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
561        cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
562
563        cur_stale = _get_stat(cfg, 'stale-events')
564        ksft_gt(cur_stale, prev_stale)
565
566        s.send(b'0123456789' * 200)
567        _check_data_outq(s, 2000, force_wait=True)
568    finally:
569        _close_psp_conn(cfg, s)
570
571
572def __nsim_psp_rereg(cfg):
573    # The PSP dev ID will change, remember what was there before
574    before = set([x['id'] for x in cfg.pspnl.dev_get({}, dump=True)])
575
576    cfg._ns.nsims[0].dfs_write('psp_rereg', '1')
577
578    after = set([x['id'] for x in cfg.pspnl.dev_get({}, dump=True)])
579
580    new_devs = list(after - before)
581    ksft_eq(len(new_devs), 1)
582    cfg.psp_dev_id = list(after - before)[0]
583
584
585def removal_device_rx(cfg):
586    """ Test removing a netdev / PSD with active Rx assoc """
587
588    # We could technically devlink reload real devices, too
589    # but that kills the control socket. So test this on
590    # netdevsim only for now
591    cfg.require_nsim()
592
593    s = _make_clr_conn(cfg)
594    try:
595        rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
596                                       "dev-id": cfg.psp_dev_id,
597                                       "sock-fd": s.fileno()})
598        ksft_not_none(rx_assoc)
599
600        __nsim_psp_rereg(cfg)
601    finally:
602        _close_conn(cfg, s)
603
604
605def removal_device_bi(cfg):
606    """ Test removing a netdev / PSD with active Rx/Tx assoc """
607
608    # We could technically devlink reload real devices, too
609    # but that kills the control socket. So test this on
610    # netdevsim only for now
611    cfg.require_nsim()
612
613    s = _make_clr_conn(cfg)
614    try:
615        rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
616                                       "dev-id": cfg.psp_dev_id,
617                                       "sock-fd": s.fileno()})
618        cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
619                            "version": 0,
620                            "tx-key": rx_assoc['rx-key'],
621                            "sock-fd": s.fileno()})
622        __nsim_psp_rereg(cfg)
623    finally:
624        _close_conn(cfg, s)
625
626
627def _get_psp_ver_ip_variants():
628    for ver in range(4):
629        for ipv in ("4", "6"):
630            yield KsftNamedVariant(f"v{ver}_ip{ipv}", ver, ipv)
631
632
633def _get_ip_variants():
634    for ipv in ("4", "6"):
635        yield KsftNamedVariant(f"ip{ipv}", ipv)
636
637
638@ksft_variants(_get_psp_ver_ip_variants())
639def data_basic_send(cfg, version, ipver):
640    """Test basic PSP data send."""
641    cfg.require_ipver(ipver)
642    _data_basic_send(cfg, version, ipver)
643
644
645@ksft_variants(_get_ip_variants())
646def data_mss_adjust(cfg, ipver):
647    """Test MSS adjustment with PSP."""
648    cfg.require_ipver(ipver)
649    _data_mss_adjust(cfg, ipver)
650
651
652def _check_assoc_list(cfg, psp_dev_id, ifindex, nsid=None):
653    """Verify assoc-list contains device with given ifindex, no duplicates."""
654    dev_info = cfg.pspnl.dev_get({'id': psp_dev_id})
655
656    ksft_true('assoc-list' in dev_info,
657              "No assoc-list in dev_get() response after association")
658    found = False
659    for assoc in dev_info['assoc-list']:
660        if assoc['ifindex'] != ifindex:
661            continue
662        if nsid is not None and assoc['nsid'] != nsid:
663            continue
664        ksft_eq(found, False, "Duplicate assoc entry found")
665        found = True
666    ksft_eq(found, True,
667            "Associated device not found in dev_get() response")
668
669
670def _data_basic_send_netkit_psp_assoc(cfg, version, ipver):
671    """
672    Test basic data send with netkit interface associated with PSP dev.
673    """
674    _assoc_nk_guest(cfg)
675
676    # Enter guest namespace (netns) to run PSP test
677    with NetNSEnter(cfg.netns.name):
678        cfg.pspnl = PSPFamily()
679
680        sock = _make_psp_conn(cfg, version, ipver)
681
682        rx_assoc = cfg.pspnl.rx_assoc({"version": version,
683                                       "dev-id": cfg.psp_dev_id,
684                                       "sock-fd": sock.fileno()})
685        rx_key = rx_assoc['rx-key']
686        tx_key = _spi_xchg(sock, rx_key)
687
688        cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
689                            "version": version,
690                            "tx-key": tx_key,
691                            "sock-fd": sock.fileno()})
692
693        data_len = _send_careful(cfg, sock, 100)
694        _check_data_rx(cfg, data_len)
695        _close_psp_conn(cfg, sock)
696
697
698def _assoc_check_list(cfg):
699    """Test that assoc-list is correctly populated after dev-assoc."""
700    _assoc_nk_guest(cfg)
701    _check_assoc_list(cfg, cfg.psp_dev_id, cfg.nk_guest_ifindex,
702                      cfg.psp_dev_peer_nsid)
703
704
705def _get_psp_ver_ip6_variants():
706    for ver in range(4):
707        yield KsftNamedVariant(f"v{ver}_ip6", ver, "6")
708
709
710@ksft_variants(_get_psp_ver_ip6_variants())
711def data_basic_send_netkit_psp_assoc(cfg, version, ipver):
712    """Test PSP data send via netkit with dev-assoc."""
713    cfg.require_ipver(ipver)
714    _data_basic_send_netkit_psp_assoc(cfg, version, ipver)
715
716
717def _key_rotation_notify_multi_ns_netkit(cfg):
718    """ Test key rotation notifications across multiple namespaces using netkit """
719    _assoc_nk_guest(cfg)
720
721    # Create listener in guest namespace; socket stays bound to that ns
722    with NetNSEnter(cfg.netns.name):
723        peer_pspnl = PSPFamily()
724        peer_pspnl.ntf_subscribe('use')
725
726    # Create listener in main namespace
727    main_pspnl = PSPFamily()
728    main_pspnl.ntf_subscribe('use')
729
730    # Trigger key rotation on the PSP device
731    cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
732
733    # Poll both sockets from main thread
734    for pspnl, label in [(main_pspnl, "main"), (peer_pspnl, "guest")]:
735        for ntf in pspnl.poll_ntf(duration=10):
736            if ntf['msg'].get('id') == cfg.psp_dev_id:
737                break
738        else:
739            raise KsftFailEx(
740                f"No key rotation notification received"
741                f" in {label} namespace")
742
743
744def _dev_change_notify_multi_ns_netkit(cfg):
745    """ Test dev_change notifications across multiple namespaces using netkit """
746    _assoc_nk_guest(cfg)
747
748    # Create listener in guest namespace; socket stays bound to that ns
749    with NetNSEnter(cfg.netns.name):
750        peer_pspnl = PSPFamily()
751        peer_pspnl.ntf_subscribe('mgmt')
752
753    # Create listener in main namespace
754    main_pspnl = PSPFamily()
755    main_pspnl.ntf_subscribe('mgmt')
756
757    # Trigger dev_change by calling dev_set (notification is always sent)
758    cfg.pspnl.dev_set({'id': cfg.psp_dev_id,
759                       'psp-versions-ena': cfg.psp_info['psp-versions-cap']})
760
761    # Poll both sockets from main thread
762    for pspnl, label in [(main_pspnl, "main"), (peer_pspnl, "guest")]:
763        for ntf in pspnl.poll_ntf(duration=10):
764            if ntf['msg'].get('id') == cfg.psp_dev_id:
765                break
766        else:
767            raise KsftFailEx(
768                f"No dev_change notification received"
769                f" in {label} namespace")
770
771
772def _psp_dev_get_check_netkit_psp_assoc(cfg):
773    """ Check psp dev-get output with netkit interface associated with PSP dev """
774    _assoc_nk_guest(cfg)
775
776    # Check 1: In default netns, verify dev-get has correct ifindex and assoc-list
777    dev_info = cfg.pspnl.dev_get({'id': cfg.psp_dev_id})
778    ksft_eq(dev_info['ifindex'], cfg.psp_ifindex)
779    _check_assoc_list(cfg, cfg.psp_dev_id, cfg.nk_guest_ifindex,
780                      cfg.psp_dev_peer_nsid)
781
782    # Check 2: In guest netns, verify dev-get has assoc-list with nk_guest device
783    with NetNSEnter(cfg.netns.name):
784        peer_pspnl = PSPFamily()
785
786        # Dump all devices in the guest namespace
787        peer_devices = peer_pspnl.dev_get({}, dump=True)
788
789        # Find the device with by-association flag
790        peer_dev = None
791        for dev in peer_devices:
792            if dev.get('by-association'):
793                peer_dev = dev
794                break
795
796        ksft_not_none(peer_dev, "No PSP device found with by-association flag in guest netns")
797
798        # Verify assoc-list contains the nk_guest device
799        ksft_true('assoc-list' in peer_dev and len(peer_dev['assoc-list']) > 0,
800                  "Guest device should have assoc-list with local devices")
801
802        # Verify the assoc-list contains nk_guest ifindex with nsid=-1 (same namespace)
803        found = False
804        for assoc in peer_dev['assoc-list']:
805            if assoc['ifindex'] == cfg.nk_guest_ifindex:
806                ksft_eq(assoc['nsid'], -1,
807                        "nsid should be -1 (NETNSA_NSID_NOT_ASSIGNED) for same-namespace device")
808                found = True
809                break
810        ksft_true(found, "nk_guest ifindex not found in assoc-list")
811
812
813def _dev_assoc_no_nsid(cfg):
814    """ Test dev-assoc and dev-disassoc without nsid attribute """
815    _init_psp_dev(cfg, True)
816
817    # Associate without nsid - should look up ifindex in caller's netns
818    cfg.pspnl.dev_assoc({'id': cfg.psp_dev_id,
819                         'ifindex': cfg.nk_host_ifindex})
820    defer(_try_disassoc, cfg,
821          cfg.psp_dev_id, cfg.nk_host_ifindex)
822    defer(delattr, cfg, 'psp_dev_id')
823    defer(delattr, cfg, 'psp_info')
824
825    # Verify assoc-list contains the device (match by ifindex only)
826    _check_assoc_list(cfg, cfg.psp_dev_id, cfg.nk_host_ifindex)
827
828    # Disassociate without nsid - should also use caller's netns
829    cfg.pspnl.dev_disassoc({'id': cfg.psp_dev_id,
830                            'ifindex': cfg.nk_host_ifindex})
831
832    # Verify assoc-list no longer contains the device
833    dev_info = cfg.pspnl.dev_get({'id': cfg.psp_dev_id})
834    found = False
835    if 'assoc-list' in dev_info:
836        for assoc in dev_info['assoc-list']:
837            if assoc['ifindex'] == cfg.nk_host_ifindex:
838                found = True
839                break
840    ksft_true(not found, "Device should not be in assoc-list after disassociation")
841
842
843def _psp_dev_assoc_cleanup_on_netkit_del(cfg):
844    """Test that assoc-list is cleared when associated netkit is deleted.
845
846    Creates a disposable netkit pair for this test to avoid destroying
847    the shared environment.
848    """
849    _init_psp_dev(cfg, True)
850    defer(delattr, cfg, 'psp_dev_id')
851    defer(delattr, cfg, 'psp_info')
852
853    existing = {cfg.nk_host_ifindex, cfg.nk_guest_ifindex}
854
855    # Create a temporary netkit pair
856    tmp_host_name = "tmp_nk_host"
857    tmp_guest_name = "tmp_nk_guest"
858    rtnl = RtnlFamily()
859    rtnl.newlink(
860        {
861            "ifname": tmp_host_name,
862            "linkinfo": {
863                "kind": "netkit",
864                "data": {
865                    "mode": "l2",
866                    "policy": "forward",
867                    "peer-policy": "forward",
868                },
869            },
870        },
871        flags=[Netlink.NLM_F_CREATE, Netlink.NLM_F_EXCL],
872    )
873    cleanup_netkit = defer(ip, f"link del {tmp_host_name}")
874
875    # Find the peer by diffing against existing netkit ifindexes
876    all_links = ip("-d link show", json=True)
877    tmp_peer = [link for link in all_links
878                if link.get('linkinfo', {}).get('info_kind') == 'netkit'
879                and link['ifindex'] not in existing
880                and link['ifname'] != tmp_host_name]
881    ksft_eq(len(tmp_peer), 1,
882            "Failed to find temporary netkit peer")
883    guest_name = tmp_peer[0]['ifname']
884
885    # Rename and move guest end into the test namespace
886    ip(f"link set dev {guest_name} name {tmp_guest_name}")
887    ip(f"link set dev {tmp_guest_name} netns {cfg.netns.name}")
888    tmp_guest_dev = ip(f"link show dev {tmp_guest_name}",
889                       json=True, ns=cfg.netns)[0]
890    tmp_guest_ifindex = tmp_guest_dev['ifindex']
891    ip(f"link set dev {tmp_guest_name} up", ns=cfg.netns)
892
893    # Associate PSP device with the temporary guest interface
894    cfg.pspnl.dev_assoc({'id': cfg.psp_dev_id,
895                         'ifindex': tmp_guest_ifindex,
896                         'nsid': cfg.psp_dev_peer_nsid})
897
898    # Verify assoc-list contains the temporary device
899    _check_assoc_list(cfg, cfg.psp_dev_id, tmp_guest_ifindex,
900                      cfg.psp_dev_peer_nsid)
901
902    # Delete the temporary netkit pair (deleting one end removes both)
903    ip(f"link del {tmp_host_name}")
904    cleanup_netkit.cancel()
905
906    # Verify assoc-list is cleared after netkit deletion
907    dev_info = cfg.pspnl.dev_get({'id': cfg.psp_dev_id})
908    ksft_true('assoc-list' not in dev_info
909              or len(dev_info['assoc-list']) == 0,
910              "assoc-list should be empty after netkit deletion")
911
912
913def _try_disassoc(cfg, psp_dev_id, ifindex, nsid=None):
914    """Best-effort disassociate, ignoring errors if already removed."""
915    try:
916        params = {'id': psp_dev_id, 'ifindex': ifindex}
917        if nsid is not None:
918            params['nsid'] = nsid
919        cfg.pspnl.dev_disassoc(params)
920    except NlError:
921        pass
922
923
924def _assoc_nk_guest(cfg):
925    """Associate nk_guest with PSP device and register cleanup via defer()."""
926    _init_psp_dev(cfg, True)
927
928    cfg.pspnl.dev_assoc({'id': cfg.psp_dev_id,
929                         'ifindex': cfg.nk_guest_ifindex,
930                         'nsid': cfg.psp_dev_peer_nsid})
931    defer(_disassoc_nk_guest, cfg,
932          cfg.psp_dev_id, cfg.nk_guest_ifindex)
933
934
935def _disassoc_nk_guest(cfg, psp_dev_id, nk_guest_ifindex):
936    """Disassociate nk_guest and reset cfg PSP state."""
937    pspnl = PSPFamily()
938    pspnl.dev_disassoc({'id': psp_dev_id, 'ifindex': nk_guest_ifindex,
939                        'nsid': cfg.psp_dev_peer_nsid})
940    cfg.pspnl = pspnl
941    del cfg.psp_dev_id
942    del cfg.psp_info
943
944
945def _get_nsid(ns_name):
946    """Get the nsid for a namespace."""
947    for entry in ip("netns list-id", json=True):
948        if entry.get("name") == str(ns_name):
949            return entry["nsid"]
950    raise KsftSkipEx(f"nsid not found for namespace {ns_name}")
951
952
953def _setup_psp_attributes(cfg):
954    # pylint: disable=protected-access
955    """
956    Set up PSP-specific attributes on the environment.
957
958    This sets attributes needed for PSP tests based on whether we're using
959    netdevsim or a real NIC.
960    """
961    if cfg._ns is not None:
962        # netdevsim case: PSP device is the local dev (in host namespace)
963        cfg.psp_dev = cfg._ns.nsims[0].dev
964        cfg.psp_ifname = cfg.psp_dev['ifname']
965        cfg.psp_ifindex = cfg.psp_dev['ifindex']
966
967        # PSP peer device is the remote dev (in _netns, where psp_responder runs)
968        cfg.psp_dev_peer = cfg._ns_peer.nsims[0].dev
969        cfg.psp_dev_peer_ifname = cfg.psp_dev_peer['ifname']
970        cfg.psp_dev_peer_ifindex = cfg.psp_dev_peer['ifindex']
971    else:
972        # Real NIC case: PSP device is the local interface
973        cfg.psp_dev = cfg.dev
974        cfg.psp_ifname = cfg.ifname
975        cfg.psp_ifindex = cfg.ifindex
976
977        # PSP peer device is the remote interface
978        cfg.psp_dev_peer = cfg.remote_dev
979        cfg.psp_dev_peer_ifname = cfg.remote_ifname
980        cfg.psp_dev_peer_ifindex = cfg.remote_ifindex
981
982    # Get nsid for the guest namespace (netns) where nk_guest is
983    cfg.psp_dev_peer_nsid = _get_nsid(cfg.netns.name)
984
985
986
987def main() -> None:
988    """ Ksft boiler plate main """
989
990    # Make sure LOCAL_PREFIX_V6 is set
991    if "LOCAL_PREFIX_V6" not in os.environ:
992        os.environ["LOCAL_PREFIX_V6"] = "2001:db8:2::"
993
994    try:
995        env = NetDrvContEnv(__file__, primary_rx_redirect=True)
996        has_cont = True
997    except KsftSkipEx:
998        env = NetDrvEpEnv(__file__)
999        has_cont = False
1000
1001    with env as cfg:
1002        cfg.pspnl = PSPFamily()
1003
1004        if has_cont:
1005            _setup_psp_attributes(cfg)
1006
1007        # Set up responder and communication sock
1008        # psp_responder runs in _netns (remote namespace with psp_dev_peer)
1009        responder = cfg.remote.deploy("psp_responder")
1010
1011        cfg.comm_port = rand_port()
1012        srv = None
1013        try:
1014            with bkg(responder + f" -p {cfg.comm_port} -i {cfg.remote_ifindex}",
1015                     host=cfg.remote, exit_wait=True) as srv:
1016                wait_port_listen(cfg.comm_port, host=cfg.remote)
1017
1018                cfg.comm_sock = socket.create_connection((cfg.remote_addr,
1019                                                          cfg.comm_port),
1020                                                         timeout=1)
1021
1022                cases = [data_basic_send, data_mss_adjust]
1023
1024                if has_cont:
1025                    cases += [
1026                        _assoc_check_list,
1027                        data_basic_send_netkit_psp_assoc,
1028                        _key_rotation_notify_multi_ns_netkit,
1029                        _dev_change_notify_multi_ns_netkit,
1030                        _psp_dev_get_check_netkit_psp_assoc,
1031                        _dev_assoc_no_nsid,
1032                        _psp_dev_assoc_cleanup_on_netkit_del,
1033                    ]
1034
1035                ksft_run(cases=cases, globs=globals(),
1036                         case_pfx={"dev_", "data_", "assoc_", "removal_"},
1037                         args=(cfg, ))
1038
1039                cfg.comm_sock.send(b"exit\0")
1040                cfg.comm_sock.close()
1041        finally:
1042            if srv and (srv.stdout or srv.stderr):
1043                ksft_pr("")
1044                ksft_pr(f"Responder logs ({srv.ret}):")
1045            if srv and srv.stdout:
1046                ksft_pr("STDOUT:\n#  " + srv.stdout.strip().replace("\n", "\n#  "))
1047            if srv and srv.stderr:
1048                ksft_pr("STDERR:\n#  " + srv.stderr.strip().replace("\n", "\n#  "))
1049    ksft_exit()
1050
1051
1052if __name__ == "__main__":
1053    main()
1054