]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSMacOSX/Scripts/bonjour-mcast-diagnose
mDNSResponder-1310.40.42.tar.gz
[apple/mdnsresponder.git] / mDNSMacOSX / Scripts / bonjour-mcast-diagnose
1 #! /bin/bash
2 #
3 # Copyright (c) 2017-2020 Apple Inc. All rights reserved.
4 #
5 # This script is currently for Apple Internal use only.
6 #
7
8 declare -r version=1.8
9 declare -r script=${BASH_SOURCE[0]}
10 declare -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
16 serviceTypesOfInterest=(
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 )
24
25 #============================================================================================================================
26 # PrintUsage
27 #============================================================================================================================
28
29 PrintUsage()
30 {
31 echo ""
32 echo "Usage: $( basename "${script}" ) [options]"
33 echo ""
34 echo "Options:"
35 echo " -s Specifies a service type of interest, e.g., _airplay._tcp, _raop._tcp, etc. Can be used more than once."
36 echo " -V Display version of this script and exit."
37 echo ""
38 }
39
40 #============================================================================================================================
41 # LogOut
42 #============================================================================================================================
43
44 LogOut()
45 {
46 echo "$( date '+%Y-%m-%d %H:%M:%S%z' ): $*"
47 }
48
49 #============================================================================================================================
50 # LogMsg
51 #============================================================================================================================
52
53 LogMsg()
54 {
55 echo "$*"
56 if [ -d "${workPath}" ]; then
57 LogOut "$*" >> "${workPath}/log.txt"
58 fi
59 }
60
61 #============================================================================================================================
62 # ErrQuit
63 #============================================================================================================================
64
65 ErrQuit()
66 {
67 echo "error: $*"
68 exit 1
69 }
70
71 #============================================================================================================================
72 # SignalHandler
73 #============================================================================================================================
74
75 SignalHandler()
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
88 ExitHandler()
89 {
90 if [ -d "${tempPath}" ]; then
91 rm -fr "${tempPath}"
92 fi
93 }
94
95 #============================================================================================================================
96 # GetStateDump
97 #============================================================================================================================
98
99 GetStateDump()
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
109 #============================================================================================================================
110 # RunNetStat
111 #============================================================================================================================
112
113 RunNetStat()
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
123 StartPacketCapture()
124 {
125 LogMsg "Starting tcpdump."
126 tcpdump -n -w "${workPath}/tcpdump.pcapng" &> "${workPath}/tcpdump.txt" &
127 tcpdumpPID=$!
128 tcpdump -i lo0 -n -w "${workPath}/tcpdump-loopback.pcapng" &> "${workPath}/tcpdump-loopback.txt" 'udp port 5353' &
129 tcpdumpLoopbackPID=$!
130 }
131
132 #============================================================================================================================
133 # SaveExistingPacketCaptures
134 #============================================================================================================================
135
136 SaveExistingPacketCaptures()
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
142 baseName=$( basename "${file}" | sed -E 's/^mdns-tcpdump.pcapng([0-9]+)$/mdns-tcpdump-\1.pcapng/' )
143 gzip < "${file}" > "${workPath}/pcaps/${baseName}.gz"
144 done
145 }
146
147 #============================================================================================================================
148 # StopPacketCapture
149 #============================================================================================================================
150
151 StopPacketCapture()
152 {
153 LogMsg "Stopping tcpdump."
154 kill -TERM "${tcpdumpPID}"
155 kill -TERM "${tcpdumpLoopbackPID}"
156 }
157
158 #============================================================================================================================
159 # RunInterfaceMulticastTests
160 #============================================================================================================================
161
162 RunInterfaceMulticastTests()
163 {
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[*]}" ) )
173
174 LogOut "List of services: ${serviceList[*]}" >> "${log}"
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
186 # Ping All Hosts IPv4 multicast address.
187
188 local routeOutput=$( route -n get -ifscope "${ifname}" "${allHostsV4}" 2> /dev/null )
189 if [ -n "${routeOutput}" ]; then
190 LogOut "Pinging ${allHostsV4} on interface ${ifname}." >> "${log}"
191 ping -t 5 -b "${ifname}" "${allHostsV4}" &> "${workPath}/ping-all-hosts-${ifname}.txt"
192 else
193 LogOut "No route to ${allHostsV4} on interface ${ifname}." >> "${log}"
194 fi
195
196 # Ping mDNS IPv4 multicast address.
197
198 routeOutput=$( route -n get -ifscope "${ifname}" "${mDNSV4}" 2> /dev/null )
199 if [ -n "${routeOutput}" ]; then
200 LogOut "Pinging ${mDNSV4} on interface ${ifname}." >> "${log}"
201 ping -t 5 -b "${ifname}" "${mDNSV4}" &> "${workPath}/ping-mDNS-${ifname}.txt"
202 else
203 LogOut "No route to ${mDNSV4} on interface ${ifname}." >> "${log}"
204 fi
205
206 # Ping All Hosts IPv6 multicast address.
207
208 routeOutput=$( route -n get -ifscope "${ifname}" -inet6 "${allHostsV6}" 2> /dev/null )
209 if [ -n "${routeOutput}" ]; then
210 LogOut "Pinging ${allHostsV6} on interface ${ifname}." >> "${log}"
211 ping6 -c 6 -I "${ifname}" "${allHostsV6}" &> "${workPath}/ping6-all-hosts-${ifname}.txt"
212 else
213 LogOut "No route to ${allHostsV6} on interface ${ifname}." >> "${log}"
214 fi
215
216 # Ping mDNS IPv6 multicast address.
217
218 routeOutput=$( route -n get -ifscope "${ifname}" -inet6 "${mDNSV6}" 2> /dev/null )
219 if [ -n "${routeOutput}" ]; then
220 LogOut "Pinging ${mDNSV6} on interface ${ifname}." >> "${log}"
221 ping6 -c 6 -I "${ifname}" "${mDNSV6}" &> "${workPath}/ping6-mDNS-${ifname}.txt"
222 else
223 LogOut "No route to ${mDNSV6} on interface ${ifname}." >> "${log}"
224 fi
225
226 # Send mDNS queries for services.
227
228 for service in "${serviceList[@]}"; do
229 LogOut "Sending mDNS queries for ${service} on interface ${ifname}." >> "${log}"
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
244 RunMulticastTests()
245 {
246 local -r interfaces=( $( ifconfig -l -u ) )
247 local -r skipPrefixes=( ap awdl bridge ipsec llw nan p2p pdp_ip pktap UDC utun )
248 local -a pids
249 local ifname
250 local skip
251 local pid
252
253 LogMsg "List of interfaces: ${interfaces[*]}"
254 for ifname in "${interfaces[@]}"; do
255 skip=false
256 for prefix in "${skipPrefixes[@]}"; do
257 if [[ ${ifname} =~ ^${prefix}[0-9]*$ ]]; then
258 skip=true
259 break
260 fi
261 done
262
263 if ! "${skip}"; then
264 ifconfig ${ifname} | egrep -q '\binet6?\b'
265 if [ $? -ne 0 ]; then
266 skip=true
267 fi
268 fi
269
270 if "${skip}"; then
271 continue
272 fi
273
274 LogMsg "Starting interface multicast tests for ${ifname}."
275 RunInterfaceMulticastTests "${ifname}" & pids+=( $! )
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
289 RunBrowseTest()
290 {
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."
303 "${dnssdutil}" browseAll -A -d local -b 10 -c 10 &> "${workPath}/browseAll.txt"
304 }
305
306 #============================================================================================================================
307 # ArchiveLogs
308 #============================================================================================================================
309
310 ArchiveLogs()
311 {
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'
322 local -r workdir=$( basename "${workPath}" )
323 local -r archivePath="${parentDir}/${workdir}.tar.gz"
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."
331 if command -v open 2>&1 > /dev/null; then
332 open "${parentDir}"
333 fi
334 else
335 echo "Failed to create archive at ${archivePath}."
336 fi
337 echo "---"
338 }
339
340 #============================================================================================================================
341 # CreateWorkDirName
342 #============================================================================================================================
343
344 CreateWorkDirName()
345 {
346 local suffix=''
347 local -r productName=$( sw_vers -productName )
348 if [ -n "${productName}" ]; then
349 suffix+="_${productName}"
350 fi
351
352 local model=''
353 if command -v gestalt_query 2>&1 > /dev/null; then
354 model=$( gestalt_query -undecorated ProductType )
355 else
356 model=$( sysctl -n hw.model )
357 fi
358 model=${model//,/-}
359 if [ -n "${model}" ]; then
360 suffix+="_${model}"
361 fi
362
363 local -r buildVersion=$( sw_vers -buildVersion )
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
377 main()
378 {
379 while getopts ":s:hV" option; do
380 case "${option}" in
381 h)
382 PrintUsage
383 exit 0
384 ;;
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 ;;
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
406 [ "${OPTIND}" -gt "$#" ] || ErrQuit "unexpected argument \"${!OPTIND}\"."
407
408 if [ "${EUID}" -ne 0 ]; then
409 if command -v sudo 2>&1 > /dev/null; then
410 echo "Re-launching with sudo"
411 exec sudo "${script}" "$@"
412 else
413 ErrQuit "$( basename "${script}" ) needs to be run as root."
414 fi
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
424 LogMsg "About: $( basename "${script}" ) version ${version} ($( md5 -q "${script}" ))."
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
433 serviceTypesOfInterest=( $( IFS=$'\n' sort -u <<< "${serviceTypesOfInterest[*]}" ) )
434
435 GetStateDump 'before'
436 RunNetStat
437 StartPacketCapture
438 SaveExistingPacketCaptures
439 RunBrowseTest
440 RunMulticastTests
441 GetStateDump 'after'
442 StopPacketCapture
443 ArchiveLogs
444 }
445
446 main "$@"