]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSMacOSX/Scripts/bonjour-mcast-diagnose
mDNSResponder-1096.60.2.tar.gz
[apple/mdnsresponder.git] / mDNSMacOSX / Scripts / bonjour-mcast-diagnose
1 #! /bin/bash
2 #
3 # Copyright (c) 2017-2019 Apple Inc. All rights reserved.
4 #
5 # This script is currently for Apple Internal use only.
6 #
7
8 declare -r version=1.6
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 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 # IsMacOS
308 #============================================================================================================================
309
310 IsMacOS()
311 {
312 [[ $( sw_vers -productName ) =~ ^Mac\ OS ]]
313 }
314
315 #============================================================================================================================
316 # ArchiveLogs
317 #============================================================================================================================
318
319 ArchiveLogs()
320 {
321 local -r workdir=$( basename "${workPath}" )
322 local parentDir
323 if IsMacOS; then
324 parentDir=/var/tmp
325 else
326 parentDir=/var/mobile/Library/Logs/CrashReporter
327 fi
328 local -r archivePath="${parentDir}/${workdir}.tar.gz"
329
330 LogMsg "Archiving logs."
331 echo "---"
332 tar -C "${tempPath}" -czf "${archivePath}" "${workdir}"
333 if [ -e "${archivePath}" ]; then
334 echo "Created log archive at ${archivePath}"
335 echo "*** Please run sysdiagnose NOW. ***"
336 echo "Attach both the log archive and the sysdiagnose archive to the radar."
337 if IsMacOS; then
338 open "${parentDir}"
339 fi
340 else
341 echo "Failed to create archive at ${archivePath}."
342 fi
343 echo "---"
344 }
345
346 #============================================================================================================================
347 # CreateWorkDirName
348 #============================================================================================================================
349
350 CreateWorkDirName()
351 {
352 local suffix=''
353 local -r productName=$( sw_vers -productName )
354 if [ -n "${productName}" ]; then
355 suffix+="_${productName}"
356 fi
357
358 local model
359 if IsMacOS; then
360 model=$( sysctl -n hw.model )
361 model=${model//,/-}
362 else
363 model=$( gestalt_query -undecorated DeviceName )
364 fi
365 if [ -n "${model}" ]; then
366 suffix+="_${model}"
367 fi
368
369 local -r buildVersion=$( sw_vers -buildVersion )
370 if [ -n "${buildVersion}" ]; then
371 suffix+="_${buildVersion}"
372 fi
373
374 suffix=${suffix//[^A-Za-z0-9._-]/_}
375
376 printf "bonjour-mcast-diags_$( date '+%Y.%m.%d_%H-%M-%S%z' )${suffix}"
377 }
378
379 #============================================================================================================================
380 # main
381 #============================================================================================================================
382
383 main()
384 {
385 while getopts ":s:hV" option; do
386 case "${option}" in
387 h)
388 PrintUsage
389 exit 0
390 ;;
391 s)
392 serviceType=$( awk '{print tolower($0)}' <<< "${OPTARG}" )
393 if [[ ${serviceType} =~ ^_[-a-z0-9]*\._(tcp|udp)$ ]]; then
394 serviceTypesOfInterest+=( "${serviceType}" )
395 else
396 ErrQuit "Service type '${OPTARG}' is malformed."
397 fi
398 ;;
399 V)
400 echo "$( basename "${script}" ) version ${version}"
401 exit 0
402 ;;
403 :)
404 ErrQuit "option '${OPTARG}' requires an argument."
405 ;;
406 *)
407 ErrQuit "unknown option '${OPTARG}'."
408 ;;
409 esac
410 done
411
412 [ "${OPTIND}" -gt "$#" ] || ErrQuit "unexpected argument \"${!OPTIND}\"."
413
414 if IsMacOS; then
415 if [ "${EUID}" -ne 0 ]; then
416 echo "Re-launching with sudo"
417 exec sudo "${script}" "$@"
418 fi
419 else
420 [ "${EUID}" -eq 0 ] || ErrQuit "$( basename "${script}" ) needs to be run as root."
421 fi
422
423 tempPath=$( mktemp -d -q ) || ErrQuit "Failed to make temp directory."
424 workPath="${tempPath}/$( CreateWorkDirName )"
425 mkdir "${workPath}" || ErrQuit "Failed to make work directory."
426
427 trap SignalHandler SIGINT SIGTERM
428 trap ExitHandler EXIT
429
430 LogMsg "About: $( basename "${script}" ) version ${version} ($( md5 -q "${script}" ))."
431 if [ "${dnssdutil}" != "dnssdutil" ]; then
432 if [ -x "$( which "${dnssdutil}" )" ]; then
433 LogMsg "Using $( "${dnssdutil}" -V ) at $( which "${dnssdutil}" )."
434 else
435 LogMsg "WARNING: dnssdutil (${dnssdutil}) isn't an executable."
436 fi
437 fi
438
439 serviceTypesOfInterest=( $( IFS=$'\n' sort -u <<< "${serviceTypesOfInterest[*]}" ) )
440
441 GetStateDump 'before'
442 RunNetStat
443 StartPacketCapture
444 SaveExistingPacketCaptures
445 RunBrowseTest
446 RunMulticastTests
447 GetStateDump 'after'
448 StopPacketCapture
449 ArchiveLogs
450 }
451
452 main "$@"