namespy 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
namespy/__init__.py ADDED
@@ -0,0 +1,176 @@
1
+ """Namespy: an async DNS resolver, message codec, and inspector.
2
+
3
+ Two things live here that every other module reaches for: the logging posture,
4
+ and the socket options used to keep UDP datagrams from being fragmented.
5
+
6
+ **Logging.** A library must not configure logging for its host application, and
7
+ must not be silent either; the usual convention is to attach a
8
+ :class:`logging.NullHandler` to the top-level logger and let the application
9
+ decide. Previously each protocol object built its own ``logging.Logger(...)``
10
+ and attached a shared module-level :data:`console` handler to it, so importing
11
+ Namespy and constructing a resolver caused output no matter what the application
12
+ had configured, and every extra object re-attached the same handler until each
13
+ line printed twice. ``console`` is still exported for anyone who was using it,
14
+ but it is no longer attached to anything.
15
+
16
+ **Path MTU discovery.** ``IP_MTU_DISCOVER``/``IP_PMTUDISC_DO`` are Linux
17
+ ``IP``-level socket options (numbers 10 and 2). Setting them hard from
18
+ ``connection_made`` raised :exc:`OSError` on any other platform -- on Windows
19
+ the option does not exist -- which means a resolver or server that worked on the
20
+ developer's box crashed at the first callback elsewhere. The values now come
21
+ from :mod:`socket` where available, are ``None`` where not, and callers gate on
22
+ :data:`PMTUD_SUPPORTED`.
23
+
24
+ This matters for DNS specifically because a response that exceeds the path MTU
25
+ gets fragmented, and fragment reassembly failure looks identical to packet loss
26
+ -- which is how an EDNS(0)-sized answer (RFC 6891 section 6.2.5) turns into a
27
+ timeout. Asking the kernel not to fragment makes the failure visible instead.
28
+ """
29
+
30
+ import logging
31
+ import socket
32
+
33
+ __author__ = "b1tninja"
34
+ __version__ = "0.1.0"
35
+
36
+ #: ``IP_MTU`` socket option: query the path MTU of a connected socket.
37
+ IP_MTU = getattr(socket, "IP_MTU", 14)
38
+
39
+ #: ``IP_MTU_DISCOVER`` socket option -- Linux only (``None`` elsewhere).
40
+ IP_MTU_DISCOVER = getattr(socket, "IP_MTU_DISCOVER", None)
41
+
42
+ #: ``IP_PMTUDISC_DO``: do PMTU discovery and refuse to fragment.
43
+ IP_PMTUDISC_DO = getattr(socket, "IP_PMTUDISC_DO", None)
44
+
45
+ #: Whether this platform understands the PMTUD option pair at all.
46
+ PMTUD_SUPPORTED = IP_MTU_DISCOVER is not None and IP_PMTUDISC_DO is not None
47
+
48
+
49
+ def enable_path_mtu_discovery(transport, log=None) -> bool:
50
+ """Ask the kernel not to fragment this socket's datagrams.
51
+
52
+ Returns whether the option was actually set. Both the resolver and the
53
+ server call this from ``connection_made``, and neither may treat failure as
54
+ fatal: the option is Linux-specific, the socket may be IPv6 (where the
55
+ equivalent is ``IPV6_MTU_DISCOVER``, a different number), and a restrictive
56
+ container may refuse the call altogether. An earlier version called
57
+ :meth:`~socket.socket.setsockopt` unguarded, which meant a datagram
58
+ transport could not even open on Windows.
59
+
60
+ :param transport: the asyncio transport whose socket should be adjusted.
61
+ :param log: optional logger for a debug-level note about what happened.
62
+ """
63
+ sock = transport.get_extra_info("socket")
64
+ if sock is None:
65
+ return False
66
+ if not PMTUD_SUPPORTED:
67
+ if log is not None:
68
+ log.debug("pmtud_unavailable(option=IP_MTU_DISCOVER platform_supported=False)")
69
+ return False
70
+ try:
71
+ sock.setsockopt(socket.IPPROTO_IP, IP_MTU_DISCOVER, IP_PMTUDISC_DO)
72
+ except OSError as exc:
73
+ if log is not None:
74
+ log.debug("pmtud_failed(err=%s)", exc)
75
+ return False
76
+ return True
77
+
78
+
79
+ #: A ready-made stderr handler, for scripts that want Namespy's messages without
80
+ #: configuring :mod:`logging`. Attaching handlers is the application's job.
81
+ console = logging.StreamHandler()
82
+ console.setLevel(logging.DEBUG)
83
+ console.setFormatter(logging.Formatter("%(name)-12s: %(levelname)-8s %(message)s"))
84
+
85
+ # Library logging posture: silence unless the application opts in.
86
+ logging.getLogger(__name__).addHandler(logging.NullHandler())
87
+
88
+ # Re-exports, deliberately at the bottom.
89
+ #
90
+ # resolver.py and server.py both say ``from . import enable_path_mtu_discovery``,
91
+ # so the package body has to finish defining that function before either
92
+ # submodule runs -- and importing a submodule here executes it immediately. Moved
93
+ # these lines above the definition and every import in the package raises
94
+ # ImportError on a half-built module object, which is a confusing failure to
95
+ # diagnose because it names neither of the two files that caused it.
96
+ #
97
+ # They exist because ``from namespy.resolver import RecursiveResolver`` is not a
98
+ # reasonable thing to make a caller type, and because the previous ``__all__``
99
+ # advertised socket option constants as the package's public surface while the
100
+ # classes people actually want were only reachable through module paths.
101
+ from .cache import ( # noqa: E402
102
+ DnsCache,
103
+ FailureCache,
104
+ FailureEntry,
105
+ MemoryDnsCache,
106
+ NegativeEntry,
107
+ PositiveEntry,
108
+ SqliteDnsCache,
109
+ )
110
+ from .edns import EdnsOption, ExtendedDNSError # noqa: E402
111
+ from .enums import ( # noqa: E402
112
+ DnsOpCode,
113
+ DnsQClass,
114
+ DnsQR,
115
+ DnsQType,
116
+ DnsRClass,
117
+ DnsResponseCode,
118
+ DnsRType,
119
+ )
120
+ from .names import DomainName # noqa: E402
121
+ from .packet import ( # noqa: E402
122
+ DnsPacket,
123
+ DnsPacketParseError,
124
+ DnsQuestion,
125
+ DnsRecord,
126
+ Query,
127
+ Response,
128
+ )
129
+ from .resolver import GluePolicy, NxdomainPolicy, RecursiveResolver, Resolver # noqa: E402
130
+ from .trace import Trace # noqa: E402
131
+ from .wire import DnsNameParseError # noqa: E402
132
+
133
+ __all__ = [
134
+ # Socket-level helpers, kept exported because server.py and resolver.py
135
+ # share them and a caller building their own transport needs the guard.
136
+ "IP_MTU",
137
+ "IP_MTU_DISCOVER",
138
+ "IP_PMTUDISC_DO",
139
+ "PMTUD_SUPPORTED",
140
+ # The API surface: names a caller should not have to know a module path for.
141
+ "DnsCache",
142
+ "DnsNameParseError",
143
+ "DnsOpCode",
144
+ "DnsPacket",
145
+ "DnsPacketParseError",
146
+ "DnsQClass",
147
+ "DnsQR",
148
+ "DnsQType",
149
+ "DnsQuestion",
150
+ "DnsRClass",
151
+ "DnsRType",
152
+ "DnsRecord",
153
+ "DnsResponseCode",
154
+ "DomainName",
155
+ "EdnsOption",
156
+ "ExtendedDNSError",
157
+ "FailureCache",
158
+ "FailureEntry",
159
+ "GluePolicy",
160
+ "MemoryDnsCache",
161
+ "NegativeEntry",
162
+ "NxdomainPolicy",
163
+ "PositiveEntry",
164
+ "Query",
165
+ "RecursiveResolver",
166
+ "Resolver",
167
+ "Response",
168
+ "SqliteDnsCache",
169
+ "Trace",
170
+ # Trailing because ruff's RUF022 sorts dunders last within this group; the
171
+ # grouping in this list is by case convention, not by meaning.
172
+ "__author__",
173
+ "__version__",
174
+ "console",
175
+ "enable_path_mtu_discovery",
176
+ ]