From 87dae59775d56c2ea908e5b4b2041ee4e1d1c436 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Fri, 9 Oct 2026 18:47:37 -0400 Subject: [PATCH] fix(registry): unregistering a custom protocol removes its name from __proto__ (#1505) - The nine code registrars (register_linktype, register_pcap, register_pcapng, register_ethertype, register_transtype, register_apptype, register_tcp, register_udp, register_sctp) end in a new _register_name: register_protocol, plus a lease on the by-name entry when the write changed it, recording what it displaced and which (destination, code) pairs still hold it. - Leases stack per name, one per write, so a class can hold two (a built-in retaking its name from a custom shadow). Releasing the top one restores what it displaced; releasing a buried one hands that to the lease above. A name held before the registration is never leased, so no restore removes it. - The lease tables are guarded by a module RLock. - Delete the Gap(1505) rows; add test_registry_name_restore_unit.py, with a seeded fuzz checked after every call. --- pcapkit/foundation/registry/protocols.py | 184 +++++++++++- .../test_registry_name_restore_unit.py | 280 ++++++++++++++++++ .../registry/test_registry_symmetry_unit.py | 9 - 3 files changed, 453 insertions(+), 20 deletions(-) create mode 100644 tests/foundation/registry/test_registry_name_restore_unit.py diff --git a/pcapkit/foundation/registry/protocols.py b/pcapkit/foundation/registry/protocols.py index f9b59ac297..f49aaf79f1 100644 --- a/pcapkit/foundation/registry/protocols.py +++ b/pcapkit/foundation/registry/protocols.py @@ -9,6 +9,7 @@ """ import collections.abc import enum +import threading from typing import TYPE_CHECKING, cast, overload import aenum @@ -169,8 +170,9 @@ def register_protocol(protocol: 'Type[ProtocolBase]') -> 'None': presence alone is not enough. This registry's key is *derived* from the class rather than supplied by a caller, and this function is the funnel every wrapper registrar calls -- :func:`register_tcp`, :func:`register_udp`, - :func:`register_apptype`, :func:`register_linktype` and the rest all end in - ``register_protocol(module)``. So registering one class under two codes, a + :func:`register_apptype`, :func:`register_linktype` and the rest all end by + calling it with their class, and registering their code back undoes that + call's write (:issue:`1505`). So registering one class under two codes, a supported and documented thing to do, reaches this function twice with the same class and nothing at stake; a presence-only guard would warn about an overwrite that overwrote nothing. Warning on the harmless case is not free: @@ -236,6 +238,164 @@ class rather than supplied by a caller, and this function is the funnel logger.debug('registered protocol: %s', protocol.__name__) +#: Sentinel for a name :data:`pcapkit.protocols.__proto__` did not hold. +_MISSING = object() + + +class _NameLease: + """A :data:`pcapkit.protocols.__proto__` entry a code registration put there. + + Each code registrar below ends by naming its class in the by-name registry, + and :mod:`pcapkit.foundation.registry` has no ``unregister``: a code is + undone by registering the entry it displaced (:issue:`1363`). Without a + record of that by-name write, handing a code back restored the code and left + the name behind (:issue:`1505`). A lease records what the write displaced and + which codes still dispatch to the class, so the last of them to be handed + back puts the displaced entry back, or removes the name if it was new. + + A lease is one write, not one class: the same class can hold two leases on + a name -- e.g. a built-in registered at one port over a custom class + shadowing it at another -- and each undoes only its own write. + + """ + + __slots__ = ('name', 'protocol', 'displaced', 'codes') + + def __init__(self, name: 'str', protocol: 'Type[ProtocolBase]', displaced: 'Any') -> 'None': + #: The key written in :data:`pcapkit.protocols.__proto__`. + self.name = name + #: The class written under it. + self.protocol = protocol + #: The entry it displaced, or :data:`_MISSING` for a new name. + self.displaced = displaced + #: ``(destination, code)`` registrations still holding the lease. + self.codes = set() # type: set[tuple[type, Any]] + + +#: Name -> its leases, oldest first. Each lease displaced the one below it, +#: unless the name was written by other means in between; the last is the one +#: a lookup sees, unless the name has been written since. +_NAME_LEASES = {} # type: dict[str, list[_NameLease]] +#: ``(destination, code)`` -> the lease its registration holds. +_CODE_LEASES = {} # type: dict[tuple[type, Any], _NameLease] +#: Serialises the two tables above, so concurrent registrars cannot tear them. +_LEASE_LOCK = threading.RLock() + + +def _fold_lease(stack: 'list[_NameLease]', index: 'int') -> 'None': + """Retire ``stack[index]``, a lease now displacing its own class. + + That happens once the lease between it and an earlier write of the same + class is handed back, e.g. a custom ``IPv4`` released from under the + built-in ``IPv4`` registered over it. Undoing it would change nothing, so + its codes move to the lease below when that is the same class, and + otherwise -- the class was named by other means -- hold nothing. + + Args: + stack: The name's leases, oldest first. + index: Position of the lease to retire. + + """ + lease = stack.pop(index) + below = stack[index - 1] if index > 0 else None + if below is not None and below.protocol is not lease.protocol: + below = None + for target in lease.codes: + if _CODE_LEASES.get(target) is not lease: + continue + if below is None: + del _CODE_LEASES[target] + else: + below.codes.add(target) + _CODE_LEASES[target] = below + + +def _release_name(target: 'tuple[type, Any]') -> 'None': + """Hand back the lease ``target``'s registration held. + + Once no code holds the lease, it leaves its name's stack. On top, it puts + the name back to the entry it displaced, or removes a new name -- unless the + name was written by other means since, in which case that write stands. + Buried, it hands what it displaced to the lease above, if that lease + displaced it. + + Args: + target: The ``(destination, code)`` being registered over. + + """ + lease = _CODE_LEASES.pop(target, None) + if lease is None: + return + lease.codes.discard(target) + if lease.codes: + return + + stack = _NAME_LEASES.get(lease.name, []) + index = next((i for i, item in enumerate(stack) if item is lease), None) + if index is None: + return + del stack[index] + if index < len(stack): + above = stack[index] + if above.displaced is lease.protocol: + above.displaced = lease.displaced + if above.displaced is above.protocol: + _fold_lease(stack, index) + elif protocol_registry.get(lease.name) is lease.protocol: + if lease.displaced is _MISSING: + protocol_registry.pop(lease.name, None) + else: + protocol_registry[lease.name] = lease.displaced + logger.debug('released protocol: %s', lease.protocol.__name__) + if not stack: + _NAME_LEASES.pop(lease.name, None) + + +def _register_name(protocol: 'Type[ProtocolBase]', *targets: 'tuple[type, Any]') -> 'None': + """Name ``protocol`` in :data:`pcapkit.protocols.__proto__` for ``targets``. + + The tail of every code registrar: :func:`register_protocol`, preceded by + releasing whatever each target code held before, and followed by a lease + when the write changed the name, so that registering the code back undoes + it (:issue:`1505`). A name already showing ``protocol`` -- shipped, defined + by a :class:`~pcapkit.protocols.protocol.Protocol` subclass, or registered + directly -- is not leased, so no restore takes it away; one showing it + through a lease extends that lease to ``targets``. + + Args: + protocol: Protocol class just registered -- and so validated -- at ``targets``. + *targets: The ``(destination, code)`` pairs it was registered at. + + """ + name = protocol.__name__.upper() + with _LEASE_LOCK: + stack = _NAME_LEASES.get(name) + if stack and stack[-1].protocol is protocol and protocol_registry.get(name) is protocol: + current = stack[-1] # type: Optional[_NameLease] + else: + current = None + for target in targets: + if current is None or _CODE_LEASES.get(target) is not current: + _release_name(target) + + incumbent = protocol_registry.get(name, _MISSING) + register_protocol(protocol) + if protocol_registry.get(name) is not protocol: + return + + stack = _NAME_LEASES.get(name) + if incumbent is not protocol: + lease = _NameLease(name, protocol, incumbent) + _NAME_LEASES.setdefault(name, []).append(lease) + elif stack and stack[-1].protocol is protocol: + lease = stack[-1] + else: + return + lease.codes.update(targets) + for target in targets: + _CODE_LEASES[target] = lease + + #: Enum type -> the class(es) owning the :attr:`ProtocolBase.__proto__ #: ` dispatch registry #: keyed by that enum type -- the "registry-of-registries" that lets @@ -413,7 +573,7 @@ def register_linktype(code: 'LinkType', module: 'str | ModuleDescriptor[Protocol # register protocol to protocol registry if isinstance(module, ModuleDescriptor): module = module.klass - register_protocol(module) + _register_name(module, (Frame, code), (PCAPNG, code)) @overload @@ -450,7 +610,7 @@ def register_pcap(code: 'LinkType', module: 'str | ModuleDescriptor[ProtocolBase # register protocol to protocol registry if isinstance(module, ModuleDescriptor): module = module.klass - register_protocol(module) + _register_name(module, (Frame, code)) @overload @@ -487,7 +647,7 @@ def register_pcapng(code: 'LinkType', module: 'str | ModuleDescriptor[ProtocolBa # register protocol to protocol registry if isinstance(module, ModuleDescriptor): module = module.klass - register_protocol(module) + _register_name(module, (PCAPNG, code)) ############################################################################### @@ -529,7 +689,7 @@ def register_ethertype(code: 'EtherType', module: 'str | ModuleDescriptor[Protoc # register protocol to protocol registry if isinstance(module, ModuleDescriptor): module = module.klass - register_protocol(module) + _register_name(module, (Link, code)) ############################################################################### @@ -571,7 +731,7 @@ def register_transtype(code: 'TransType', module: 'str | ModuleDescriptor[Protoc # register protocol to protocol registry if isinstance(module, ModuleDescriptor): module = module.klass - register_protocol(module) + _register_name(module, (Internet, code)) # NOTE: pcapkit.protocols.internet.ipv4.IPv4.__option__ @@ -942,6 +1102,7 @@ def register_apptype(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor TransportProtocol.tcp: TCP, TransportProtocol.udp: UDP, }) + targets = [] # type: list[tuple[type, Any]] for proto in transport: # NOTE: a composite is not a key here, so ``tcp | udp`` is rejected # rather than quietly registering under both. Naming them one at a time @@ -951,12 +1112,13 @@ def register_apptype(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor raise RegistryError(f'unknown transport protocol: {proto.name}') cls.register(code, module) + targets.append((cls, code)) logger.debug('registered %s port: %s', proto.name, code) # register protocol to protocol registry if isinstance(module, ModuleDescriptor): module = module.klass - register_protocol(module) + _register_name(module, *targets) @overload @@ -995,7 +1157,7 @@ def register_tcp(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor[Pro # register protocol to protocol registry if isinstance(module, ModuleDescriptor): module = module.klass - register_protocol(module) + _register_name(module, (TCP, code)) # NOTE: pcapkit.protocols.transport.tcp.TCP.__option__ @@ -1084,7 +1246,7 @@ def register_udp(code: 'int | Enum_AppType', module: 'str | ModuleDescriptor[Pro # register protocol to protocol registry if isinstance(module, ModuleDescriptor): module = module.klass - register_protocol(module) + _register_name(module, (UDP, code)) @overload @@ -1128,7 +1290,7 @@ def register_sctp(code: 'int | SCTP_PayloadProtocolIdentifier', module: 'str | M # register protocol to protocol registry if isinstance(module, ModuleDescriptor): module = module.klass - register_protocol(module) + _register_name(module, (SCTP, code)) ############################################################################### diff --git a/tests/foundation/registry/test_registry_name_restore_unit.py b/tests/foundation/registry/test_registry_name_restore_unit.py new file mode 100644 index 0000000000..8c15e947f6 --- /dev/null +++ b/tests/foundation/registry/test_registry_name_restore_unit.py @@ -0,0 +1,280 @@ +# -*- coding: utf-8 -*- +"""Handing a code back takes back the by-name entry its registration made. C.f. #1505. + +Every code registrar in :mod:`pcapkit.foundation.registry.protocols` -- +``register_tcp``, ``register_linktype`` and the rest -- ends by naming the class +it registered in :data:`pcapkit.protocols.__proto__`. Registering the shipped +entry back restored the code but never the name: ``register_tcp(80, Custom)`` +then ``register_tcp(80, shipped)`` left ``'CUSTOM'`` behind for good, and a +custom class named like a built-in left the built-in displaced. + +:mod:`tests.foundation.registry.test_registry_symmetry_unit` covers the plain +round trip for all nine registrars. These pin the cases it does not reach: a +displaced built-in name, a class still held by a second code, a name that was +there before the registration, which a restore must not take away, and a +built-in retaking its name from a custom class shadowing it -- plus a seeded +fuzz over all of those, checked after every call. + +""" + +from __future__ import annotations + +import importlib.util +import unittest +import warnings +from typing import TYPE_CHECKING + +from tests._support import reimport_once_per_class + +if TYPE_CHECKING: + from typing import Any + +HAS_RUNTIME = all(importlib.util.find_spec(name) is not None + for name in ('tbtrim', 'aenum', 'chardet', 'dictdumper')) + + +@unittest.skipUnless(HAS_RUNTIME, 'runtime dependencies not installed') +class TestRegistryNameRestore(unittest.TestCase): + """Restoring a code leaves :data:`pcapkit.protocols.__proto__` as it found it.""" + + def setUp(self) -> None: + reimport_once_per_class(self) + + from pcapkit.protocols import __proto__ as names + from pcapkit.protocols.misc.pcap.frame import Frame + from pcapkit.protocols.misc.pcapng import PCAPNG + from pcapkit.protocols.transport.tcp import TCP + from pcapkit.protocols.transport.udp import UDP + + self.tables = (names, TCP.__proto__, UDP.__proto__, Frame.__proto__, PCAPNG.__proto__) + self.saved = [dict(table) for table in self.tables] + self.addCleanup(self._reset) + self.names = names + self.before = dict(names) + + def _reset(self) -> None: + for table, copy in zip(self.tables, self.saved): + table.clear() + table.update(copy) + + def _custom(self, name: 'str') -> 'Any': + from pcapkit.protocols.application.application import Application + + return type(name, (Application,), {'__module__': __name__}) + + def _call(self, register: 'Any', *args: 'Any') -> None: + with warnings.catch_warnings(): + warnings.simplefilter('ignore') + register(*args) + + def assert_names_as_found(self) -> None: + self.assertEqual(sorted(self.names), sorted(self.before)) + for name, entry in self.before.items(): + self.assertIs(self.names[name], entry, name) + + def test_restoring_a_code_removes_the_name_it_added(self) -> None: + """The issue's reproduction: the custom name goes when port 80 is restored.""" + from pcapkit.foundation.registry import register_tcp + from pcapkit.protocols.transport.tcp import TCP + + shipped = TCP.__proto__[80] + custom = self._custom('Custom') + + self.assertNotIn('CUSTOM', self.names) + self._call(register_tcp, 80, custom) + self.assertIs(self.names['CUSTOM'], custom) + self._call(register_tcp, 80, shipped) + self.assertNotIn('CUSTOM', self.names) + self.assert_names_as_found() + + def test_restoring_a_code_puts_back_the_name_it_displaced(self) -> None: + """A custom class named like a built-in hands the built-in its name back.""" + from pcapkit.foundation.registry import register_tcp + from pcapkit.protocols.internet.ipv4 import IPv4 + from pcapkit.protocols.transport.tcp import TCP + + shipped = TCP.__proto__[80] + custom = self._custom('IPv4') + + self._call(register_tcp, 80, custom) + self.assertIs(self.names['IPV4'], custom) + self._call(register_tcp, 80, shipped) + self.assertIs(self.names['IPV4'], IPv4) + self.assert_names_as_found() + + def test_a_name_stays_while_another_code_still_holds_it(self) -> None: + """The name goes with the last code dispatching to the class, not the first.""" + from pcapkit.foundation.registry import register_tcp, register_udp + from pcapkit.protocols.transport.tcp import TCP + from pcapkit.protocols.transport.udp import UDP + + tcp, udp = TCP.__proto__[80], UDP.__proto__[80] + custom = self._custom('Custom') + + self._call(register_tcp, 80, custom) + self._call(register_udp, 80, custom) + self._call(register_tcp, 80, tcp) + self.assertIs(UDP.__proto__[80], custom) + self.assertIs(self.names['CUSTOM'], custom) + self._call(register_udp, 80, udp) + self.assert_names_as_found() + + def test_linktype_holds_the_name_from_both_registries(self) -> None: + """``register_linktype`` writes Frame and PCAPNG, so both must be handed back.""" + from pcapkit.const.reg.linktype import LinkType + from pcapkit.foundation.registry import register_linktype, register_pcap, register_pcapng + from pcapkit.protocols.link.link import Link + from pcapkit.protocols.misc.pcap.frame import Frame + from pcapkit.protocols.misc.pcapng import PCAPNG + + frame, pcapng = Frame.__proto__[LinkType.ETHERNET], PCAPNG.__proto__[LinkType.ETHERNET] + custom = type('Custom', (Link,), {'__module__': __name__}) + + self._call(register_linktype, LinkType.ETHERNET, custom) + self._call(register_pcap, LinkType.ETHERNET, frame) + self.assertIs(self.names['CUSTOM'], custom) + self._call(register_pcapng, LinkType.ETHERNET, pcapng) + self.assert_names_as_found() + + def test_a_name_held_before_the_registration_is_kept(self) -> None: + """A class named before it took a code keeps its name once the code goes back.""" + from pcapkit.foundation.registry import register_protocol, register_tcp + from pcapkit.protocols.protocol import Protocol + from pcapkit.protocols.transport.tcp import TCP + + shipped = TCP.__proto__[80] + defined = type('Defined', (Protocol,), {'__module__': __name__}) # named by definition + explicit = self._custom('Explicit') + register_protocol(explicit) + self.before = dict(self.names) + self.assertIs(self.before['DEFINED'], defined) + + for custom in (defined, explicit): + with self.subTest(name=custom.__name__): + self._call(register_tcp, 80, custom) + self._call(register_tcp, 80, shipped) + self.assert_names_as_found() + + def test_re_registering_the_same_override_stays_quiet(self) -> None: + """A repeated override neither warns nor loses the name it displaced.""" + from pcapkit.foundation.registry import register_tcp + from pcapkit.protocols.application.http import HTTP + from pcapkit.protocols.transport.tcp import TCP + from pcapkit.utilities.warnings import RegistryWarning + + shipped = TCP.__proto__[80] + custom = self._custom('HTTP') + + self._call(register_tcp, 80, custom) + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter('always') + register_tcp(80, custom) + self.assertEqual([str(item.message) for item in caught + if issubclass(item.category, RegistryWarning)], []) + self.assertIs(self.names['HTTP'], custom) + self._call(register_tcp, 80, shipped) + self.assertIs(self.names['HTTP'], HTTP) + self.assert_names_as_found() + + def test_a_built_in_over_its_own_shadow_is_undone_in_either_order(self) -> None: + """Each restore undoes its own write, when a built-in retakes its name from a shadow. + + A custom ``IPv4`` at port 80 shadows the built-in. Registering the built-in + at port 8080 takes the name back. Restoring 8080 must hand the name back to + the shadow, which port 80 still dispatches to, and restoring 80 then hands + it to the built-in. Done in the other order, the built-in keeps the name + throughout. + + """ + from pcapkit.foundation.registry import register_tcp + from pcapkit.protocols.internet.ipv4 import IPv4 + from pcapkit.protocols.transport.tcp import TCP + + s80, s8080 = TCP.__proto__[80], TCP.__proto__[8080] + custom = self._custom('IPv4') + for order in ((8080, 80), (80, 8080)): + with self.subTest(restore=order): + self._reset() + self._call(register_tcp, 80, custom) + self._call(register_tcp, 8080, IPv4) + self.assertIs(self.names['IPV4'], IPv4) + self._call(register_tcp, order[0], {80: s80, 8080: s8080}[order[0]]) + self.assertIs(self.names['IPV4'], custom if order[0] == 8080 else IPv4) + self._call(register_tcp, order[1], {80: s80, 8080: s8080}[order[1]]) + self.assert_names_as_found() + + def test_any_sequence_restored_in_full_leaves_names_as_found(self) -> None: + """Seeded fuzz: register at random, restore every code, compare with the start. + + Custom classes, some named like each other and some like built-ins, and the + built-ins themselves (``IPv4`` and the three ``HTTP`` classes), go through + ``register_tcp``, ``register_udp`` and ``register_apptype`` at a handful of + shipped ports, with restores mixed in. Every code is then restored to its + shipped entry, in a shuffled order, and the names must be exactly as found. + + Each step is checked too: a name shows the class of the latest registration + still in place under it, or what it was found as once none is. + + """ + import random + + from pcapkit.foundation.registry import register_apptype, register_tcp, register_udp + from pcapkit.protocols.application import http, httpv1, httpv2 + from pcapkit.protocols.internet.ipv4 import IPv4 + from pcapkit.protocols.transport.tcp import TCP + from pcapkit.protocols.transport.udp import UDP + + registrars = {'tcp': register_tcp, 'udp': register_udp} + shipped = {('tcp', code): TCP.__proto__[code] for code in (21, 80, 8080)} + shipped.update({('udp', code): UDP.__proto__[code] for code in (80, 1701, 8080)}) + targets = sorted(shipped) + builtins = [IPv4, http.HTTP, httpv1.HTTP, httpv2.HTTP] + + def expected(held: 'dict[Any, tuple[int, Any]]', name: 'str') -> 'Any': + latest = max((entry for entry in held.values() if entry[1].__name__.upper() == name), + default=None, key=lambda entry: entry[0]) + return self.before.get(name) if latest is None else latest[1] + + rng = random.Random(1505) + failures = [] # type: list[tuple[list[tuple[Any, ...]], str]] + for _ in range(2000): + pool = [self._custom(name) for name in ('IPv4', 'HTTP', 'Custom', 'Custom')] + builtins + ops = [] # type: list[tuple[Any, ...]] + for _ in range(rng.randint(1, 8)): + draw, target = rng.random(), rng.choice(targets) + if draw < 0.55: + ops.append(('register', target, rng.randrange(len(pool)))) + elif draw < 0.65: + ops.append(('apptype', rng.choice((80, 8080)), rng.randrange(len(pool)))) + else: + ops.append(('restore', target)) + final = list(targets) + rng.shuffle(final) + ops.extend(('restore', target) for target in final) + + held = {target: (0, shipped[target].klass) for target in targets} + watched = set(self.before) | {cls.__name__.upper() for cls in pool} + for step, op in enumerate(ops, 1): + if op[0] == 'register': + self._call(registrars[op[1][0]], op[1][1], pool[op[2]]) + held[op[1]] = (step, pool[op[2]]) + elif op[0] == 'apptype': + self._call(register_apptype, op[1], pool[op[2]], 'tcp', 'udp') + held[('tcp', op[1])] = held[('udp', op[1])] = (step, pool[op[2]]) + else: + self._call(registrars[op[1][0]], op[1][1], shipped[op[1]]) + held[op[1]] = (step, shipped[op[1]].klass) + wrong = [f'step {step}, {name}: {self.names.get(name)!r}, expected ' + f'{expected(held, name)!r}' for name in sorted(watched | set(self.names)) + if self.names.get(name) is not expected(held, name)] + if wrong: + failures.append((ops[:step], wrong[0])) + break + self._reset() + + failures.sort(key=lambda failure: len(failure[0])) + self.assertEqual(failures[:1], [], f'{len(failures)} of 2000 sequences went wrong') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/foundation/registry/test_registry_symmetry_unit.py b/tests/foundation/registry/test_registry_symmetry_unit.py index d4b885daf2..a373f8f225 100644 --- a/tests/foundation/registry/test_registry_symmetry_unit.py +++ b/tests/foundation/registry/test_registry_symmetry_unit.py @@ -417,15 +417,6 @@ class TestRegistrySymmetry(harness.RoundTripBase): 'register_ethertype', 'register_transtype', 'register_tcp', 'register_udp', 'register_apptype', 'register_sctp') for variant in ('override', 'override-same-name', 'restore-by-name'))), - Gap(1505, 'a protocol registrar adds the class it registers to pcapkit.protocols.__proto__ ' - 'by name (pcapkit/foundation/registry/protocols.py:235, reached from e.g. :416), ' - 'and restoring the code leaves that name behind: nothing removes it', - 'CHANGED', "names['ROUNDTRIPCUSTOM'] added", - tuple(f'{name}/{variant}/names' - for name in ('register_linktype', 'register_pcap', 'register_pcapng', - 'register_ethertype', 'register_transtype', 'register_tcp', - 'register_udp', 'register_apptype', 'register_sctp') - for variant in ('override', 'restore-by-name'))), ) def setUp(self) -> None: