Unix sockets: Difference between revisions

From iPhone Development Wiki
No edit summary
 
(8 intermediate revisions by the same user not shown)
Line 1: Line 1:
Unix sockets are a low-level way of achieving [[IPC|inter-process communication]] on Unix systems, including iOS.
Unix sockets are a low-level way of achieving [[IPC|inter-process communication]] on Unix systems. It isn't really used in iOS, as most other forms of IPC are built on top of [https://developer.apple.com/library/content/documentation/Darwin/Conceptual/KernelProgramming/Mach/Mach.html Mach ports]. However iOS is a unix system so it also includes Unix sockets, which can be useful in certain scenarios.
All of the other types of IPC in iOS, e.g. mach ports and the like, are built on top of Unix sockets.


For more/better reading material, check out beej's [http://beej.us/guide/bgipc/html/multi/unixsock.html Unix socket guide] or his excellent [http://beej.us/guide/bgnet/html/single/bgnet.html network programming guide].
For more/better reading material, check out beej's [http://beej.us/guide/bgipc/html/multi/unixsock.html Unix socket guide] or his excellent [http://beej.us/guide/bgnet/html/single/bgnet.html network programming guide].
Line 19: Line 18:
* Takes more work to properly set up
* Takes more work to properly set up


If this is your first time doing IPC, it's probably better to use another method such as [[CPDistributedMessagingCenter]] or [[LightMessaging]], which are more straightforward.
If this is your first time doing IPC, it's probably better to use another method such as [[RocketBootstrap#CPDistributedMessagingCenter_Example|CPDistributedMessagingCenter]] or [[LightMessaging]], which are more straightforward.
Unix sockets should only really be used if you've used another IPC method and have found it lacking or restrictive.
Unix sockets should only really be used if you've used another IPC method and have found it lacking or restrictive.


Line 164: Line 163:
First off, the entire server logic is under one thread. That means only one client can connect at a time. If another client tries to connect, it will hang until the first one disconnects. This can be fixed by putting various parts of the server logic under a <code>dispatch_async</code>, or pthread.
First off, the entire server logic is under one thread. That means only one client can connect at a time. If another client tries to connect, it will hang until the first one disconnects. This can be fixed by putting various parts of the server logic under a <code>dispatch_async</code>, or pthread.


The other issue is that it doesn't handle messages that exceed 4096 characters. To mitigate this, it's best to decide on some sort of "separator" string between messages for your protocol (<code>\0</code> is a good choice), and then just do the processing only once you hit that separator.
The other issue is that it doesn't handle messages that exceed 4096 characters. To mitigate this, it's best to decide on some sort of "separator" string between messages for your protocol (<code>\n</code> works well with netcat), and then just do the processing only once you hit that separator.


== Bypassing sandbox with inet sockets ==
== Bypassing sandbox with inet sockets ==


In iOS 10, daemons/processes with the "seatbelt" entitlement (e.g. mediaserverd) don't allow you to create unix domain sockets anymore. :( This is also a problem with the higher-level libraries that use [[RocketBootstrap]].
In iOS 10, daemons/processes with the "seatbelt" entitlement (e.g. mediaserverd) don't allow you to create <i>or connect to</i> unix domain sockets anymore. :( This is also appears to be an issue with creating Mach ports. Sandboxed processes can still connect to external Mach ports, thanks to [[RocketBootstrap]].


Luckily for us, there is still a workaround: local internet sockets. The key difference with these is that they're not <i>actual</i> internet sockets. The way these will be set up is that they can only be accessed from localhost, so essentially it poses the same security risk as any other IPC method.
Luckily for us, there is still a workaround: local internet sockets. The key difference with these is that they're not <i>actual</i> internet sockets. The way these will be set up is that they can only be accessed from localhost, so essentially it poses the same security risk as any other IPC method.
Line 187: Line 186:
local.sin_family = AF_INET;
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
local.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
local.sin_port = htons(80);
local.sin_port = htons(port);
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
</source>
</source>
Line 206: Line 205:
=== Choosing a port ===
=== Choosing a port ===


The only real pitfall of this approach is having to pick a port. You'll have to pick one between 1024-65535, and is not in [https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers Wikipedia's list of TCP port numbers] and also not in the list of reserved ports below. Naturally, there is a very real chance that the port you pick will conflict with another service, so choose wisely. Typically something above 10000 and below 50000 will be unlikely to conflict with anything.
The huge pitfall of this approach is having to pick a port. You'll have to pick one between 1024-65535, and is not in [https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers Wikipedia's list of TCP port numbers] and also not in the list of reserved ports below. Naturally, there is a very real chance that the port you pick will conflict with another service, so choose wisely. Typically something above 10000 and below 50000 will be unlikely to conflict with anything.


==== List of reserved ports on jailbroken iOS ====
==== List of reserved ports on jailbroken iOS ====


