xref: /linux/tools/testing/selftests/drivers/net/psp.py (revision b3827c91cc9979fe04d99e016fb9c5f6260f29a0)
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 ksft_not_none
17from lib.py import KsftSkipEx
18from lib.py import NetDrvEpEnv, PSPFamily, NlError
19from lib.py import bkg, rand_port, wait_port_listen
20
21
22def _get_outq(s):
23    one = b'\0' * 4
24    outq = fcntl.ioctl(s.fileno(), termios.TIOCOUTQ, one)
25    return struct.unpack("I", outq)[0]
26
27
28def _send_with_ack(cfg, msg):
29    cfg.comm_sock.send(msg)
30    response = cfg.comm_sock.recv(4)
31    if response != b'ack\0':
32        raise RuntimeError("Unexpected server response", response)
33
34
35def _remote_read_len(cfg):
36    cfg.comm_sock.send(b'read len\0')
37    return int(cfg.comm_sock.recv(1024)[:-1].decode('utf-8'))
38
39
40def _make_clr_conn(cfg, ipver=None):
41    _send_with_ack(cfg, b'conn clr\0')
42    remote_addr = cfg.remote_addr_v[ipver] if ipver else cfg.remote_addr
43    s = socket.create_connection((remote_addr, cfg.comm_port), )
44    return s
45
46
47def _make_psp_conn(cfg, version=0, ipver=None):
48    _send_with_ack(cfg, b'conn psp\0' + struct.pack('BB', version, version))
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 _close_conn(cfg, s):
55    _send_with_ack(cfg, b'data close\0')
56    s.close()
57
58
59def _close_psp_conn(cfg, s):
60    _close_conn(cfg, s)
61
62
63def _spi_xchg(s, rx):
64    s.send(struct.pack('I', rx['spi']) + rx['key'])
65    tx = s.recv(4 + len(rx['key']))
66    return {
67        'spi': struct.unpack('I', tx[:4])[0],
68        'key': tx[4:]
69    }
70
71
72def _send_careful(cfg, s, rounds):
73    data = b'0123456789' * 200
74    for i in range(rounds):
75        n = 0
76        for _ in range(10): # allow 10 retries
77            try:
78                n += s.send(data[n:], socket.MSG_DONTWAIT)
79                if n == len(data):
80                    break
81            except BlockingIOError:
82                time.sleep(0.05)
83        else:
84            rlen = _remote_read_len(cfg)
85            outq = _get_outq(s)
86            report = f'sent: {i * len(data) + n} remote len: {rlen} outq: {outq}'
87            raise RuntimeError(report)
88
89    return len(data) * rounds
90
91
92def _check_data_rx(cfg, exp_len):
93    read_len = -1
94    for _ in range(30):
95        cfg.comm_sock.send(b'read len\0')
96        read_len = int(cfg.comm_sock.recv(1024)[:-1].decode('utf-8'))
97        if read_len == exp_len:
98            break
99        time.sleep(0.01)
100    ksft_eq(read_len, exp_len)
101
102
103def _check_data_outq(s, exp_len, force_wait=False):
104    outq = 0
105    for _ in range(10):
106        outq = _get_outq(s)
107        if not force_wait and outq == exp_len:
108            break
109        time.sleep(0.01)
110    ksft_eq(outq, exp_len)
111
112
113def _get_stat(cfg, key):
114    return cfg.pspnl.get_stats({'dev-id': cfg.psp_dev_id})[key]
115
116#
117# Test case boiler plate
118#
119
120def _init_psp_dev(cfg):
121    if not hasattr(cfg, 'psp_dev_id'):
122        # Figure out which local device we are testing against
123        for dev in cfg.pspnl.dev_get({}, dump=True):
124            if dev['ifindex'] == cfg.ifindex:
125                cfg.psp_info = dev
126                cfg.psp_dev_id = cfg.psp_info['id']
127                break
128        else:
129            raise KsftSkipEx("No PSP devices found")
130
131    # Enable PSP if necessary
132    cap = cfg.psp_info['psp-versions-cap']
133    ena = cfg.psp_info['psp-versions-ena']
134    if cap != ena:
135        cfg.pspnl.dev_set({'id': cfg.psp_dev_id, 'psp-versions-ena': cap})
136        defer(cfg.pspnl.dev_set, {'id': cfg.psp_dev_id,
137                                  'psp-versions-ena': ena })
138
139#
140# Test cases
141#
142
143def dev_list_devices(cfg):
144    """ Dump all devices """
145    _init_psp_dev(cfg)
146
147    devices = cfg.pspnl.dev_get({}, dump=True)
148
149    found = False
150    for dev in devices:
151        found |= dev['id'] == cfg.psp_dev_id
152    ksft_true(found)
153
154
155def dev_get_device(cfg):
156    """ Get the device we intend to use """
157    _init_psp_dev(cfg)
158
159    dev = cfg.pspnl.dev_get({'id': cfg.psp_dev_id})
160    ksft_eq(dev['id'], cfg.psp_dev_id)
161
162
163def dev_get_device_bad(cfg):
164    """ Test getting device which doesn't exist """
165    raised = False
166    try:
167        cfg.pspnl.dev_get({'id': 1234567})
168    except NlError as e:
169        ksft_eq(e.nl_msg.error, -errno.ENODEV)
170        raised = True
171    ksft_true(raised)
172
173
174def dev_rotate(cfg):
175    """ Test key rotation """
176    _init_psp_dev(cfg)
177
178    prev_rotations = _get_stat(cfg, 'key-rotations')
179
180    rot = cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
181    ksft_eq(rot['id'], cfg.psp_dev_id)
182    rot = cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
183    ksft_eq(rot['id'], cfg.psp_dev_id)
184
185    cur_rotations = _get_stat(cfg, 'key-rotations')
186    ksft_eq(cur_rotations, prev_rotations + 2)
187
188
189def dev_rotate_spi(cfg):
190    """ Test key rotation and SPI check """
191    _init_psp_dev(cfg)
192
193    top_a = top_b = 0
194    with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
195        assoc_a = cfg.pspnl.rx_assoc({"version": 0,
196                                     "dev-id": cfg.psp_dev_id,
197                                     "sock-fd": s.fileno()})
198        top_a = assoc_a['rx-key']['spi'] >> 31
199        s.close()
200    rot = cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
201    with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
202        ksft_eq(rot['id'], cfg.psp_dev_id)
203        assoc_b = cfg.pspnl.rx_assoc({"version": 0,
204                                    "dev-id": cfg.psp_dev_id,
205                                    "sock-fd": s.fileno()})
206        top_b = assoc_b['rx-key']['spi'] >> 31
207        s.close()
208    ksft_ne(top_a, top_b)
209
210
211def assoc_basic(cfg):
212    """ Test creating associations """
213    _init_psp_dev(cfg)
214
215    with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
216        assoc = cfg.pspnl.rx_assoc({"version": 0,
217                                  "dev-id": cfg.psp_dev_id,
218                                  "sock-fd": s.fileno()})
219        ksft_eq(assoc['dev-id'], cfg.psp_dev_id)
220        ksft_gt(assoc['rx-key']['spi'], 0)
221        ksft_eq(len(assoc['rx-key']['key']), 16)
222
223        assoc = cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
224                                  "version": 0,
225                                  "tx-key": assoc['rx-key'],
226                                  "sock-fd": s.fileno()})
227        ksft_eq(len(assoc), 0)
228        s.close()
229
230
231def assoc_bad_dev(cfg):
232    """ Test creating associations with bad device ID """
233    _init_psp_dev(cfg)
234
235    with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
236        with ksft_raises(NlError) as cm:
237            cfg.pspnl.rx_assoc({"version": 0,
238                              "dev-id": cfg.psp_dev_id + 1234567,
239                              "sock-fd": s.fileno()})
240        ksft_eq(cm.exception.nl_msg.error, -errno.ENODEV)
241
242
243def assoc_sk_only_conn(cfg):
244    """ Test creating associations based on socket """
245    _init_psp_dev(cfg)
246
247    with _make_clr_conn(cfg) as s:
248        assoc = cfg.pspnl.rx_assoc({"version": 0,
249                                  "sock-fd": s.fileno()})
250        ksft_eq(assoc['dev-id'], cfg.psp_dev_id)
251        cfg.pspnl.tx_assoc({"version": 0,
252                          "tx-key": assoc['rx-key'],
253                          "sock-fd": s.fileno()})
254        _close_conn(cfg, s)
255
256
257def assoc_sk_only_mismatch(cfg):
258    """ Test creating associations based on socket (dev mismatch) """
259    _init_psp_dev(cfg)
260
261    with _make_clr_conn(cfg) as s:
262        with ksft_raises(NlError) as cm:
263            cfg.pspnl.rx_assoc({"version": 0,
264                              "dev-id": cfg.psp_dev_id + 1234567,
265                              "sock-fd": s.fileno()})
266        the_exception = cm.exception
267        ksft_eq(the_exception.nl_msg.extack['bad-attr'], ".dev-id")
268        ksft_eq(the_exception.nl_msg.error, -errno.EINVAL)
269        _close_conn(cfg, s)
270
271
272def assoc_sk_only_mismatch_tx(cfg):
273    """ Test creating associations based on socket (dev mismatch) """
274    _init_psp_dev(cfg)
275
276    with _make_clr_conn(cfg) as s:
277        with ksft_raises(NlError) as cm:
278            assoc = cfg.pspnl.rx_assoc({"version": 0,
279                                      "sock-fd": s.fileno()})
280            cfg.pspnl.tx_assoc({"version": 0,
281                              "tx-key": assoc['rx-key'],
282                              "dev-id": cfg.psp_dev_id + 1234567,
283                              "sock-fd": s.fileno()})
284        the_exception = cm.exception
285        ksft_eq(the_exception.nl_msg.extack['bad-attr'], ".dev-id")
286        ksft_eq(the_exception.nl_msg.error, -errno.EINVAL)
287        _close_conn(cfg, s)
288
289
290def assoc_sk_only_unconn(cfg):
291    """ Test creating associations based on socket (unconnected, should fail) """
292    _init_psp_dev(cfg)
293
294    with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
295        with ksft_raises(NlError) as cm:
296            cfg.pspnl.rx_assoc({"version": 0,
297                              "sock-fd": s.fileno()})
298        the_exception = cm.exception
299        ksft_eq(the_exception.nl_msg.extack['miss-type'], "dev-id")
300        ksft_eq(the_exception.nl_msg.error, -errno.EINVAL)
301
302
303def assoc_version_mismatch(cfg):
304    """ Test creating associations where Rx and Tx PSP versions do not match """
305    _init_psp_dev(cfg)
306
307    versions = list(cfg.psp_info['psp-versions-cap'])
308    if len(versions) < 2:
309        raise KsftSkipEx("Not enough PSP versions supported by the device for the test")
310
311    # Translate versions to integers
312    versions = [cfg.pspnl.consts["version"].entries[v].value for v in versions]
313
314    with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
315        rx = cfg.pspnl.rx_assoc({"version": versions[0],
316                                 "dev-id": cfg.psp_dev_id,
317                                 "sock-fd": s.fileno()})
318
319        for version in versions[1:]:
320            with ksft_raises(NlError) as cm:
321                cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
322                                    "version": version,
323                                    "tx-key": rx['rx-key'],
324                                    "sock-fd": s.fileno()})
325            the_exception = cm.exception
326            ksft_eq(the_exception.nl_msg.error, -errno.EINVAL)
327
328
329def assoc_twice(cfg):
330    """ Test reusing Tx assoc for two sockets """
331    _init_psp_dev(cfg)
332
333    def rx_assoc_check(s):
334        assoc = cfg.pspnl.rx_assoc({"version": 0,
335                                  "dev-id": cfg.psp_dev_id,
336                                  "sock-fd": s.fileno()})
337        ksft_eq(assoc['dev-id'], cfg.psp_dev_id)
338        ksft_gt(assoc['rx-key']['spi'], 0)
339        ksft_eq(len(assoc['rx-key']['key']), 16)
340
341        return assoc
342
343    with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
344        assoc = rx_assoc_check(s)
345        tx = cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
346                               "version": 0,
347                               "tx-key": assoc['rx-key'],
348                               "sock-fd": s.fileno()})
349        ksft_eq(len(tx), 0)
350
351        # Use the same Tx assoc second time
352        with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s2:
353            rx_assoc_check(s2)
354            tx = cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
355                                   "version": 0,
356                                   "tx-key": assoc['rx-key'],
357                                   "sock-fd": s2.fileno()})
358            ksft_eq(len(tx), 0)
359
360        s.close()
361
362
363def _data_basic_send(cfg, version, ipver):
364    """ Test basic data send """
365    _init_psp_dev(cfg)
366
367    # Version 0 is required by spec, don't let it skip
368    if version:
369        name = cfg.pspnl.consts["version"].entries_by_val[version].name
370        if name not in cfg.psp_info['psp-versions-cap']:
371            with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s:
372                with ksft_raises(NlError) as cm:
373                    cfg.pspnl.rx_assoc({"version": version,
374                                        "dev-id": cfg.psp_dev_id,
375                                        "sock-fd": s.fileno()})
376                ksft_eq(cm.exception.nl_msg.error, -errno.EOPNOTSUPP)
377            raise KsftSkipEx("PSP version not supported", name)
378
379    s = _make_psp_conn(cfg, version, ipver)
380
381    rx_assoc = cfg.pspnl.rx_assoc({"version": version,
382                                   "dev-id": cfg.psp_dev_id,
383                                   "sock-fd": s.fileno()})
384    rx = rx_assoc['rx-key']
385    tx = _spi_xchg(s, rx)
386
387    cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
388                        "version": version,
389                        "tx-key": tx,
390                        "sock-fd": s.fileno()})
391
392    data_len = _send_careful(cfg, s, 100)
393    _check_data_rx(cfg, data_len)
394    _close_psp_conn(cfg, s)
395
396
397def __bad_xfer_do(cfg, s, tx, version='hdr0-aes-gcm-128'):
398    # Make sure we accept the ACK for the SPI before we seal with the bad assoc
399    _check_data_outq(s, 0)
400
401    cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
402                        "version": version,
403                        "tx-key": tx,
404                        "sock-fd": s.fileno()})
405
406    data_len = _send_careful(cfg, s, 20)
407    _check_data_outq(s, data_len, force_wait=True)
408    _check_data_rx(cfg, 0)
409    _close_psp_conn(cfg, s)
410
411
412def data_send_bad_key(cfg):
413    """ Test send data with bad key """
414    _init_psp_dev(cfg)
415
416    s = _make_psp_conn(cfg)
417
418    rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
419                                   "dev-id": cfg.psp_dev_id,
420                                   "sock-fd": s.fileno()})
421    rx = rx_assoc['rx-key']
422    tx = _spi_xchg(s, rx)
423    tx['key'] = (tx['key'][0] ^ 0xff).to_bytes(1, 'little') + tx['key'][1:]
424    __bad_xfer_do(cfg, s, tx)
425
426
427def data_send_disconnect(cfg):
428    """ Test socket close after sending data """
429    _init_psp_dev(cfg)
430
431    with _make_psp_conn(cfg) as s:
432        assoc = cfg.pspnl.rx_assoc({"version": 0,
433                                  "sock-fd": s.fileno()})
434        tx = _spi_xchg(s, assoc['rx-key'])
435        cfg.pspnl.tx_assoc({"version": 0,
436                          "tx-key": tx,
437                          "sock-fd": s.fileno()})
438
439        data_len = _send_careful(cfg, s, 100)
440        _check_data_rx(cfg, data_len)
441
442        s.shutdown(socket.SHUT_RDWR)
443        s.close()
444
445
446def _data_mss_adjust(cfg, ipver):
447    _init_psp_dev(cfg)
448
449    # First figure out what the MSS would be without any adjustments
450    s = _make_clr_conn(cfg, ipver)
451    s.send(b"0123456789abcdef" * 1024)
452    _check_data_rx(cfg, 16 * 1024)
453    mss = s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG)
454    _close_conn(cfg, s)
455
456    s = _make_psp_conn(cfg, 0, ipver)
457    try:
458        rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
459                                     "dev-id": cfg.psp_dev_id,
460                                     "sock-fd": s.fileno()})
461        rx = rx_assoc['rx-key']
462        tx = _spi_xchg(s, rx)
463
464        rxmss = s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG)
465        ksft_eq(mss, rxmss)
466
467        cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
468                          "version": 0,
469                          "tx-key": tx,
470                          "sock-fd": s.fileno()})
471
472        txmss = s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG)
473        ksft_eq(mss, txmss + 40)
474
475        data_len = _send_careful(cfg, s, 100)
476        _check_data_rx(cfg, data_len)
477        _check_data_outq(s, 0)
478
479        txmss = s.getsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG)
480        ksft_eq(mss, txmss + 40)
481    finally:
482        _close_psp_conn(cfg, s)
483
484
485def data_stale_key(cfg):
486    """ Test send on a double-rotated key """
487    _init_psp_dev(cfg)
488
489    prev_stale = _get_stat(cfg, 'stale-events')
490    s = _make_psp_conn(cfg)
491    try:
492        rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
493                                     "dev-id": cfg.psp_dev_id,
494                                     "sock-fd": s.fileno()})
495        rx = rx_assoc['rx-key']
496        tx = _spi_xchg(s, rx)
497
498        cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
499                          "version": 0,
500                          "tx-key": tx,
501                          "sock-fd": s.fileno()})
502
503        data_len = _send_careful(cfg, s, 100)
504        _check_data_rx(cfg, data_len)
505        _check_data_outq(s, 0)
506
507        cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
508        cfg.pspnl.key_rotate({"id": cfg.psp_dev_id})
509
510        cur_stale = _get_stat(cfg, 'stale-events')
511        ksft_gt(cur_stale, prev_stale)
512
513        s.send(b'0123456789' * 200)
514        _check_data_outq(s, 2000, force_wait=True)
515    finally:
516        _close_psp_conn(cfg, s)
517
518
519def __nsim_psp_rereg(cfg):
520    # The PSP dev ID will change, remember what was there before
521    before = set([x['id'] for x in cfg.pspnl.dev_get({}, dump=True)])
522
523    cfg._ns.nsims[0].dfs_write('psp_rereg', '1')
524
525    after = set([x['id'] for x in cfg.pspnl.dev_get({}, dump=True)])
526
527    new_devs = list(after - before)
528    ksft_eq(len(new_devs), 1)
529    cfg.psp_dev_id = list(after - before)[0]
530
531
532def removal_device_rx(cfg):
533    """ Test removing a netdev / PSD with active Rx assoc """
534
535    # We could technically devlink reload real devices, too
536    # but that kills the control socket. So test this on
537    # netdevsim only for now
538    cfg.require_nsim()
539
540    s = _make_clr_conn(cfg)
541    try:
542        rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
543                                       "dev-id": cfg.psp_dev_id,
544                                       "sock-fd": s.fileno()})
545        ksft_not_none(rx_assoc)
546
547        __nsim_psp_rereg(cfg)
548    finally:
549        _close_conn(cfg, s)
550
551
552def removal_device_bi(cfg):
553    """ Test removing a netdev / PSD with active Rx/Tx assoc """
554
555    # We could technically devlink reload real devices, too
556    # but that kills the control socket. So test this on
557    # netdevsim only for now
558    cfg.require_nsim()
559
560    s = _make_clr_conn(cfg)
561    try:
562        rx_assoc = cfg.pspnl.rx_assoc({"version": 0,
563                                       "dev-id": cfg.psp_dev_id,
564                                       "sock-fd": s.fileno()})
565        cfg.pspnl.tx_assoc({"dev-id": cfg.psp_dev_id,
566                            "version": 0,
567                            "tx-key": rx_assoc['rx-key'],
568                            "sock-fd": s.fileno()})
569        __nsim_psp_rereg(cfg)
570    finally:
571        _close_conn(cfg, s)
572
573
574def psp_ip_ver_test_builder(name, test_func, psp_ver, ipver):
575    """Build test cases for each combo of PSP version and IP version"""
576    def test_case(cfg):
577        cfg.require_ipver(ipver)
578        test_func(cfg, psp_ver, ipver)
579
580    test_case.__name__ = f"{name}_v{psp_ver}_ip{ipver}"
581    return test_case
582
583
584def ipver_test_builder(name, test_func, ipver):
585    """Build test cases for each IP version"""
586    def test_case(cfg):
587        cfg.require_ipver(ipver)
588        test_func(cfg, ipver)
589
590    test_case.__name__ = f"{name}_ip{ipver}"
591    return test_case
592
593
594def main() -> None:
595    """ Ksft boiler plate main """
596
597    with NetDrvEpEnv(__file__) as cfg:
598        cfg.pspnl = PSPFamily()
599
600        # Set up responder and communication sock
601        responder = cfg.remote.deploy("psp_responder")
602
603        cfg.comm_port = rand_port()
604        srv = None
605        try:
606            with bkg(responder + f" -p {cfg.comm_port} -i {cfg.remote_ifindex}",
607                     host=cfg.remote, exit_wait=True) as srv:
608                wait_port_listen(cfg.comm_port, host=cfg.remote)
609
610                cfg.comm_sock = socket.create_connection((cfg.remote_addr,
611                                                          cfg.comm_port),
612                                                         timeout=1)
613
614                cases = [
615                    psp_ip_ver_test_builder(
616                        "data_basic_send", _data_basic_send, version, ipver
617                    )
618                    for version in range(0, 4)
619                    for ipver in ("4", "6")
620                ]
621                cases += [
622                    ipver_test_builder("data_mss_adjust", _data_mss_adjust, ipver)
623                    for ipver in ("4", "6")
624                ]
625
626                ksft_run(cases=cases, globs=globals(),
627                         case_pfx={"dev_", "data_", "assoc_", "removal_"},
628                         args=(cfg, ))
629
630                cfg.comm_sock.send(b"exit\0")
631                cfg.comm_sock.close()
632        finally:
633            if srv and (srv.stdout or srv.stderr):
634                ksft_pr("")
635                ksft_pr(f"Responder logs ({srv.ret}):")
636            if srv and srv.stdout:
637                ksft_pr("STDOUT:\n#  " + srv.stdout.strip().replace("\n", "\n#  "))
638            if srv and srv.stderr:
639                ksft_pr("STDERR:\n#  " + srv.stderr.strip().replace("\n", "\n#  "))
640    ksft_exit()
641
642
643if __name__ == "__main__":
644    main()
645