|
#!/usr/bin/env python3
|
|
"""
|
|
python_dns_fork_test.py - Test for macOS Tahoe DNS fork bug
|
|
|
|
This script exercises the exact same conditions as the Ruby reproduction:
|
|
1. Parent process resolves DNS for an IPv4-only host (poisoning the child)
|
|
2. Fork a child process
|
|
3. Child attempts DNS resolution
|
|
|
|
On macOS Tahoe with Ruby, this causes a hang/crash.
|
|
On Python, this should work fine (demonstrating the bug is Ruby-specific).
|
|
"""
|
|
|
|
import socket
|
|
import os
|
|
import sys
|
|
import signal
|
|
|
|
TEST_HOST = "api.segment.io" # IPv4-only host
|
|
TIMEOUT_SECONDS = 90
|
|
|
|
print(f"Python version: {sys.version}")
|
|
print(f"Platform: {sys.platform}")
|
|
print()
|
|
|
|
# Parent does DNS resolution first (this "poisons" the child in Ruby)
|
|
print(f"Parent: Resolving {TEST_HOST}...")
|
|
socket.getaddrinfo(TEST_HOST, 443, socket.AF_UNSPEC, socket.SOCK_STREAM)
|
|
print("Parent: Done")
|
|
|
|
# Fork and try DNS in child
|
|
print("Forking...")
|
|
pid = os.fork()
|
|
|
|
if pid == 0:
|
|
# Child process
|
|
print(f"Child ({os.getpid()}): Attempting DNS resolution...")
|
|
|
|
# Set up alarm for timeout
|
|
def timeout_handler(signum, frame):
|
|
print("Child: FAILED - DNS resolution hung (bug present)")
|
|
sys.exit(1)
|
|
|
|
signal.signal(signal.SIGALRM, timeout_handler)
|
|
signal.alarm(TIMEOUT_SECONDS)
|
|
|
|
try:
|
|
socket.getaddrinfo(TEST_HOST, 443, socket.AF_UNSPEC, socket.SOCK_STREAM)
|
|
signal.alarm(0) # Cancel the alarm
|
|
print("Child: SUCCESS - DNS resolution completed!")
|
|
sys.exit(0)
|
|
except Exception as e:
|
|
signal.alarm(0)
|
|
print(f"Child: ERROR - {e}")
|
|
sys.exit(1)
|
|
else:
|
|
# Parent process
|
|
_, status = os.waitpid(pid, 0)
|
|
exit_status = os.WEXITSTATUS(status)
|
|
|
|
print()
|
|
if exit_status == 0:
|
|
print("✅ Python does NOT have the DNS fork bug")
|
|
else:
|
|
print("❌ Python HAS the DNS fork bug")
|
|
|