Skip to content

Commit 5f72880

Browse files
committed
[docker] add TREL automated integration test suite in DinD
Adds automated integration testing for the Thread Radio Encapsulation Link (TREL) feature in the Docker-in-Docker (DinD) test environment. Specifically: - Refactors tests/scripts/spinel_proxy.sh to support EXP_RCP_PORT for concurrent container simulation relays on shared bridge links. - Modifies tests/scripts/test_dind_dns_sd.sh to sequentially orchestrate dind_dns_sd.exp and dind_trel.exp expect suites, optimizing CI build layers caching while extending cleanup traps to drop PTY and bridges. - Implements tests/scripts/expect/dind_trel.exp to orchestrate PTY socat tunnels, configure datasets pre-boot, wait for child attachment, assert mDNS peer discovery via glob matches, parse unicast routable route targets, and verify TREL keep-alive inbound packet counters.
1 parent 2323cd1 commit 5f72880

5 files changed

Lines changed: 319 additions & 28 deletions

File tree

tests/scripts/expect/_common.exp

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
# POSSIBILITY OF SUCH DAMAGE.
2828
#
2929

30+
proc fail {message} {
31+
send_user "Error: $message\n"
32+
exit 1
33+
}
34+
3035
proc wait_for {command success {failure {[\r\n]FAILURE_NOT_EXPECTED[\r\n]}}} {
3136
set timeout 1
3237
for {set i 0} {$i < 40} {incr i} {
@@ -130,6 +135,22 @@ proc create_socat {id} {
130135
return [list $pty1 $pty2]
131136
}
132137

138+
proc create_socat_tcp {id port} {
139+
global socat_pids
140+
spawn socat -d -d pty,raw,echo=0 tcp-listen:$port,reuseaddr,bind=0.0.0.0
141+
lappend socat_pids [exp_pid]
142+
set pty ""
143+
expect {
144+
-re {PTY is (\S+)} {
145+
set pty $expect_out(1,string)
146+
}
147+
timeout {
148+
fail "Error: Timed out starting socat for id $id on port $port"
149+
}
150+
}
151+
return $pty
152+
}
153+
133154
proc start_otbr_docker {name sim_app sim_id pty1 pty2} {
134155
exec $sim_app $sim_id <$pty1 >$pty1 &
135156
exec docker run -d \
@@ -159,8 +180,16 @@ proc dispose_node {id} {
159180
}
160181
proc dispose_socat {} {
161182
global socat_pid
183+
global socat_pids
162184
if { [info exists socat_pid] } {
163-
exec kill $socat_pid
185+
catch { exec kill $socat_pid }
186+
unset socat_pid
187+
}
188+
if { [info exists socat_pids] } {
189+
foreach pid $socat_pids {
190+
catch { exec kill $pid }
191+
}
192+
set socat_pids {}
164193
}
165194
}
166195
proc dispose_all {} {

tests/scripts/expect/dind_dns_sd.exp

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,6 @@
2727
# POSSIBILITY OF SUCH DAMAGE.
2828
#
2929

30-
proc fail {message} {
31-
send_user "Error: $message\n"
32-
exit 1
33-
}
34-
3530
source "tests/scripts/expect/_common.exp"
3631

3732
# Custom start_otbr_docker for our network name
@@ -60,21 +55,6 @@ proc start_otbr_docker_dind {name sim_app sim_id pty} {
6055
sleep 5
6156
}
6257

63-
proc create_socat_tcp {id port} {
64-
global socat_pid
65-
spawn socat -d -d pty,raw,echo=0 tcp-listen:$port,reuseaddr,bind=0.0.0.0
66-
set socat_pid [exp_pid]
67-
set pty ""
68-
expect {
69-
-re {PTY is (\S+)} {
70-
set pty $expect_out(1,string)
71-
}
72-
timeout {
73-
send_user "Error: Timed out starting socat\n"; exit 1
74-
}
75-
}
76-
return $pty
77-
}
7858

7959
set pty1 [create_socat_tcp 1 9000]
8060
set container "otbr-test-container"

tests/scripts/expect/dind_trel.exp

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
#!/usr/bin/expect -f
2+
#
3+
# Copyright (c) 2026, The OpenThread Authors.
4+
# All rights reserved.
5+
#
6+
# Redistribution and use in source and binary forms, with or without
7+
# modification, are permitted provided that the following conditions are met:
8+
# 1. Redistributions of source code must retain the above copyright
9+
# notice, this list of conditions and the following disclaimer.
10+
# 2. Redistributions in binary form must reproduce the above copyright
11+
# notice, this list of conditions and the following disclaimer in the
12+
# documentation and/or other materials provided with the distribution.
13+
# 3. Neither the name of the copyright holder nor the
14+
# names of its contributors may be used to endorse or promote products
15+
# derived from this software without specific prior written permission.
16+
#
17+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27+
# POSSIBILITY OF SUCH DAMAGE.
28+
#
29+
30+
source "tests/scripts/expect/_common.exp"
31+
32+
33+
34+
# Dynamic helper to start the OTBR docker container and map the spinel relay
35+
proc start_otbr_docker_trel {name sim_app sim_id pty rcp_port} {
36+
exec docker run -d \
37+
--name $name \
38+
--network infrastructure-link \
39+
--cap-add=NET_ADMIN \
40+
--privileged \
41+
--add-host=host.docker.internal:host-gateway \
42+
--sysctl net.ipv6.conf.all.disable_ipv6=0 \
43+
--sysctl net.ipv4.conf.all.forwarding=1 \
44+
--sysctl net.ipv4.conf.default.forwarding=1 \
45+
--sysctl net.ipv6.conf.all.forwarding=1 \
46+
--sysctl net.ipv6.conf.default.forwarding=1 \
47+
-v $::env(EXP_REPO_ROOT)/tests/scripts/spinel_proxy.sh:/usr/bin/spinel_proxy.sh \
48+
-e OT_RCP_DEVICE=spinel+hdlc+forkpty:///usr/bin/spinel_proxy.sh \
49+
-e OT_INFRA_IF=eth0 \
50+
-e OT_THREAD_IF=wpan0 \
51+
-e EXP_RCP_PORT=$rcp_port \
52+
$::env(EXP_OTBR_DOCKER_IMAGE)
53+
sleep 2
54+
55+
# Start the simulated RCP binary on the host
56+
exec $sim_app $sim_id <$pty >$pty &
57+
sleep 5
58+
}
59+
60+
# Start relays and containers
61+
set pty1 [create_socat_tcp 1 9000]
62+
set pty2 [create_socat_tcp 2 9001]
63+
64+
set container1 "otbr-test-container-1"
65+
set container2 "otbr-test-container-2"
66+
67+
set dataset "0e080000000000010000000300001435060004001fffe002087d61eb42cdc48d6a0708fd0d07fca1b9f0500510ba088fc2bd6c3b3897f7a10f58263ff3030f4f70656e5468726561642d353234660102524f04109dc023ccd447b12b50997ef68020f19e0c0402a0f7f8"
68+
69+
send_user -- "--- Starting container 1 ---\n"
70+
start_otbr_docker_trel $container1 $::env(EXP_OT_RCP_PATH) 2 $pty1 9000
71+
72+
send_user -- "--- Starting container 2 ---\n"
73+
start_otbr_docker_trel $container2 $::env(EXP_OT_RCP_PATH) 3 $pty2 9001
74+
75+
# Ensure both containers have initialized correctly and otbr-agent socket is ready
76+
foreach c [list $container1 $container2] {
77+
set socket_ready false
78+
for {set i 0} {$i < 10} {incr i} {
79+
if {![catch {exec docker exec -i $c ot-ctl state}]} {
80+
set socket_ready true
81+
break
82+
}
83+
sleep 2
84+
}
85+
if {!$socket_ready} {
86+
fail "ot-ctl failed to communicate with otbr-agent on $c"
87+
}
88+
}
89+
90+
# Stop Thread and down interface on both containers initially to prevent default auto-start
91+
foreach c [list $container1 $container2] {
92+
exec docker exec -i $c ot-ctl thread stop
93+
exec docker exec -i $c ot-ctl ifconfig down
94+
}
95+
96+
# 1. Configure Active Dataset on both containers BEFORE starting Thread
97+
send_user -- "--- Configuring Active Dataset on both containers ---\n"
98+
exec docker exec -i $container1 ot-ctl dataset set active $dataset
99+
exec docker exec -i $container2 ot-ctl dataset set active $dataset
100+
101+
# Start Thread on Container 1 to form the network
102+
send_user -- "--- Starting Thread on Container 1 ---\n"
103+
exec docker exec -i $container1 ot-ctl ifconfig up
104+
exec docker exec -i $container1 ot-ctl thread start
105+
106+
# Wait for Container 1 to become leader
107+
set leader false
108+
for {set i 0} {$i < 20} {incr i} {
109+
spawn docker exec -i $container1 ot-ctl state
110+
set timeout 3
111+
expect {
112+
"leader" {
113+
set leader true
114+
}
115+
timeout {}
116+
}
117+
catch {close}; catch {wait}
118+
if {$leader} {
119+
break
120+
}
121+
sleep 2
122+
}
123+
if {!$leader} {
124+
fail "Container 1 did not become leader."
125+
}
126+
127+
# 2. Start Thread on Container 2 to join the network
128+
send_user -- "--- Starting Thread on Container 2 ---\n"
129+
exec docker exec -i $container2 ot-ctl ifconfig up
130+
exec docker exec -i $container2 ot-ctl thread start
131+
132+
# Wait for Container 2 to join successfully (router or child state)
133+
set joined false
134+
for {set i 0} {$i < 25} {incr i} {
135+
spawn docker exec -i $container2 ot-ctl state
136+
set timeout 3
137+
expect {
138+
"router" {
139+
set joined true
140+
}
141+
"child" {
142+
set joined true
143+
}
144+
timeout {}
145+
}
146+
catch {close}; catch {wait}
147+
if {$joined} {
148+
break
149+
}
150+
sleep 2
151+
}
152+
if {!$joined} {
153+
fail "Container 2 failed to join the network."
154+
}
155+
156+
# 3. Fetch Extended MAC Addresses to verify TREL peer discovery later
157+
send_user -- "--- Fetching Extended MAC Addresses for TREL verification ---\n"
158+
set otbr1_extaddr [exec docker exec -i $container1 ot-ctl extaddr]
159+
set otbr1_extaddr [lindex [split $otbr1_extaddr "\n"] 0]
160+
set otbr1_extaddr [string trim $otbr1_extaddr]
161+
set otbr2_extaddr [exec docker exec -i $container2 ot-ctl extaddr]
162+
set otbr2_extaddr [lindex [split $otbr2_extaddr "\n"] 0]
163+
set otbr2_extaddr [string trim $otbr2_extaddr]
164+
165+
# Retrieve infrastructure global IP address of both containers to verify TREL SocketAddress mappings
166+
set format "\{\{range .NetworkSettings.Networks\}\}\{\{.GlobalIPv6Address\}\}\{\{end\}\}"
167+
set otbr1_infra_ip [exec docker inspect $container1 -f $format]
168+
set otbr1_infra_ip [string trim $otbr1_infra_ip]
169+
170+
set otbr2_infra_ip [exec docker inspect $container2 -f $format]
171+
set otbr2_infra_ip [string trim $otbr2_infra_ip]
172+
173+
send_user "Container 1 Infra IP: $otbr1_infra_ip\n"
174+
send_user "Container 2 Infra IP: $otbr2_infra_ip\n"
175+
176+
# 4. Verify peer discovery via 'trel peers' matching peer's Extended MAC address using glob wildcards
177+
send_user -- "--- Verifying TREL peer discovery on Container 1 ---\n"
178+
set discovered false
179+
for {set i 0} {$i < 15} {incr i} {
180+
spawn docker exec -i $container1 ot-ctl trel peers
181+
set timeout 3
182+
expect {
183+
"*$otbr2_extaddr*" {
184+
send_user "Found Container 2 TREL peer (Ext MAC: $otbr2_extaddr) on Container 1!\n"
185+
set discovered true
186+
}
187+
timeout {}
188+
}
189+
catch {close}; catch {wait}
190+
if {$discovered} {
191+
break
192+
}
193+
sleep 1
194+
}
195+
if {!$discovered} {
196+
fail "Error: Container 2 TREL peer not discovered on Container 1 (Timeout)."
197+
}
198+
199+
send_user -- "--- Verifying TREL peer discovery on Container 2 ---\n"
200+
set discovered false
201+
for {set i 0} {$i < 15} {incr i} {
202+
spawn docker exec -i $container2 ot-ctl trel peers
203+
set timeout 3
204+
expect {
205+
"*$otbr1_extaddr*" {
206+
send_user "Found Container 1 TREL peer (Ext MAC: $otbr1_extaddr) on Container 2!\n"
207+
set discovered true
208+
}
209+
timeout {}
210+
}
211+
catch {close}; catch {wait}
212+
if {$discovered} {
213+
break
214+
}
215+
sleep 1
216+
}
217+
if {!$discovered} {
218+
fail "Error: Container 1 TREL peer not discovered on Container 2 (Timeout)."
219+
}
220+
221+
# 5. Ping peer and verify UDP packet encapsulation counters
222+
set otbr2_addrs [exec docker exec -i $container2 ot-ctl ipaddr]
223+
set ping_target ""
224+
foreach addr [split $otbr2_addrs "\n"] {
225+
set addr [string trim $addr]
226+
if {[string match "fd*" $addr] && ![string match "*ff:fe00:*" $addr]} {
227+
set ping_target $addr
228+
break
229+
}
230+
}
231+
if {$ping_target == ""} {
232+
fail "Error: Could not locate a routable Thread OMR/ML-EID address for Container 2."
233+
}
234+
send_user "Pinging Container 2 Thread Routable Address: $ping_target\n"
235+
236+
spawn docker exec -i $container1 ot-ctl ping $ping_target
237+
set timeout 10
238+
expect {
239+
"16 bytes from" {
240+
send_user "Ping succeeded!\n"
241+
}
242+
timeout {
243+
fail "Error: Ping timed out."
244+
}
245+
eof {
246+
fail "Error: Ping failed (EOF)."
247+
}
248+
}
249+
catch {close}; catch {wait}
250+
251+
# Verify TREL link counters (either keep-alives or data packets must be present)
252+
spawn docker exec -i $container1 ot-ctl trel counters
253+
expect {
254+
-re {Inbound:\s+Packets\s+([1-9][0-9]*)} {
255+
send_user "TREL link traffic verified: Inbound $expect_out(1,string) packets received over TREL!\n"
256+
}
257+
timeout {
258+
fail "Error: TREL Inbound counter is zero (Timeout)."
259+
}
260+
eof {
261+
fail "Error: TREL Inbound counter is zero (EOF)."
262+
}
263+
}
264+
catch {close}; catch {wait}
265+
266+
dispose_all
267+
268+
send_user -- "--- TREL Integration Test PASSED successfully! ---\n"
269+
exit 0

tests/scripts/spinel_proxy.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
# POSSIBILITY OF SUCH DAMAGE.
2727
#
2828

29-
exec 3<>/dev/tcp/"${EXP_GATEWAY_IP:-host.docker.internal}"/9000
29+
exec 3<>/dev/tcp/"${EXP_GATEWAY_IP:-host.docker.internal}"/"${EXP_RCP_PORT:-9000}"
3030
stdbuf -i0 -o0 -e0 cat <&3 &
3131
stdbuf -i0 -o0 -e0 cat >&3
3232
kill $!

0 commit comments

Comments
 (0)