Skip to content

Commit 8a604f6

Browse files
committed
Add DNS testing setup using BIND 9
This creates a test frameword for testing PHP DNS functions that are currently completely untested. It provides script for starting BIND 9 DNS server that uses locally defined zone files. Those can be then used for creating specific test scenarios without a need to use online tests. As part of that, the systemd resolv script is provided to use BIND as a primary resolver. This all is then used in the CI.
1 parent 61884c3 commit 8a604f6

File tree

11 files changed

+214
-0
lines changed

11 files changed

+214
-0
lines changed

.circleci/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ jobs:
4242
ldap-utils \
4343
openssl \
4444
slapd \
45+
bind9 \
46+
bind9utils \
4547
libgmp-dev \
4648
libicu-dev \
4749
libtidy-dev \

.github/actions/setup-x64/action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ runs:
66
run: |
77
set -x
88
9+
sudo ./ext/standard/tests/dns/bind-start.sh
10+
sudo ./ext/standard/tests/dns/resolv-setup.sh
11+
912
sudo service slapd start
1013
docker exec sql1 /opt/mssql-tools18/bin/sqlcmd -S 127.0.0.1 -U SA -C -P "<YourStrong@Passw0rd>" -Q "create login pdo_test with password='password', check_policy=off; create user pdo_test for login pdo_test; grant alter, control to pdo_test;"
1114
docker exec sql1 /opt/mssql-tools18/bin/sqlcmd -S 127.0.0.1 -U SA -C -P "<YourStrong@Passw0rd>" -Q "create login odbc_test with password='password', check_policy=off; create user odbc_test for login odbc_test; grant alter, control, delete to odbc_test;"
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/bash
2+
3+
set -euo pipefail
4+
5+
# Resolve script location
6+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7+
ZONES_DIR="$SCRIPT_DIR/zones"
8+
NAMED_CONF="named.conf.local"
9+
PID_FILE="$ZONES_DIR/named.pid"
10+
LOG_FILE="$SCRIPT_DIR/named.log"
11+
12+
# Default mode: background
13+
FOREGROUND=false
14+
if [[ "${1:-}" == "-f" ]]; then
15+
FOREGROUND=true
16+
fi
17+
18+
# Ensure zones directory exists
19+
if [ ! -d "$ZONES_DIR" ]; then
20+
echo "Zone directory $ZONES_DIR not found."
21+
exit 1
22+
fi
23+
24+
# Clean up any leftover journal or PID files
25+
rm -f "$ZONES_DIR"/*.jnl "$PID_FILE"
26+
27+
# Print what we're doing
28+
echo "Starting BIND from $SCRIPT_DIR"
29+
30+
if $FOREGROUND; then
31+
echo "(running in foreground)"
32+
exec named -c "$NAMED_CONF" -p 53 -u "$(whoami)" -t "$SCRIPT_DIR" -g -d 1
33+
else
34+
echo "(running in background)"
35+
named -c "$NAMED_CONF" -p 53 -u "$(whoami)" -t "$SCRIPT_DIR" > "$LOG_FILE" 2>&1
36+
37+
# Wait for BIND to start with periodic checks
38+
MAX_WAIT=20 # Maximum wait time in attempts (20 * 0.5s = 10s)
39+
CHECK_INTERVAL=0.5 # Check every 500ms
40+
ATTEMPTS=0
41+
42+
echo -n "Waiting for BIND to start"
43+
44+
while [[ $ATTEMPTS -lt $MAX_WAIT ]]; do
45+
if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
46+
echo "" # New line after the dots
47+
ELAPSED=$(echo "scale=1; $ATTEMPTS * $CHECK_INTERVAL" | bc 2>/dev/null || echo "${ATTEMPTS}")
48+
echo "BIND started in background with PID $(cat "$PID_FILE") (took ~${ELAPSED}s)"
49+
exit 0
50+
fi
51+
52+
echo -n "."
53+
sleep "$CHECK_INTERVAL"
54+
((ATTEMPTS++))
55+
done
56+
57+
echo "" # New line after the dots
58+
TOTAL_WAIT=$(echo "scale=1; $MAX_WAIT * $CHECK_INTERVAL" | bc 2>/dev/null || echo "${MAX_WAIT}")
59+
echo "Failed to start BIND within ~${TOTAL_WAIT}s. See $LOG_FILE for details."
60+
61+
# Show last few lines of log for debugging
62+
if [[ -f "$LOG_FILE" ]]; then
63+
echo "Last few lines from log:"
64+
tail -5 "$LOG_FILE"
65+
fi
66+
67+
exit 1
68+
fi
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/bash
2+
3+
set -euo pipefail
4+
5+
# Resolve script location
6+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7+
ZONES_DIR="$SCRIPT_DIR/zones"
8+
PID_FILE="$ZONES_DIR/named.pid"
9+
10+
if [ -f "$PID_FILE" ]; then
11+
NAMED_PID=$(cat $PID_FILE)
12+
if [ -n "$NAMED_PID" ]; then
13+
echo "Stopping BIND running on pid $NAMED_PID"
14+
kill $NAMED_PID
15+
else
16+
echo "BIND pid is empty"
17+
fi
18+
else
19+
echo "BIND is not running"
20+
fi
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
dns_get_record() basic usage
3+
--SKIPIF--
4+
<?php require "skipif.inc"; ?>
5+
--FILE--
6+
<?php
7+
$domain = 'www.basic.dnstest.php.net';
8+
9+
$result = dns_get_record($domain, DNS_A);
10+
var_dump($result);
11+
?>
12+
--EXPECTF--
13+
array(%d) {
14+
[0]=>
15+
array(%d) {
16+
["host"]=>
17+
string(%d) "www.basic.dnstest.php.net"
18+
["class"]=>
19+
string(2) "IN"
20+
["ttl"]=>
21+
int(%d)
22+
["type"]=>
23+
string(1) "A"
24+
["ip"]=>
25+
string(%d) "192.0.2.1"
26+
}
27+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
options {
2+
directory "zones";
3+
listen-on port 53 { 127.0.0.1; };
4+
allow-query { any; };
5+
pid-file "named.pid";
6+
recursion yes;
7+
};
8+
9+
zone "basic.dnstest.php.net" {
10+
type master;
11+
file "basic.dnstest.php.net.zone";
12+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/bash
2+
set -euo pipefail
3+
4+
echo "Current DNS configuration:"
5+
resolvectl status | grep -E 'Link|Current DNS Server:|DNS Servers:'
6+
7+
echo -e "\nResetting DNS configuration by restarting systemd-resolved..."
8+
systemctl restart systemd-resolved.service
9+
10+
# Give it a moment to fully restart
11+
sleep 1
12+
13+
echo -e "\nUpdated DNS configuration:"
14+
resolvectl status | grep -E 'Link|Current DNS Server:|DNS Servers:'
15+
16+
echo -e "\nDNS configuration has been reset to original state."
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/bash
2+
set -euo pipefail
3+
4+
LOCAL_DNS="127.0.0.1"
5+
6+
echo "Looking for a DNS-enabled network interface..."
7+
8+
resolvectl status
9+
10+
# Find the interface with DNS and DefaultRoute using grep
11+
IFACE=$(resolvectl status | grep -B1 "Current Scopes: DNS" | grep "Link" | head -n1 | sed -E 's/Link [0-9]+ \(([^)]+)\)/\1/')
12+
13+
if [[ -z "$IFACE" ]]; then
14+
echo "Could not find a suitable interface with DNS configured."
15+
exit 1
16+
fi
17+
18+
echo "Using interface: $IFACE"
19+
20+
# Get current DNS server
21+
echo "Current configuration:"
22+
resolvectl status "$IFACE" | grep -E 'Current DNS Server:|DNS Servers:'
23+
24+
echo "Setting DNS to $LOCAL_DNS for $IFACE"
25+
26+
# Reset interface configuration
27+
resolvectl revert "$IFACE"
28+
29+
# Set DNS to local
30+
resolvectl dns "$IFACE" "$LOCAL_DNS"
31+
32+
# Confirm setup
33+
echo -e "\nUpdated configuration:"
34+
resolvectl status "$IFACE" | grep -E 'Current DNS Server:|DNS Servers:'
35+
36+
echo -e "\nDNS configuration has been updated."

ext/standard/tests/dns/skipif.inc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
// Do not run on Windows
3+
if (substr(PHP_OS, 0, 3) == 'WIN') {
4+
die("skip not for Windows");
5+
}
6+
// Run only if base functions are available
7+
if (!function_exists('dns_get_record')) {
8+
die("skip DNS functions not available");
9+
}
10+
// Run only if BIND server is running
11+
if (!file_exists(__DIR__ . '/zones/named.pid')) {
12+
die("skip BIND server is not running");
13+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
managed-keys.*

0 commit comments

Comments
 (0)