Skip to content

Commit 198090e

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 a5f21ca commit 198090e

File tree

11 files changed

+234
-0
lines changed

11 files changed

+234
-0
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ runs:
2727
ldap-utils \
2828
openssl \
2929
slapd \
30+
bind9 \
31+
bind9utils \
3032
language-pack-de \
3133
libgmp-dev \
3234
libicu-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;"

ext/standard/tests/dns/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
managed-keys.*
2+
named.conf

ext/standard/tests/dns/bind-start.sh

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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_TEMPLATE="$SCRIPT_DIR/named.conf.in"
9+
NAMED_CONF="$SCRIPT_DIR/named.conf"
10+
PID_FILE="$ZONES_DIR/named.pid"
11+
LOG_FILE="$SCRIPT_DIR/named.log"
12+
13+
# Default mode: background
14+
FOREGROUND=false
15+
if [[ "${1:-}" == "-f" ]]; then
16+
FOREGROUND=true
17+
fi
18+
19+
# Ensure zones directory exists
20+
if [ ! -d "$ZONES_DIR" ]; then
21+
echo "Zone directory $ZONES_DIR not found."
22+
exit 1
23+
fi
24+
25+
# Ensure template exists
26+
if [ ! -f "$NAMED_CONF_TEMPLATE" ]; then
27+
echo "Template file $NAMED_CONF_TEMPLATE not found."
28+
exit 1
29+
fi
30+
31+
# Generate named.conf from template
32+
echo "Generating $NAMED_CONF from $NAMED_CONF_TEMPLATE"
33+
sed -e "s|@ZONES_DIR@|$ZONES_DIR|g" \
34+
-e "s|@PID_FILE@|$PID_FILE|g" \
35+
-e "s|@SCRIPT_DIR@|$SCRIPT_DIR|g" \
36+
"$NAMED_CONF_TEMPLATE" > "$NAMED_CONF"
37+
38+
# Clean up any leftover journal or PID files
39+
rm -f "$ZONES_DIR"/*.jnl "$PID_FILE"
40+
41+
# Print what we're doing
42+
echo "Starting BIND from $SCRIPT_DIR"
43+
44+
if $FOREGROUND; then
45+
echo "(running in foreground)"
46+
exec named -c "$NAMED_CONF" -p 53 -u "$(whoami)" -g -d 1
47+
else
48+
echo "(running in background)"
49+
named -c "$NAMED_CONF" -p 53 -u "$(whoami)" > "$LOG_FILE" 2>&1
50+
51+
# Wait for BIND to start with periodic checks
52+
MAX_WAIT=20 # Maximum wait time in attempts (20 * 0.5s = 10s)
53+
CHECK_INTERVAL=0.5 # Check every 500ms
54+
ATTEMPTS=0
55+
56+
echo -n "Waiting for BIND to start"
57+
58+
while [[ $ATTEMPTS -lt $MAX_WAIT ]]; do
59+
if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then
60+
echo "" # New line after the dots
61+
ELAPSED=$(echo "scale=1; $ATTEMPTS * $CHECK_INTERVAL" | bc 2>/dev/null || echo "${ATTEMPTS}")
62+
echo "BIND started in background with PID $(cat "$PID_FILE") (took ~${ELAPSED}s)"
63+
exit 0
64+
fi
65+
66+
echo -n "."
67+
sleep "$CHECK_INTERVAL"
68+
((ATTEMPTS++))
69+
done
70+
71+
echo "" # New line after the dots
72+
TOTAL_WAIT=$(echo "scale=1; $MAX_WAIT * $CHECK_INTERVAL" | bc 2>/dev/null || echo "${MAX_WAIT}")
73+
echo "Failed to start BIND within ~${TOTAL_WAIT}s. See $LOG_FILE for details."
74+
75+
# Show last few lines of log for debugging
76+
if [[ -f "$LOG_FILE" ]]; then
77+
echo "Last few lines from log:"
78+
tail -5 "$LOG_FILE"
79+
fi
80+
81+
exit 1
82+
fi

ext/standard/tests/dns/bind-stop.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
NAMED_CONF="$SCRIPT_DIR/named.conf"
10+
11+
if [ -f "$PID_FILE" ]; then
12+
NAMED_PID=$(cat $PID_FILE)
13+
if [ -n "$NAMED_PID" ]; then
14+
echo "Stopping BIND running on pid $NAMED_PID"
15+
kill $NAMED_PID
16+
else
17+
echo "BIND pid is empty"
18+
fi
19+
else
20+
echo "BIND is not running"
21+
fi
22+
23+
if [ -f "$NAMED_CONF" ]; then
24+
rm "$NAMED_CONF"
25+
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+
}

ext/standard/tests/dns/named.conf.in

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
options {
2+
directory "@ZONES_DIR@";
3+
listen-on port 53 { 127.0.0.1; };
4+
allow-query { any; };
5+
pid-file "@PID_FILE@";
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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
$TTL 86400
2+
@ IN SOA ns1.basic.dnstest.php.net. hostmaster.basic.dnstest.php.net. (
3+
2025041101 ; Serial
4+
3600 ; Refresh
5+
1800 ; Retry
6+
604800 ; Expire
7+
86400 ) ; Minimum TTL
8+
9+
IN NS ns1.basic.dnstest.php.net.
10+
11+
ns1 IN A 127.0.0.1
12+
www IN A 192.0.2.1
13+
mx1 IN A 192.0.2.2
14+
IN MX 10 mx1.basic.dnstest.php.net.
15+
16+
txt1 IN TXT "This is a test TXT record"

0 commit comments

Comments
 (0)