Distributed Objects communication with a launchd "on-demand" daemon
My dear fellow Cocoa programmers, I am trying to create a launchd daemon that is started "on-demand" by a client call to a TCP port number and then communicate with the client via Distributed Objects. I am having problems combining the "on-demand" launchd feature with the particular choice of Distributed Objects for communication. I have no problem with using Distributed Objects with a daemon that is always running or communicating with with "on-demand" launchd daemons using techniques other than Distributed Objects. (1) So my first question, is it indeed possible to use Distributed Objects with "on-demand" launchd daemons? Here I want to note that this launchd-dev thread implies it is: http://old.nabble.com/Diagnosing-launchd-daemon-failure-to-launch-td20344390... ... and so does this blog entry by Chris Hanson: http://chanson.livejournal.com/179229.html Here the code that I am testing with: // TestDaemonLaunchd.plist <dict> <key>ServiceIPC</key> <true/> <key>Sockets</key> <dict> <key>Socket</key> <dict> <key>SockServiceName</key> <string>8081</string> </dict> </dict> <key>OnDemand</key> <true/> <key>Label</key> <string>com.example.TestDaemon</string> <key>ProgramArguments</key> <array> <string>/path/to/TestDaemon</string> </array> </dict> // TestDaemon.m int main (int argc, const char * argv[]) { NSAutoreleasePool *pool = [NSAutoreleasePool new]; // NOTE: Getting the TCP socket file descriptor (fd) from launchd. launch_data_t checkinRequest = launch_data_new_string(LAUNCH_KEY_CHECKIN); launch_data_t checkinResponse = launch_msg(checkinRequest); launch_data_t socketsDict = launch_data_dict_lookup(checkinResponse, LAUNCH_JOBKEY_SOCKETS); launch_data_t fdArray = launch_data_dict_lookup(socketsDict, "Socket"); launch_data_t fdData = launch_data_array_get_index(fdArray, 0); int fd = launch_data_get_fd(fdData); launch_data_free(checkinResponse); launch_data_free(checkinRequest); NSSocketPort *receivePort = [[NSSocketPort alloc] initWithProtocolFamily:PF_INET socketType:SOCK_STREAM protocol:IPPROTO_TCP socket:fd]; NSConnection *connection = [NSConnection connectionWithReceivePort:receivePort sendPort:nil]; [receivePort release]; id testObject = [NSObject new]; [connection setRootObject:testObject]; [[NSRunLoop currentRunLoop] run]; [testObject release]; [pool release]; return 0; } // TestClient.m int main (int argc, const char * argv[]) { NSAutoreleasePool *pool = [NSAutoreleasePool new]; NSSocketPort *sendPort = [[NSSocketPort alloc] initRemoteWithTCPPort:8081 host:@"127.0.0.1"]; NSConnection *connection = [NSConnection connectionWithReceivePort:nil sendPort:sendPort]; [connection setRequestTimeout:10.0]; [connection setReplyTimeout:10.0]; [sendPort release]; // NOTE: Here the -rootProxy method always raises an NSPortTimeoutException when used with an "on-demand" launchd daemon. id proxy = [connection rootProxy]; [pool release]; return 0; } (2) When I use launchctl to the TestDaemonLaunchd.plist above, I always get an NSPortTimeoutException in TestClient.m when calling [connection rootProxy]. If I remove the Sockets dictionary from the TestDaemonLaunchd.plist (so launchd does not interfere with normal communication from the client to the dameon) and set "OnDemand" to false (so the daemon runs all the time), there is no problem. [connection rootProxy] return a valid NSDistantObject for the testObject set in TestDaemon.m. Why am I getting NSPortTimeoutException when I try running it "OnDmeand"? I suspect that answer has to do with the fact I am not a setting up a kqueue and then getting socket file descriptors subsequent kevent as is down in Apple's SampleD example: http://developer.apple.com/mac/library/samplecode/SampleD/listing3.html ... /* * Initialize a new kernel event. This will trigger when * a connection occurs on our listener socket. * */ for (i = 0; i < launch_data_array_get_count(listening_fd_array); i++) { launch_data_t this_listening_fd = launch_data_array_get_index(listening_fd_array, i); EV_SET(&kev_init, launch_data_get_fd(this_listening_fd), EVFILT_READ, EV_ADD, 0, 0, NULL); if (kevent(kq, &kev_init, 1, NULL, 0, NULL) == -1) { asl_log(asl, log_msg, ASL_LEVEL_DEBUG, "kevent(): %m"); retval = EXIT_FAILURE; goto done; } } launch_data_free(checkin_response); for (;;) { FILE *the_stream; int filedesc, gai_r; char nodename[1024]; char servname[1024]; /* * * Get the next event from the kernel event queue. * */ if ((filedesc = kevent(kq, NULL, 0, &kev_listener, 1, NULL)) == -1) { asl_log(asl, log_msg, ASL_LEVEL_ERR, "kevent(): %m"); retval = EXIT_FAILURE; goto done; } else if (filedesc == 0) { retval = EXIT_SUCCESS; goto done; } /* * * Accept an incoming connection. * */ if ((filedesc = accept(kev_listener.ident, (struct sockaddr *)&ss, &slen)) == -1) { asl_log(asl, log_msg, ASL_LEVEL_ERR, "accept(): %m"); continue; /* this isn't fatal */ } ... Is that sort of code required to make Distributed Objects work with "OnDemand" launchd daemons? If so, what I am supposed to do with the additional sock file descriptors (filedesc)? I can certainly create NSSocketPorts and then NSConnections with them, but isn't Distributed Objects supposed to do that automatically? Is it even possible to do it for Distributed Objects manually? Thanks in advance for any help, - Frank Rizzo
I believe that I now have solved my problem using a launchd "on-demand" daemon with Distributed Objects (DO). I was only using TCP ports here for initial development because some of the launchd "on-demand" sample code does and because I had been unable to create an NSMachPort with the mach_port_t value I received from launchd. However, while writing a response to a replay to my message (which I had originally posted on the Cocoa-dev list), I caught a trivial programming error that fixed my NSMachPort creation problem (they were coming back nil) and also allowed DO to work with the launchd daemon "on-demand." I have not yet tried the UNIX domain socket approach to client<->daemon communication DO yet, and since I believe that Mach ports are preferred for DO I do not intend to at this point. Here is the mach services version of my code (which works with the launchd daemon "on-demand"): // TestDaemonLaunchd.plist <dict> <key>ServiceIPC</key> <true/> <key>MachServices</key> <dict> <key>com.example.TestDaemon</key> </true> </dict> <key>OnDemand</key> <true/> <key>Label</key> <string>com.example.TestDaemon</string> <key>ProgramArguments</key> <array> <string>/path/to/TestDaemon</string> </array> </dict> // TestDaemon.m int main (int argc, const char * argv[]) { NSAutoreleasePool *pool = [NSAutoreleasePool new]; // NOTE: Getting the Mach port descriptor (mp) from launchd. launch_data_t checkinRequest = launch_data_new_string(LAUNCH_KEY_CHECKIN); launch_data_t checkinResponse = launch_msg(checkinRequest); launch_data_t machServicesDict = launch_data_dict_lookup(checkinResponse, LAUNCH_JOBKEY_MACHSERVICES); launch_data_t machPort = launch_data_dict_lookup(machServicesDict, "com.example.TestDaemon"); mach_port_t mp = launch_data_get_machport(machPort); launch_data_free(checkinResponse); launch_data_free(checkinRequest); NSMachPort *receivePort = [[NSMachPort alloc] initWithMachPort:mp]; NSConnection *connection = [NSConnection connectionWithReceivePort:receivePort sendPort:nil]; [receivePort release]; id testObject = [NSObject new]; [connection setRootObject:testObject]; [[NSRunLoop currentRunLoop] run]; [testObject release]; [pool release]; return 0; } // TestClient.m int main (int argc, const char * argv[]) { NSAutoreleasePool *pool = [NSAutoreleasePool new]; NSConnection *connection = [NSConnection connectionWithRegisteredName:@"com. example.TestDaemon" host:nil]; [connection setRequestTimeout:10.0]; [connection setReplyTimeout:10.0]; // NOTE: No NSPortTimeoutException is raised when used with an "on-demand" launchd daemon after switching to Mach ports for DO communication. id proxy = [connection rootProxy]; [pool release]; return 0; }
On Dec 17, 2009, at 10:47 AM, Frank Rizzo wrote:
I believe that I now have solved my problem using a launchd "on-demand" daemon with Distributed Objects (DO).
I was only using TCP ports here for initial development because some of the launchd "on-demand" sample code does and because I had been unable to create an NSMachPort with the mach_port_t value I received from launchd. However, while writing a response to a replay to my message (which I had originally posted on the Cocoa-dev list), I caught a trivial programming error that fixed my NSMachPort creation problem (they were coming back nil) and also allowed DO to work with the launchd daemon "on-demand." I have not yet tried the UNIX domain socket approach to client<->daemon communication DO yet, and since I believe that Mach ports are preferred for DO I do not intend to at this point.
Here is the mach services version of my code (which works with the launchd daemon "on-demand"):
// TestDaemonLaunchd.plist
Please name your plist in the reverse-DNS style. In your case, com.example.TestDaemon. We intend to begin enforcing this convention sometime down the line.
<dict> <key>ServiceIPC</key> <true/> <key>MachServices</key> <dict> <key>com.example.TestDaemon</key> </true> </dict> <key>OnDemand</key> <true/> <key>Label</key> <string>com.example.TestDaemon</string> <key>ProgramArguments</key> <array> <string>/path/to/TestDaemon</string> </array> </dict>
The ServiceIPC and OnDemand keys aren't needed anymore. They were only relevant on Tiger.
// TestDaemon.m
int main (int argc, const char * argv[]) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
// NOTE: Getting the Mach port descriptor (mp) from launchd. launch_data_t checkinRequest = launch_data_new_string(LAUNCH_KEY_CHECKIN); launch_data_t checkinResponse = launch_msg(checkinRequest); launch_data_t machServicesDict = launch_data_dict_lookup(checkinResponse, LAUNCH_JOBKEY_MACHSERVICES); launch_data_t machPort = launch_data_dict_lookup(machServicesDict, "com.example.TestDaemon");
mach_port_t mp = launch_data_get_machport(machPort);
launch_data_free(checkinResponse); launch_data_free(checkinRequest);
NSMachPort *receivePort = [[NSMachPort alloc] initWithMachPort:mp]; NSConnection *connection = [NSConnection connectionWithReceivePort:receivePort sendPort:nil]; [receivePort release];
id testObject = [NSObject new]; [connection setRootObject:testObject];
[[NSRunLoop currentRunLoop] run];
[testObject release]; [pool release]; return 0; }
Just use bootstrap_check_in() to obtain your Mach port. The launch_msg() API is pretty crufty and only needed for obtaining socket file descriptors.
// TestClient.m int main (int argc, const char * argv[]) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSConnection *connection = [NSConnection connectionWithRegisteredName:@"com. example.TestDaemon" host:nil];
[connection setRequestTimeout:10.0]; [connection setReplyTimeout:10.0];
// NOTE: No NSPortTimeoutException is raised when used with an "on-demand" launchd daemon after switching to Mach ports for DO communication. id proxy = [connection rootProxy]; [pool release]; return 0; }
On Dec 18, 2009, at 8:21 PM, Frank Rizzo wrote:
I have no intention of using DO for "cross-security domain communications."
Since this service is a daemon and anyone can look up the service, you are crossing security domains. -- Damien Sorresso BSD Engineering Apple Inc.
At 00:09 -0500 17/12/09, Frank Rizzo wrote:
I am trying to create a launchd daemon that is started "on-demand" by a client call to a TCP port number and then communicate with the client via Distributed Objects.
I'd recommend that you think long and hard before taking this approach. There are two issues: o DO over TCP -- DO over TCP has serious practical issues. It looks like you've switched to Mach messaging anyway, so I won't go into the details. o DO across security domains -- DO is not a great solution for cross-security domain communications. So if your plan is to run your DO code as a daemon and make its service available to non-privileged users, you should think again. There are numerous reasons why DO is problematic security-wise. The most obvious is that DO makes heavy use of Cocoa archiving (to serialise objects and send them over the wire), and Cocoa archives are not recommended across security domains. <http://developer.apple.com/iPhone/library/documentation/Security/Conceptual/SecureCodingGuide/Articles/ValidatingInput.html#//apple_ref/doc/uid/TP40007246> Beyond that, DO is a huge and complex piece of code with lots of flexibility on lots of axes, and adding all of that code to your attack surface [1] is a bad idea. S+E -- Quinn "The Eskimo!" <http://www.apple.com/developer/> Apple Developer Relations, Developer Technical Support, Core OS/Hardware [1] <http://en.wikipedia.org/wiki/Attack_surface>
participants (3)
-
Damien Sorresso
-
Frank Rizzo
-
Quinn