wormhole-proxy 3.4.0__tar.gz → 3.4.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wormhole-proxy
3
- Version: 3.4.0
3
+ Version: 3.4.2
4
4
  Summary: Asynchronous I/O HTTP and HTTPS Proxy on Python >= 3.11
5
5
  License: MIT
6
6
  Keywords: wormhole,asynchronous,web,proxy
@@ -1,7 +1,7 @@
1
1
  # Main project metadata (PEP 621 standard)
2
2
  [project]
3
3
  name = "wormhole-proxy"
4
- version = "3.4.0"
4
+ version = "3.4.2"
5
5
  description = "Asynchronous I/O HTTP and HTTPS Proxy on Python >= 3.11"
6
6
  readme = "README.md"
7
7
  authors = [
@@ -77,4 +77,4 @@ filterwarnings = [
77
77
  # Build system configuration
78
78
  [build-system]
79
79
  requires = ["poetry-core>=1.0.0"]
80
- build-backend = "poetry.core.masonry.api"
80
+ build-backend = "poetry.core.masonry.api"
@@ -9,8 +9,9 @@ import random
9
9
  import time
10
10
 
11
11
  # --- DNS Cache for Performance ---
12
- DNS_CACHE: dict[str, tuple[list[str], float]] = {}
13
- DNS_CACHE_TTL: int = 300 # Cache DNS results for 5 minutes
12
+ DNS_CACHE: dict[str, tuple[list[str], float, float]] = (
13
+ {}
14
+ ) # (ip_list, timestamp, ttl_expiration)
14
15
 
15
16
 
16
17
  # --- Modernized Relay Stream Function ---
@@ -96,8 +97,8 @@ async def _resolve_and_validate_host(
96
97
 
97
98
  # Check cache first
98
99
  if host in DNS_CACHE:
99
- ip_list, timestamp = DNS_CACHE[host]
100
- if time.time() - timestamp < DNS_CACHE_TTL:
100
+ ip_list, timestamp, ttl_expiration = DNS_CACHE[host]
101
+ if time.time() < ttl_expiration:
101
102
  logger.debug(
102
103
  flm(
103
104
  f"DNS cache hit for '{host}'. ({len(DNS_CACHE)} hosts cached)",
@@ -107,9 +108,9 @@ async def _resolve_and_validate_host(
107
108
  )
108
109
  return ip_list
109
110
 
110
- # Resolve hostname using aiodns resolver
111
+ # Resolve hostname using aiodns resolver with TTL information
111
112
  try:
112
- resolved_ips = await resolver.resolve(host)
113
+ resolved_ips, min_ttl = await resolver.resolve_with_ttl(host)
113
114
  except OSError as e:
114
115
  raise OSError(f"Failed to resolve host: {host}") from e
115
116
 
@@ -154,13 +155,14 @@ async def _resolve_and_validate_host(
154
155
  f"Blocked access to '{host}' as it resolved to only private/reserved IPs."
155
156
  )
156
157
 
157
- # Update cache
158
- DNS_CACHE[host] = (final_ip_list, time.time())
158
+ # Update cache with TTL-based expiration
159
+ ttl_expiration = time.time() + min_ttl
160
+ DNS_CACHE[host] = (final_ip_list, time.time(), ttl_expiration)
159
161
  logger.debug(
160
162
  flm(
161
163
  (
162
164
  f"DNS cache miss for '{host}'. "
163
- f"Resolved to {final_ip_list}. Caching. "
165
+ f"Resolved to {final_ip_list} with TTL {min_ttl}s. Caching until {ttl_expiration}. "
164
166
  f"({len(DNS_CACHE)} hosts cached)"
165
167
  ),
166
168
  context.ident,
@@ -82,8 +82,13 @@ class LogThrottler:
82
82
  if self.timer:
83
83
  self.timer.cancel()
84
84
 
85
- loop = asyncio.get_running_loop()
86
- self.timer = loop.call_later(self.delay, self._flush_summary)
85
+ try:
86
+ loop = asyncio.get_running_loop()
87
+ self.timer = loop.call_later(self.delay, self._flush_summary)
88
+ except RuntimeError:
89
+ # If there's no running event loop, log the message directly
90
+ # This can happen in synchronous test cases
91
+ self._flush_summary()
87
92
 
88
93
 
89
94
  # In loguru, the logger is imported and ready to be configured.
@@ -281,8 +281,13 @@ def main() -> int:
281
281
  # Determine if we are running the full async server or a utility.
282
282
  is_server_mode = not args.update_ad_block_db
283
283
 
284
- # Setup the uvloop before any other operations that might use the event loop.
285
- if uvloop:
284
+ # Use the new uvloop.run() approach for Python 3.12+
285
+ # For older versions or if uvloop is not available, we'll use the standard approach
286
+ if uvloop and hasattr(uvloop, "run"):
287
+ # For Python 3.12+, we'll use uvloop.run() directly when starting the server
288
+ pass # We'll handle this in the asyncio.run() call
289
+ elif uvloop:
290
+ # For older versions, use the deprecated install() method
286
291
  uvloop.install()
287
292
 
288
293
  # Setup logging. Disable async features for synchronous utility commands.
@@ -297,9 +302,16 @@ def main() -> int:
297
302
  # For this standalone utility, configure a simple logger to show progress.
298
303
  logger.info(f"Updating ad-block database at: {args.update_ad_block_db}")
299
304
  try:
300
- asyncio.run(
301
- update_database(args.update_ad_block_db, args.allowlist)
302
- )
305
+ if uvloop and hasattr(uvloop, "run"):
306
+ # Use the new uvloop.run() method for Python 3.12+
307
+ uvloop.run(
308
+ update_database(args.update_ad_block_db, args.allowlist)
309
+ )
310
+ else:
311
+ # Use the standard asyncio.run() for older versions or if uvloop is not available
312
+ asyncio.run(
313
+ update_database(args.update_ad_block_db, args.allowlist)
314
+ )
303
315
  except Exception as e:
304
316
  logger.error(f"\nAn error occurred during update: {e}")
305
317
  return 1
@@ -313,7 +325,12 @@ def main() -> int:
313
325
  parser.error(f"Authentication file not found: {args.auth}")
314
326
 
315
327
  try:
316
- asyncio.run(main_async(args))
328
+ if uvloop and hasattr(uvloop, "run"):
329
+ # Use the new uvloop.run() method for Python 3.12+
330
+ uvloop.run(main_async(args))
331
+ else:
332
+ # Use the standard asyncio.run() for older versions or if uvloop is not available
333
+ asyncio.run(main_async(args))
317
334
  except KeyboardInterrupt:
318
335
  print("\nInterrupted by user. Exiting.")
319
336
  except Exception as e:
@@ -137,18 +137,18 @@ class Resolver:
137
137
  )
138
138
  )
139
139
 
140
- async def resolve(self, hostname: str) -> list[str]:
140
+ async def resolve_with_ttl(self, hostname: str) -> tuple[list[str], int]:
141
141
  """
142
- Resolves a hostname to a list of IP addresses.
142
+ Resolves a hostname to a list of IP addresses and the minimum TTL.
143
143
 
144
144
  1. Checks the local hosts file cache first.
145
- 2. If not found, queries DNS for A and AAAA records using aiodns.
145
+ 2. If not found, queries DNS for A and AAAA records using aiodns and extracts TTL.
146
146
 
147
147
  Args:
148
148
  hostname (str): The hostname to resolve.
149
149
 
150
150
  Returns:
151
- list[str]: A list of IP addresses associated with the hostname.
151
+ tuple[list[str], int]: A list of IP addresses and the minimum TTL value.
152
152
 
153
153
  Raises:
154
154
  OSError: If no IP addresses could be resolved.
@@ -171,7 +171,8 @@ class Resolver:
171
171
  self.verbose,
172
172
  )
173
173
  )
174
- return [ip]
174
+ # Hosts file entries don't have TTL, use a default high value
175
+ return [ip], 3600
175
176
 
176
177
  # 2. Query DNS using aiodns for IPv4 and IPv6 addresses concurrently
177
178
  results = await asyncio.gather(
@@ -181,6 +182,8 @@ class Resolver:
181
182
  )
182
183
 
183
184
  resolved_ips: set[str] = set()
185
+ min_ttl = float("inf")
186
+
184
187
  for res in results:
185
188
  if isinstance(res, list):
186
189
  for record in res:
@@ -188,6 +191,9 @@ class Resolver:
188
191
  resolved_ips.add(record.host.decode())
189
192
  else:
190
193
  resolved_ips.add(record.host)
194
+ # Extract TTL from record
195
+ if hasattr(record, "ttl") and record.ttl < min_ttl:
196
+ min_ttl = record.ttl
191
197
  elif isinstance(res, aiodns.error.DNSError):
192
198
  # Ignore common "not found" errors. Log other DNS errors for debugging.
193
199
  if res.args[0] not in (
@@ -209,7 +215,30 @@ class Resolver:
209
215
  if not resolved_ips:
210
216
  raise OSError(f"Failed to resolve host: {hostname}")
211
217
 
212
- return list(resolved_ips)
218
+ # If no TTL was found (shouldn't happen but just in case), use default
219
+ if min_ttl == float("inf"):
220
+ min_ttl = 300
221
+
222
+ return list(resolved_ips), int(min_ttl)
223
+
224
+ async def resolve(self, hostname: str) -> list[str]:
225
+ """
226
+ Resolves a hostname to a list of IP addresses.
227
+
228
+ 1. Checks the local hosts file cache first.
229
+ 2. If not found, queries DNS for A and AAAA records using aiodns.
230
+
231
+ Args:
232
+ hostname (str): The hostname to resolve.
233
+
234
+ Returns:
235
+ list[str]: A list of IP addresses associated with the hostname.
236
+
237
+ Raises:
238
+ OSError: If no IP addresses could be resolved.
239
+ """
240
+ ips, _ = await self.resolve_with_ttl(hostname)
241
+ return ips
213
242
 
214
243
 
215
244
  # Initialize the singleton instance so it's ready for other modules to import
File without changes
File without changes