2 * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include <dirent.h> // opendir
19 #include <sys/stat.h> // stat
21 #include <archive_entry.h>
22 #include <AssertMacros.h> // require, require_action
24 #include "mDNSMacOSX.h"
26 #include "xpc_services.h"
27 #include "xpc_service_log_utility.h"
28 #include "xpc_clients.h"
29 #include "system_utilities.h" // IsAppleInternalBuild
31 #define STATE_DUMP_PLAIN_SUFFIX "txt"
32 #define STATE_DUMP_COMPRESSED_SUFFIX "tar.bz2"
35 extern mDNS mDNSStorage
;
36 static dispatch_queue_t log_utility_server_queue
= NULL
;
38 // function declaration
40 dump_state_to_fd(int fd
);
43 accept_client(xpc_connection_t conn
);
46 handle_requests(xpc_object_t req
);
49 check_permission(xpc_connection_t connection
);
52 handle_state_dump(mDNSu32 dump_option
, char *full_file_name
, mDNSu32 name_buffer_len
, int client_fd
,
53 mDNSs32
*time_ms_used
);
56 find_oldest_state_dump(const char *dump_dir
, const char *file_name
, char *full_file_name
, mDNSu32 buffer_len
,
57 char *oldest_file_name
);
60 remove_state_dump_if_too_many(const char *dump_dir
, const char *oldest_file_name
, mDNSs32 dump_file_count
,
64 create_new_state_dump_file(const char *dump_dir
, const char *file_name
, char *full_file_name
, mDNSu32 buffer_len
);
67 handle_state_dump_to_fd(const char *dump_dir
, const char *file_name
, char *full_file_name
, mDNSu32 buffer_len
,
68 mDNSBool if_compress
);
71 compress_state_dump_and_delete(char *input_file
, mDNSu32 buffer_len
);
74 timediff_ms(struct timeval
*t1
, struct timeval
*t2
);
76 // function definition
78 init_log_utility_service(void)
80 xpc_connection_t log_utility_listener
= xpc_connection_create_mach_service(kDNSLogUtilityService
, NULL
, XPC_CONNECTION_MACH_SERVICE_LISTENER
);
81 if (!log_utility_listener
|| xpc_get_type(log_utility_listener
) != XPC_TYPE_CONNECTION
) {
82 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_ERROR
, "Error Creating XPC Listener for Log Utility Server!");
86 log_utility_server_queue
= dispatch_queue_create("com.apple.mDNSResponder.log_utility_server_queue", NULL
);
88 xpc_connection_set_event_handler(log_utility_listener
, ^(xpc_object_t eventmsg
) {
89 xpc_type_t type
= xpc_get_type(eventmsg
);
91 if (type
== XPC_TYPE_CONNECTION
) {
92 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_INFO
, "C%p {action='receives connection'}", eventmsg
);
93 accept_client(eventmsg
);
94 } else if (type
== XPC_TYPE_ERROR
) {
95 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_ERROR
, "C%p {xpc_error=\n" PUB_S
"\n}", eventmsg
,
96 xpc_dictionary_get_string(eventmsg
, XPC_ERROR_KEY_DESCRIPTION
));
98 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_ERROR
, "C%p {error='receives unknown xpc request'}", eventmsg
);
102 xpc_connection_resume(log_utility_listener
);
106 accept_client(xpc_connection_t conn
)
109 xpc_connection_set_target_queue(conn
, log_utility_server_queue
);
110 xpc_connection_set_event_handler(conn
, ^(xpc_object_t req_msg
) {
111 xpc_type_t type
= xpc_get_type(req_msg
);
113 if (type
== XPC_TYPE_DICTIONARY
) {
114 handle_requests(req_msg
);
115 } else { // We hit this case ONLY if Client Terminated Connection OR Crashed
116 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEFAULT
, "C%p {status='client closed the connection'}", conn
);
121 xpc_connection_resume(conn
);
125 handle_requests(xpc_object_t req
)
128 xpc_connection_t remote_conn
= xpc_dictionary_get_remote_connection(req
);
130 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_INFO
, "C%p {action='handling log utility request'}", remote_conn
);
132 // create the dictionary for response purpose
133 xpc_object_t response
= xpc_dictionary_create_reply(req
);
134 if (response
== mDNSNULL
) {
135 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_ERROR
, "C%p {error='cannot create reply response dictionary'}", remote_conn
);
140 ret
= check_permission(remote_conn
);
143 reply_value
= kDNSMsg_Error
;
145 xpc_dictionary_set_string(response
, kDNSErrorDescription
, "Client must be running as root");
146 } else if (ret
== -2) {
147 xpc_dictionary_set_string(response
, kDNSErrorDescription
, "Client is missing the entitlement");
149 } else if (xpc_dictionary_get_uint64(req
, kDNSStateDump
)) {
150 mDNSu32 dump_option
= (mDNSs32
)xpc_dictionary_get_uint64(req
, kDNSStateDump
);
151 char full_file_name
[PATH_MAX
];
154 // We do not dump state in the customer build due to privacy consideration.
155 if (IsAppleInternalBuild()) {
156 int client_fd
= xpc_dictionary_dup_fd(req
, kDNSStateDumpFD
);
157 ret
= handle_state_dump(dump_option
, full_file_name
, sizeof(full_file_name
), client_fd
, &time_used
);
159 reply_value
= kDNSMsg_NoError
;
160 xpc_dictionary_set_int64(response
, kDNSStateDumpTimeUsed
, time_used
);
162 if (dump_option
!= full_state_to_stdout
) {
163 xpc_dictionary_set_string(response
, kDNSDumpFilePath
, full_file_name
);
166 reply_value
= kDNSMsg_Error
;
167 xpc_dictionary_set_string(response
, kDNSErrorDescription
, "State dump fails");
171 reply_value
= kDNSMsg_Error
;
172 xpc_dictionary_set_string(response
, kDNSErrorDescription
, "State dump is only enabled in internal builds");
175 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_ERROR
,
176 "C%p {error='unknown log utility request from client'}", remote_conn
);
177 reply_value
= kDNSMsg_UnknownRequest
;
178 xpc_dictionary_set_string(response
, kDNSErrorDescription
, "unknown log utility request from client");
181 xpc_dictionary_set_uint64(response
, kDNSDaemonReply
, reply_value
);
182 xpc_connection_send_message(remote_conn
, response
);
183 xpc_release(response
);
189 check_permission(xpc_connection_t connection
)
191 uid_t client_euid
= xpc_connection_get_euid(connection
);
192 int client_pid
= xpc_connection_get_pid(connection
);
195 if (client_euid
!= 0) {
196 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEFAULT
,
197 "C%p {client_pid=%d,error='not running as root'}", connection
, client_pid
);
201 if (!IsEntitled(connection
, kDNSLogUtilityService
)){
202 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEFAULT
,
203 "C%p {client_pid=%d,error='Client is missing entitlement'}", connection
, client_pid
);
211 * pointers of full_file_name and time_used are passed into, when function returns, full_file_name will be filled
212 * with the full path to the dumped file, and time_used is filled with time duration(ms) when mDNSResponder is
213 * blocked. if_get_lock indicates if we should lock the kqueue before dumping the state.
216 handle_state_dump(mDNSu32 dump_option
, char *full_file_name
, mDNSu32 name_buffer_len
, int client_fd
,
217 mDNSs32
*time_ms_used
)
221 // record the start time, and lock the kqueue
222 struct timeval time_start
;
223 gettimeofday(&time_start
, mDNSNULL
);
226 if (dump_option
== full_state_to_stdout
) {
227 dump_state_to_fd(client_fd
);
230 // dump_option == full_state || dump_option == full_state_with_compression
231 ret
= handle_state_dump_to_fd(MDSNRESPONDER_STATE_DUMP_DIR
, MDSNRESPONDER_STATE_DUMP_FILE_NAME
,
232 full_file_name
, name_buffer_len
, dump_option
== full_state_with_compression
? mDNStrue
: mDNSfalse
);
235 // unlock the kqueue, record the end time and calculate the duration.
236 KQueueUnlock("State Dump");
237 struct timeval time_end
;
238 gettimeofday(&time_end
, mDNSNULL
);
239 *time_ms_used
= timediff_ms(&time_end
, &time_start
);
244 #define MAX_NUM_DUMP_FILES 5 // controls how many files we are allowed to created for the state dump
246 handle_state_dump_to_fd(const char *dump_dir
, const char *file_name
, char *full_file_name
, mDNSu32 buffer_len
,
247 mDNSBool if_compress
){
249 char oldest_file_name
[PATH_MAX
];
250 mDNSs32 dump_file_count
= 0;
253 dump_file_count
= find_oldest_state_dump(dump_dir
, file_name
, full_file_name
, buffer_len
, oldest_file_name
);
254 require_action(dump_file_count
>= 0, error
,
255 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEBUG
, "find_oldest_state_dump fails"));
257 ret
= remove_state_dump_if_too_many(dump_dir
, oldest_file_name
, dump_file_count
, MAX_NUM_DUMP_FILES
);
258 require_action(ret
== 0, error
,
259 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEBUG
, "remove_state_dump_if_too_many fails"));
261 int fd
= create_new_state_dump_file(dump_dir
, file_name
, full_file_name
, buffer_len
);
262 require_action(fd
>= 0, error
,
263 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEBUG
, "create_new_state_dump_file fails"));
265 dump_state_to_fd(fd
);
266 close(fd
); // create_new_state_dump_file open the file, we have to close it here
269 ret
= compress_state_dump_and_delete(full_file_name
, buffer_len
);
270 require_action(ret
== 0, error
,
271 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEFAULT
, "State Dump: Error happens when trying to compress the state dump, reason: %s", strerror(errno
)));
281 * Scan the directory, find all the files that start with <mDNSResponder state dump file name>. Return the number of
282 * state dump files and the name of the oldest file created.
285 find_oldest_state_dump(const char *dump_dir
, const char *file_name
, char *full_file_name
, mDNSu32 buffer_len
,
286 char *oldest_file_name
)
290 full_file_name
[0] = '\0';
291 full_file_name
[buffer_len
- 1]= '\0';
292 snprintf(full_file_name
, buffer_len
- 1, "%s/%s", dump_dir
, file_name
);
294 DIR *dir_p
= opendir(dump_dir
);
295 if (dir_p
== mDNSNULL
) {
296 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEFAULT
,
297 "State Dump: directory " PUB_S
" cannot be opened, reason: " PUB_S
, dump_dir
, strerror(errno
));
301 // scan every entry under directory, if starts with <mDNSResponder state dump file name>, check its create time.
302 struct dirent
*dir_entry_p
= mDNSNULL
;
303 size_t file_name_len
= strnlen(file_name
, MAXPATHLEN
);
304 mDNSu8 dump_file_count
= 0;
305 struct timespec oldest_time
= {LONG_MAX
, LONG_MAX
};
307 while ((dir_entry_p
= readdir(dir_p
)) != mDNSNULL
) {
308 if (dir_entry_p
->d_namlen
<= file_name_len
)
311 if (strncmp(dir_entry_p
->d_name
, file_name
, file_name_len
) == 0) {
312 struct stat file_state
;
313 snprintf(full_file_name
, buffer_len
- 1, "%s/%s", dump_dir
, dir_entry_p
->d_name
);
315 // use stat to get creation time
316 ret
= stat(full_file_name
, &file_state
);
318 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEFAULT
,
319 "State Dump: error when reading file properties, reason: " PUB_S
, strerror(errno
));
322 // if the file is older than the current record
323 if (oldest_time
.tv_sec
> file_state
.st_birthtimespec
.tv_sec
324 || (oldest_time
.tv_sec
== file_state
.st_birthtimespec
.tv_sec
325 && oldest_time
.tv_sec
> file_state
.st_birthtimespec
.tv_sec
)) {
326 oldest_time
= file_state
.st_birthtimespec
;
327 strncpy(oldest_file_name
, dir_entry_p
->d_name
, MIN(PATH_MAX
- 1, dir_entry_p
->d_namlen
+ 1));
335 return dump_file_count
;
339 remove_state_dump_if_too_many(const char *dump_dir
, const char *oldest_file_name
, mDNSs32 dump_file_count
,
342 char path_file_to_remove
[PATH_MAX
];
343 path_file_to_remove
[PATH_MAX
- 1] = '\0';
344 // If the number of state dump files has reached the maximum value, we delete the oldest one.
345 if (dump_file_count
== max_allowed
) {
346 // construct the full name
347 snprintf(path_file_to_remove
, PATH_MAX
- 1, "%s/%s", dump_dir
, oldest_file_name
);
348 if (remove(path_file_to_remove
) != 0) {
349 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEFAULT
,
350 "State Dump: file " PUB_S
" cannot be deleted, reason: " PUB_S
, path_file_to_remove
, strerror(errno
));
359 * Generate the file name of state dump with current time stamp, and return the FILE pointer, anyone who calls this
360 * function must call fclose() to release the FILE pointer.
363 create_new_state_dump_file(const char *dump_dir
, const char *file_name
, char *full_file_name
, mDNSu32 buffer_len
)
366 struct tm local_time
;
367 char date_time_str
[32];
368 char time_zone_str
[32];
370 gettimeofday(&now
, NULL
);
371 localtime_r(&now
.tv_sec
, &local_time
);
373 // 2008-08-08_20-00-00
374 strftime(date_time_str
, sizeof(date_time_str
), "%F_%H-%M-%S", &local_time
);
376 strftime(time_zone_str
, sizeof(time_zone_str
), "%z", &local_time
);
377 // /private/var/log/mDNSResponder/mDNSResponder_state_dump_2008-08-08_20-00-00-000000+0800.txt
378 snprintf(full_file_name
, buffer_len
, "%s/%s_%s-%06lu%s." STATE_DUMP_PLAIN_SUFFIX
,
379 dump_dir
, file_name
, date_time_str
, (unsigned long)now
.tv_usec
, time_zone_str
);
381 int fd
= open(full_file_name
, O_WRONLY
| O_CREAT
, 0644); // 0644 means * (owning) User: read & write * Group: read * Other: read
383 LogRedact(MDNS_LOG_CATEGORY_DEFAULT
, MDNS_LOG_DEFAULT
,
384 "State Dump: file " PUB_S
" cannot be opened, reason: " PUB_S
, full_file_name
, strerror(errno
));
392 * Compress the state dump from pliantext to tar.bz2, remove the original one, and change the content of input_file to
393 * newly created compressed file if compression succeeds.
396 compress_state_dump_and_delete(char *input_file
, mDNSu32 buffer_len
)
398 struct archive
*a
= mDNSNULL
;
399 mDNSBool archive_opened
= mDNSfalse
;
400 struct archive_entry
*entry
= mDNSNULL
;
403 char output_file
[PATH_MAX
];
404 void *mapped_pointer
= MAP_FAILED
;
409 output_file
[PATH_MAX
- 1] = '\0';
411 a
= archive_write_new();
412 require_action(a
!= mDNSNULL
, exit
, error
= -1;
413 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEBUG
, "archive_write_new fails: " PUB_S
, archive_error_string(a
)));
414 archive_write_add_filter_bzip2(a
);
415 archive_write_set_format_ustar(a
);
417 // remove the .txt suffix, and append .tar.bz2 suffix
418 size_t plain_file_name_len
= strlen(input_file
); // input_file is guaranteed to be '\0'-terminated
419 strncpy(output_file
, input_file
, plain_file_name_len
- sizeof(STATE_DUMP_PLAIN_SUFFIX
));
420 output_file
[plain_file_name_len
- sizeof(STATE_DUMP_PLAIN_SUFFIX
)] = '\0';
421 strncat(output_file
, "." STATE_DUMP_COMPRESSED_SUFFIX
, 1 + sizeof(STATE_DUMP_COMPRESSED_SUFFIX
));
423 // open/create the archive for the given path name
424 ret
= archive_write_open_filename(a
, output_file
);
425 archive_opened
= (ret
== ARCHIVE_OK
);
426 require_action(archive_opened
, exit
, error
= -1;
427 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEBUG
, "archive_write_open_filename fails: " PUB_S
, archive_error_string(a
)));
429 // get the state of file to be compressed
430 stat(input_file
, &st
);
432 // entry is required to create an archive
433 entry
= archive_entry_new();
434 require_action(entry
!= mDNSNULL
, exit
, error
= -1;
435 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEBUG
, "archive_entry_new fails: " PUB_S
, strerror(errno
)));
437 // set the name of file in the compressed file
438 const char *file_name_with_timestamp
= strstr(input_file
, MDSNRESPONDER_STATE_DUMP_FILE_NAME
);
439 if (file_name_with_timestamp
== mDNSNULL
) {
440 file_name_with_timestamp
= MDSNRESPONDER_STATE_DUMP_FILE_NAME
"." STATE_DUMP_PLAIN_SUFFIX
;
443 // copy the original file state to entry
444 archive_entry_copy_stat(entry
, &st
);
445 archive_entry_set_pathname(entry
, file_name_with_timestamp
);
447 // write entry into archive
449 ret
= archive_write_header(a
, entry
);
450 } while (ret
== ARCHIVE_RETRY
);
451 require_action(ret
== ARCHIVE_OK
, exit
, error
= -1;
452 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEBUG
, "archive_write_header fails: " PUB_S
, archive_error_string(a
)));
454 // if the original file has something to compress, use mmap to read its content
455 require_action_quiet(st
.st_size
> 0, exit
, error
= -1);
456 require_action_quiet(st
.st_size
<= UINT32_MAX
, exit
, error
= -1);
458 file_size
= (mDNSu32
)st
.st_size
;
459 fd
= open(input_file
, O_RDONLY
);
460 require_action_quiet(fd
!= -1, exit
, error
= -1);
462 mapped_pointer
= mmap(NULL
, file_size
, PROT_READ
, MAP_FILE
| MAP_PRIVATE
, fd
, 0);
463 require_action(mapped_pointer
!= MAP_FAILED
, exit
, error
= -1;
464 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEBUG
, "mmap fails: " PUB_S
, strerror(errno
)));
466 const ssize_t amount_written
= archive_write_data(a
, mapped_pointer
, file_size
);
467 require_action((amount_written
>= 0) && (((size_t)amount_written
) == file_size
), exit
, error
= -1;
468 LogRedact(MDNS_LOG_CATEGORY_XPC
, MDNS_LOG_DEBUG
, "archive_write_data fails: amount_written(%ld) != (%u)",
469 (long)amount_written
, file_size
));
474 if (mapped_pointer
!= MAP_FAILED
) {
475 munmap(mapped_pointer
, file_size
); // undo mmap
478 close(fd
); // undo open
480 if (entry
!= mDNSNULL
) {
481 archive_entry_free(entry
); // undo archive_entry_new
483 if (archive_opened
) {
484 archive_write_close(a
); // undo archive_write_open_filename
487 archive_write_free(a
); // undo archive_write_new
491 strncpy(input_file
, output_file
, buffer_len
);
493 input_file
[0] = '\0';
499 * Return the time difference(ms) between two struct timeval.
501 #define US_PER_S 1000000
502 #define MS_PER_S 1000
504 timediff_ms(struct timeval
*t1
, struct timeval
*t2
)
508 if (t1
->tv_sec
< t2
->tv_sec
|| (t1
->tv_sec
== t2
->tv_sec
&& t1
->tv_usec
< t2
->tv_usec
)) {
509 return -timediff_ms(t2
, t1
);
512 sec
= (int)(t1
->tv_sec
- t2
->tv_sec
);
513 if (t1
->tv_usec
>= t2
->tv_usec
) {
514 usec
= t1
->tv_usec
- t2
->tv_usec
;
516 usec
= t1
->tv_usec
+ US_PER_S
- t2
->tv_usec
;
520 ms
+= usec
/ MS_PER_S
;