]>
Commit | Line | Data |
---|---|---|
2682e09e A |
1 | #! /bin/bash |
2 | # | |
f0cc3e7b | 3 | # Copyright (c) 2017-2019 Apple Inc. All rights reserved. |
2682e09e A |
4 | # |
5 | # This script is currently for Apple Internal use only. | |
6 | # | |
7 | ||
f0cc3e7b A |
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 | ) | |
2682e09e A |
24 | |
25 | #============================================================================================================================ | |
26 | # PrintUsage | |
27 | #============================================================================================================================ | |
28 | ||
29 | PrintUsage() | |
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 | ||
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 | ||
f0cc3e7b A |
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 | ||
2682e09e A |
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=$! | |
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 | ||
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 | |
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 | ||
151 | StopPacketCapture() | |
152 | { | |
153 | LogMsg "Stopping tcpdump." | |
f0cc3e7b A |
154 | kill -TERM "${tcpdumpPID}" |
155 | kill -TERM "${tcpdumpLoopbackPID}" | |
2682e09e A |
156 | } |
157 | ||
158 | #============================================================================================================================ | |
159 | # RunInterfaceMulticastTests | |
160 | #============================================================================================================================ | |
161 | ||
162 | RunInterfaceMulticastTests() | |
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 | ||
244 | RunMulticastTests() | |
245 | { | |
f0cc3e7b A |
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 | |
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 | ||
289 | RunBrowseTest() | |
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 | ||
306 | #============================================================================================================================ | |
307 | # IsMacOS | |
308 | #============================================================================================================================ | |
309 | ||
310 | IsMacOS() | |
311 | { | |
312 | [[ $( sw_vers -productName ) =~ ^Mac\ OS ]] | |
313 | } | |
314 | ||
315 | #============================================================================================================================ | |
316 | # ArchiveLogs | |
317 | #============================================================================================================================ | |
318 | ||
319 | ArchiveLogs() | |
320 | { | |
f0cc3e7b A |
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" | |
2682e09e A |
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 | |
f0cc3e7b | 338 | open "${parentDir}" |
2682e09e A |
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 | { | |
f0cc3e7b A |
352 | local suffix='' |
353 | local -r productName=$( sw_vers -productName ) | |
2682e09e A |
354 | if [ -n "${productName}" ]; then |
355 | suffix+="_${productName}" | |
356 | fi | |
357 | ||
f0cc3e7b | 358 | local model |
2682e09e A |
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 | ||
f0cc3e7b | 369 | local -r buildVersion=$( sw_vers -buildVersion ) |
2682e09e A |
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 | { | |
f0cc3e7b | 385 | while getopts ":s:hV" option; do |
2682e09e A |
386 | case "${option}" in |
387 | h) | |
388 | PrintUsage | |
389 | exit 0 | |
390 | ;; | |
f0cc3e7b A |
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 | ;; | |
2682e09e A |
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 | ||
f0cc3e7b | 412 | [ "${OPTIND}" -gt "$#" ] || ErrQuit "unexpected argument \"${!OPTIND}\"." |
2682e09e A |
413 | |
414 | if IsMacOS; then | |
415 | if [ "${EUID}" -ne 0 ]; then | |
416 | echo "Re-launching with sudo" | |
f0cc3e7b | 417 | exec sudo "${script}" "$@" |
2682e09e | 418 | fi |
2682e09e A |
419 | else |
420 | [ "${EUID}" -eq 0 ] || ErrQuit "$( basename "${script}" ) needs to be run as root." | |
2682e09e A |
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 | ||
f0cc3e7b | 430 | LogMsg "About: $( basename "${script}" ) version ${version} ($( md5 -q "${script}" ))." |
2682e09e A |
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 | ||
f0cc3e7b A |
439 | serviceTypesOfInterest=( $( IFS=$'\n' sort -u <<< "${serviceTypesOfInterest[*]}" ) ) |
440 | ||
441 | GetStateDump 'before' | |
2682e09e A |
442 | RunNetStat |
443 | StartPacketCapture | |
444 | SaveExistingPacketCaptures | |
445 | RunBrowseTest | |
446 | RunMulticastTests | |
f0cc3e7b | 447 | GetStateDump 'after' |
2682e09e A |
448 | StopPacketCapture |
449 | ArchiveLogs | |
450 | } | |
451 | ||
452 | main "$@" |