* 787: [http://cycript.org cycript] (I might actually be wrong about this, I just grepped the source for AF_INET and found a match, [https://git.saurik.com/cycript.git/commitdiff/0abb2a2f9d5b1c7fbe7a43619bab5291d7e55f87 this commit] is also telling)
* 787: [http://cycript.org cycript] (I might actually be wrong about this, I just grepped the source for AF_INET and found a match, [https://git.saurik.com/cycript.git/commitdiff/0abb2a2f9d5b1c7fbe7a43619bab5291d7e55f87 this commit] is also telling)
* 43333: [https://github.com/Nepeta/AudioSnapshotServer AudioSnapshotServer]
* 27724: [https://eqe.fm/about EQE]
* 27724: [https://eqe.fm/about EQE]

Latest revision as of 01:53, 19 December 2020

Unix sockets are a low-level way of achieving inter-process communication on Unix systems. It isn't really used in iOS, as most other forms of IPC are built on top of Mach ports. However iOS is a unix system so it also includes Unix sockets, which can be useful in certain scenarios.

For more/better reading material, check out beej's Unix socket guide or his excellent network programming guide.

Comparison to other IPC methods

Pros

  • No dependencies
  • Extremely portable
  • Less overhead
  • "Easier" to bypass sandbox restrictions (more on this later)

Cons

  • API is in C instead of Objective-C
  • Not "the Apple way"
  • Takes more work to properly set up

If this is your first time doing IPC, it's probably better to use another method such as CPDistributedMessagingCenter or LightMessaging, which are more straightforward. Unix sockets should only really be used if you've used another IPC method and have found it lacking or restrictive.

Rudimentary setup

Server

#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

void server_start()
{
    // you can change this to whatever you want
    // but it's good practice to put in /var/run
    const char *socket_path = "/var/run/your_server.socket";

    // setup socket

    struct sockaddr_un local;
    strcpy(local.sun_path, socket_path);
    unlink(local.sun_path);
    local.sun_family = AF_UNIX;

    int listenfd = socket(AF_UNIX, SOCK_STREAM, 0);
    printf("listenfd: %d\n", listenfd);

    // start the server

    int r = -1;
    while(r != 0) {
        r = bind(listenfd, (struct sockaddr*)&local, sizeof(local));
        printf("bind: %d\n", r);
        usleep(200 * 1000);
    }

    int one = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));

    // start listening for new connections

    r = -1;
    while(r != 0) {
        r = listen(listenfd, 20);
        printf("listen: %d\n", r);
        usleep(200 * 1000);
    }

    // wait for new connection, and then process it

    int connfd = -1;
    while(true) {
        if(connfd == -1) {
            // wait for new connection
            connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
            printf("new connfd: %d\n", connfd);
        }

        // process incoming data

        char buffer[4096];
        int len = recv(connfd, buffer, sizeof(buffer), 0);
        if(len == 0) {
            printf("connfd %d disconnected!\n", connfd);
            connfd = -1;
            continue;
        } else {
            printf("connfd %d recieved data: %s", connfd, buffer);
            // send some data back (optional)
            const char *response = "got it!\n";
            send(connfd, response, strlen(response) + 1, 0); 
        }
    }
}

You should run server_start() in a background thread, otherwise it will block.

To test, run the command socat - UNIX-CONNECT:/var/run/your_server.socket which will give you a REPL.

Client

#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>

#include <stdio.h>
#include <string.h>

void client_start()
{
    // setup socket
 
    struct sockaddr_un remote;
    remote.sun_family = AF_UNIX;
    strcpy(remote.sun_path, "/var/run/your_server.socket");

    int connfd = socket(AF_UNIX, SOCK_STREAM, 0);
    printf("connfd: %d\n", connfd);

    // connect to server

    int r = -1;
    while(r != 0) {
        r = connect(connfd, (struct sockaddr *)&remote, sizeof(remote));
        printf("connect: %d\n", r);
    }

    // send and receive messages

    const char *message = "why hello there!\n";
    send(connfd, message, strlen(message), 0);

    char buffer[4096];

    int len = recv(connfd, buffer, sizeof(buffer), 0);
    if(len != 0) {
        printf("recieved response: %s\n", buffer);
    }

    close(connfd);
}

Pitfalls of this example

There are a few issues with this example.

First off, the entire server logic is under one thread. That means only one client can connect at a time. If another client tries to connect, it will hang until the first one disconnects. This can be fixed by putting various parts of the server logic under a dispatch_async, or pthread.

The other issue is that it doesn't handle messages that exceed 4096 characters. To mitigate this, it's best to decide on some sort of "separator" string between messages for your protocol (\n works well with netcat), and then just do the processing only once you hit that separator.

Bypassing sandbox with inet sockets

In iOS 10, daemons/processes with the "seatbelt" entitlement (e.g. mediaserverd) don't allow you to create or connect to unix domain sockets anymore. :( This is also appears to be an issue with creating Mach ports. Sandboxed processes can still connect to external Mach ports, thanks to RocketBootstrap.

Luckily for us, there is still a workaround: local internet sockets. The key difference with these is that they're not actual internet sockets. The way these will be set up is that they can only be accessed from localhost, so essentially it poses the same security risk as any other IPC method.

But seriously, don't use this method unless you absolutely have to. Using regular unix domain sockets is much, much better than this approach, since you can tie the socket to a file descriptor instead of a port. There are unlimited potential file descriptors, and only a finite number of available ports. Don't reserve a port unless you absolutely have to.

Code

Basically, all you'd have to change is the socket setup:

Server

// setup socket

int port = 80; // CHANGE THIS!!!!
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
local.sin_port = htons(port);
int listenfd = socket(AF_INET, SOCK_STREAM, 0);

Client

// setup socket

int port = 80; // CHANGE THIS!!!!!
struct sockaddr_in remote;
remote.sin_family = AF_INET;
remote.sin_port = htons(port);
inet_aton("127.0.0.1", &remote.sin_addr);
int connfd = socket(AF_INET, SOCK_STREAM, 0);

Choosing a port

The huge pitfall of this approach is having to pick a port. You'll have to pick one between 1024-65535, and is not in Wikipedia's list of TCP port numbers and also not in the list of reserved ports below. Naturally, there is a very real chance that the port you pick will conflict with another service, so choose wisely. Typically something above 10000 and below 50000 will be unlikely to conflict with anything.

List of reserved ports on jailbroken iOS