]> git.saurik.com Git - apple/mdnsresponder.git/blame - mDNSMacOSX/Scripts/bonjour-mcast-diagnose
mDNSResponder-1310.80.1.tar.gz
[apple/mdnsresponder.git] / mDNSMacOSX / Scripts / bonjour-mcast-diagnose
CommitLineData
2682e09e
A
1#! /bin/bash
2#
19fa75a9 3# Copyright (c) 2017-2020 Apple Inc. All rights reserved.
2682e09e
A
4#
5# This script is currently for Apple Internal use only.
6#
7
19fa75a9 8declare -r version=1.8
f0cc3e7b
A
9declare -r script=${BASH_SOURCE[0]}
10declare -r dnssdutil=${dnssdutil:-dnssdutil}
11
12# The serviceTypesOfInterest array is initialized with commonly-debugged service types or service types whose records can
13# provide useful debugging information, e.g., _airport._tcp in case an AirPort base station is a WiFi network's access
14# point. Note: Additional service types can be added with the '-s' option.
15
16serviceTypesOfInterest=(
17 _airplay._tcp # AirPlay
18 _airport._tcp # AirPort Base Station
19 _companion-link._tcp # Companion Link
20 _hap._tcp # HomeKit Accessory Protocol
21 _homekit._tcp # HomeKit
22 _raop._tcp # Remote Audio Output Protocol
23)
2682e09e
A
24
25#============================================================================================================================
26# PrintUsage
27#============================================================================================================================
28
29PrintUsage()
30{
31 echo ""
32 echo "Usage: $( basename "${script}" ) [options]"
33 echo ""
34 echo "Options:"
f0cc3e7b 35 echo " -s Specifies a service type of interest, e.g., _airplay._tcp, _raop._tcp, etc. Can be used more than once."
2682e09e
A
36 echo " -V Display version of this script and exit."
37 echo ""
38}
39
40#============================================================================================================================
41# LogOut
42#============================================================================================================================
43
44LogOut()
45{
46 echo "$( date '+%Y-%m-%d %H:%M:%S%z' ): $*"
47}
48
49#============================================================================================================================
50# LogMsg
51#============================================================================================================================
52
53LogMsg()
54{
55 echo "$*"
56 if [ -d "${workPath}" ]; then
57 LogOut "$*" >> "${workPath}/log.txt"
58 fi
59}
60
61#============================================================================================================================
62# ErrQuit
63#============================================================================================================================
64
65ErrQuit()
66{
67 echo "error: $*"
68 exit 1
69}
70
71#============================================================================================================================
72# SignalHandler
73#============================================================================================================================
74
75SignalHandler()
76{
77 LogMsg "Exiting due to signal."
78 trap '' SIGINT SIGTERM
79 pkill -TERM -P $$
80 wait
81 exit 2
82}
83
84#============================================================================================================================
85# ExitHandler
86#============================================================================================================================
87
88ExitHandler()
89{
90 if [ -d "${tempPath}" ]; then
91 rm -fr "${tempPath}"
92 fi
93}
94
f0cc3e7b
A
95#============================================================================================================================
96# GetStateDump
97#============================================================================================================================
98
99GetStateDump()
100{
101 local suffix=''
102 if [ -n "${1}" ]; then
103 suffix="-${1//[^A-Za-z0-9._-]/_}"
104 fi
105 LogMsg "Getting mDNSResponder state dump."
106 dns-sd -O -stdout &> "${workPath}/state-dump${suffix}.txt"
107}
108
2682e09e
A
109#============================================================================================================================
110# RunNetStat
111#============================================================================================================================
112
113RunNetStat()
114{
115 LogMsg "Running netstat -g -n -s"
116 netstat -g -n -s &> "${workPath}/netstat-g-n-s.txt"
117}
118
119#============================================================================================================================
120# StartPacketCapture
121#============================================================================================================================
122
123StartPacketCapture()
124{
125 LogMsg "Starting tcpdump."
126 tcpdump -n -w "${workPath}/tcpdump.pcapng" &> "${workPath}/tcpdump.txt" &
127 tcpdumpPID=$!
f0cc3e7b
A
128 tcpdump -i lo0 -n -w "${workPath}/tcpdump-loopback.pcapng" &> "${workPath}/tcpdump-loopback.txt" 'udp port 5353' &
129 tcpdumpLoopbackPID=$!
2682e09e
A
130}
131
132#============================================================================================================================
133# SaveExistingPacketCaptures
134#============================================================================================================================
135
136SaveExistingPacketCaptures()
137{
138 LogMsg "Saving existing mDNS packet captures."
139 mkdir "${workPath}/pcaps"
140 for file in /tmp/mdns-tcpdump.pcapng*; do
141 [ -e "${file}" ] || continue
f0cc3e7b
A
142 baseName=$( basename "${file}" | sed -E 's/^mdns-tcpdump.pcapng([0-9]+)$/mdns-tcpdump-\1.pcapng/' )
143 gzip < "${file}" > "${workPath}/pcaps/${baseName}.gz"
2682e09e
A
144 done
145}
146
147#============================================================================================================================
148# StopPacketCapture
149#============================================================================================================================
150
151StopPacketCapture()
152{
153 LogMsg "Stopping tcpdump."
f0cc3e7b
A
154 kill -TERM "${tcpdumpPID}"
155 kill -TERM "${tcpdumpLoopbackPID}"
2682e09e
A
156}
157
158#============================================================================================================================
159# RunInterfaceMulticastTests
160#============================================================================================================================
161
162RunInterfaceMulticastTests()
163{
f0cc3e7b
A
164 local -r ifname=${1}
165 local -r allHostsV4=224.0.0.1
166 local -r allHostsV6=ff02::1
167 local -r mDNSV4=224.0.0.251
168 local -r mDNSV6=ff02::fb
169 local -r log="${workPath}/mcast-test-log-${ifname}.txt"
170 local serviceList=( $( "${dnssdutil}" queryrecord -i "${ifname}" -A -t ptr -n _services._dns-sd._udp.local -l 6 | sed -E -n 's/.*(_.*_(tcp|udp)\.local\.)$/\1/p' ) )
171 serviceList+=( "${serviceTypesOfInterest[@]/%/.local.}" )
172 serviceList=( $( IFS=$'\n' sort -f -u <<< "${serviceList[*]}" ) )
2682e09e
A
173
174 LogOut "List of services: ${serviceList[*]}" >> "${log}"
f0cc3e7b
A
175
176 # Ping IPv4 broadcast address.
177
178 local broadcastAddr=$( ifconfig "${ifname}" inet | awk '$5 == "broadcast" {print $6}' )
179 if [ -n "${broadcastAddr}" ]; then
180 LogOut "Pinging ${broadcastAddr} on interface ${ifname}." >> "${log}"
181 ping -t 5 -b "${ifname}" "${broadcastAddr}" &> "${workPath}/ping-broadcast-${ifname}.txt"
182 else
183 LogOut "No IPv4 broadcast address for ${ifname}." >> "${log}"
184 fi
185
2682e09e
A
186 # Ping All Hosts IPv4 multicast address.
187
f0cc3e7b 188 local routeOutput=$( route -n get -ifscope "${ifname}" "${allHostsV4}" 2> /dev/null )
2682e09e 189 if [ -n "${routeOutput}" ]; then
f0cc3e7b
A
190 LogOut "Pinging ${allHostsV4} on interface ${ifname}." >> "${log}"
191 ping -t 5 -b "${ifname}" "${allHostsV4}" &> "${workPath}/ping-all-hosts-${ifname}.txt"
2682e09e 192 else
f0cc3e7b 193 LogOut "No route to ${allHostsV4} on interface ${ifname}." >> "${log}"
2682e09e
A
194 fi
195
196 # Ping mDNS IPv4 multicast address.
197
f0cc3e7b 198 routeOutput=$( route -n get -ifscope "${ifname}" "${mDNSV4}" 2> /dev/null )
2682e09e 199 if [ -n "${routeOutput}" ]; then
f0cc3e7b
A
200 LogOut "Pinging ${mDNSV4} on interface ${ifname}." >> "${log}"
201 ping -t 5 -b "${ifname}" "${mDNSV4}" &> "${workPath}/ping-mDNS-${ifname}.txt"
2682e09e 202 else
f0cc3e7b 203 LogOut "No route to ${mDNSV4} on interface ${ifname}." >> "${log}"
2682e09e
A
204 fi
205
206 # Ping All Hosts IPv6 multicast address.
207
f0cc3e7b 208 routeOutput=$( route -n get -ifscope "${ifname}" -inet6 "${allHostsV6}" 2> /dev/null )
2682e09e 209 if [ -n "${routeOutput}" ]; then
f0cc3e7b
A
210 LogOut "Pinging ${allHostsV6} on interface ${ifname}." >> "${log}"
211 ping6 -c 6 -I "${ifname}" "${allHostsV6}" &> "${workPath}/ping6-all-hosts-${ifname}.txt"
2682e09e 212 else
f0cc3e7b 213 LogOut "No route to ${allHostsV6} on interface ${ifname}." >> "${log}"
2682e09e
A
214 fi
215
216 # Ping mDNS IPv6 multicast address.
217
f0cc3e7b 218 routeOutput=$( route -n get -ifscope "${ifname}" -inet6 "${mDNSV6}" 2> /dev/null )
2682e09e 219 if [ -n "${routeOutput}" ]; then
f0cc3e7b
A
220 LogOut "Pinging ${mDNSV6} on interface ${ifname}." >> "${log}"
221 ping6 -c 6 -I "${ifname}" "${mDNSV6}" &> "${workPath}/ping6-mDNS-${ifname}.txt"
2682e09e 222 else
f0cc3e7b 223 LogOut "No route to ${mDNSV6} on interface ${ifname}." >> "${log}"
2682e09e
A
224 fi
225
226 # Send mDNS queries for services.
227
228 for service in "${serviceList[@]}"; do
f0cc3e7b 229 LogOut "Sending mDNS queries for ${service} on interface ${ifname}." >> "${log}"
2682e09e
A
230 for(( i = 1; i <= 3; ++i )); do
231 printf "\n"
232 "${dnssdutil}" mdnsquery -i "${ifname}" -n "${service}" -t ptr -r 2
233 printf "\n"
234 "${dnssdutil}" mdnsquery -i "${ifname}" -n "${service}" -t ptr -r 1 --QU -p 5353
235 printf "\n"
236 done >> "${workPath}/mdnsquery-${ifname}.txt" 2>&1
237 done
238}
239
240#============================================================================================================================
241# RunMulticastTests
242#============================================================================================================================
243
244RunMulticastTests()
245{
f0cc3e7b 246 local -r interfaces=( $( ifconfig -l -u ) )
19fa75a9 247 local -r skipPrefixes=( ap awdl bridge ipsec llw nan p2p pdp_ip pktap UDC utun )
f0cc3e7b
A
248 local -a pids
249 local ifname
250 local skip
251 local pid
2682e09e
A
252
253 LogMsg "List of interfaces: ${interfaces[*]}"
254 for ifname in "${interfaces[@]}"; do
f0cc3e7b
A
255 skip=false
256 for prefix in "${skipPrefixes[@]}"; do
2682e09e
A
257 if [[ ${ifname} =~ ^${prefix}[0-9]*$ ]]; then
258 skip=true
259 break
260 fi
261 done
262
f0cc3e7b
A
263 if ! "${skip}"; then
264 ifconfig ${ifname} | egrep -q '\binet6?\b'
2682e09e
A
265 if [ $? -ne 0 ]; then
266 skip=true
267 fi
268 fi
269
f0cc3e7b 270 if "${skip}"; then
2682e09e
A
271 continue
272 fi
273
274 LogMsg "Starting interface multicast tests for ${ifname}."
f0cc3e7b 275 RunInterfaceMulticastTests "${ifname}" & pids+=( $! )
2682e09e
A
276 done
277
278 LogMsg "Waiting for interface multicast tests to complete..."
279 for pid in "${pids[@]}"; do
280 wait "${pid}"
281 done
282 LogMsg "All interface multicast tests completed."
283}
284
285#============================================================================================================================
286# RunBrowseTest
287#============================================================================================================================
288
289RunBrowseTest()
290{
f0cc3e7b
A
291 local -a typeArgs
292
293 if [ "${#serviceTypesOfInterest[@]}" -gt 0 ]; then
294 for serviceType in "${serviceTypesOfInterest[@]}"; do
295 typeArgs+=( "-t" "${serviceType}" )
296 done
297
298 LogMsg "Running dnssdutil browseAll command for service types of interest."
299 "${dnssdutil}" browseAll -A -d local -b 10 -c 10 "${typeArgs[@]}" &> "${workPath}/browseAll-STOI.txt"
300 fi
301
302 LogMsg "Running general dnssdutil browseAll command."
2682e09e
A
303 "${dnssdutil}" browseAll -A -d local -b 10 -c 10 &> "${workPath}/browseAll.txt"
304}
305
2682e09e
A
306#============================================================================================================================
307# ArchiveLogs
308#============================================================================================================================
309
310ArchiveLogs()
311{
19fa75a9
A
312 local parentDir=''
313 # First, check for the non-macOS sysdiagnose archive path, then check for the macOS sysdiagnose archive path.
314 for dir in '/var/mobile/Library/Logs/CrashReporter' '/var/tmp'; do
315 if [ -w "${dir}" ]; then
316 parentDir="${dir}"
317 break
318 fi
319 done
320 # If a writable path wasn't available, just use /tmp.
321 [ -n "${parentDir}" ] || parentDir='/tmp'
f0cc3e7b 322 local -r workdir=$( basename "${workPath}" )
f0cc3e7b 323 local -r archivePath="${parentDir}/${workdir}.tar.gz"
2682e09e
A
324 LogMsg "Archiving logs."
325 echo "---"
326 tar -C "${tempPath}" -czf "${archivePath}" "${workdir}"
327 if [ -e "${archivePath}" ]; then
328 echo "Created log archive at ${archivePath}"
329 echo "*** Please run sysdiagnose NOW. ***"
330 echo "Attach both the log archive and the sysdiagnose archive to the radar."
19fa75a9 331 if command -v open 2>&1 > /dev/null; then
f0cc3e7b 332 open "${parentDir}"
2682e09e
A
333 fi
334 else
335 echo "Failed to create archive at ${archivePath}."
336 fi
337 echo "---"
338}
339
340#============================================================================================================================
341# CreateWorkDirName
342#============================================================================================================================
343
344CreateWorkDirName()
345{
f0cc3e7b
A
346 local suffix=''
347 local -r productName=$( sw_vers -productName )
2682e09e
A
348 if [ -n "${productName}" ]; then
349 suffix+="_${productName}"
350 fi
351
19fa75a9
A
352 local model=''
353 if command -v gestalt_query 2>&1 > /dev/null; then
354 model=$( gestalt_query -undecorated ProductType )
2682e09e 355 else
19fa75a9 356 model=$( sysctl -n hw.model )
2682e09e 357 fi
19fa75a9 358 model=${model//,/-}
2682e09e
A
359 if [ -n "${model}" ]; then
360 suffix+="_${model}"
361 fi
362
f0cc3e7b 363 local -r buildVersion=$( sw_vers -buildVersion )
2682e09e
A
364 if [ -n "${buildVersion}" ]; then
365 suffix+="_${buildVersion}"
366 fi
367
368 suffix=${suffix//[^A-Za-z0-9._-]/_}
369
370 printf "bonjour-mcast-diags_$( date '+%Y.%m.%d_%H-%M-%S%z' )${suffix}"
371}
372
373#============================================================================================================================
374# main
375#============================================================================================================================
376
377main()
378{
f0cc3e7b 379 while getopts ":s:hV" option; do
2682e09e
A
380 case "${option}" in
381 h)
382 PrintUsage
383 exit 0
384 ;;
f0cc3e7b
A
385 s)
386 serviceType=$( awk '{print tolower($0)}' <<< "${OPTARG}" )
387 if [[ ${serviceType} =~ ^_[-a-z0-9]*\._(tcp|udp)$ ]]; then
388 serviceTypesOfInterest+=( "${serviceType}" )
389 else
390 ErrQuit "Service type '${OPTARG}' is malformed."
391 fi
392 ;;
2682e09e
A
393 V)
394 echo "$( basename "${script}" ) version ${version}"
395 exit 0
396 ;;
397 :)
398 ErrQuit "option '${OPTARG}' requires an argument."
399 ;;
400 *)
401 ErrQuit "unknown option '${OPTARG}'."
402 ;;
403 esac
404 done
405
f0cc3e7b 406 [ "${OPTIND}" -gt "$#" ] || ErrQuit "unexpected argument \"${!OPTIND}\"."
2682e09e 407
19fa75a9
A
408 if [ "${EUID}" -ne 0 ]; then
409 if command -v sudo 2>&1 > /dev/null; then
2682e09e 410 echo "Re-launching with sudo"
f0cc3e7b 411 exec sudo "${script}" "$@"
19fa75a9
A
412 else
413 ErrQuit "$( basename "${script}" ) needs to be run as root."
2682e09e 414 fi
2682e09e
A
415 fi
416
417 tempPath=$( mktemp -d -q ) || ErrQuit "Failed to make temp directory."
418 workPath="${tempPath}/$( CreateWorkDirName )"
419 mkdir "${workPath}" || ErrQuit "Failed to make work directory."
420
421 trap SignalHandler SIGINT SIGTERM
422 trap ExitHandler EXIT
423
f0cc3e7b 424 LogMsg "About: $( basename "${script}" ) version ${version} ($( md5 -q "${script}" ))."
2682e09e
A
425 if [ "${dnssdutil}" != "dnssdutil" ]; then
426 if [ -x "$( which "${dnssdutil}" )" ]; then
427 LogMsg "Using $( "${dnssdutil}" -V ) at $( which "${dnssdutil}" )."
428 else
429 LogMsg "WARNING: dnssdutil (${dnssdutil}) isn't an executable."
430 fi
431 fi
432
f0cc3e7b
A
433 serviceTypesOfInterest=( $( IFS=$'\n' sort -u <<< "${serviceTypesOfInterest[*]}" ) )
434
435 GetStateDump 'before'
2682e09e
A
436 RunNetStat
437 StartPacketCapture
438 SaveExistingPacketCaptures
439 RunBrowseTest
440 RunMulticastTests
f0cc3e7b 441 GetStateDump 'after'
2682e09e
A
442 StopPacketCapture
443 ArchiveLogs
444}
445
446main "$@"