Не удается подключить к Linux "абстрактный" сокет unix

Я пытаюсь использовать UNIX-сокеты для связи между потоками. Программа предназначена только для запуска в Linux. Чтобы избежать создания файлов сокетов, я хотел использовать "абстрактные" сокеты, как описано в unix(7).

Тем не менее, я не могу подключиться к этим разъемам. Все работает, если я использую сокеты "pathname".

Вот код (я не процитировал какую-либо обработку ошибок, но это сделано): поток #1:

int log_socket = socket(AF_LOCAL, SOCK_STREAM, 0);
struct sockaddr_un logaddr;
socklen_t sun_len = sizeof(struct sockaddr_un);
logaddr.sun_family = AF_UNIX;
logaddr.sun_path[0] = 0;
strcpy(logaddr.sun_path+1, "futurama");
bind(log_socket, &logaddr, sun_len);
listen(log_socket, 5);
accept(log_socket, &logaddr, &sun_len);
... // send - receive

Тема #2:

struct sockaddr_un tolog;
int sock = socket(AF_LOCAL, SOCK_STREAM, 0);
tolog.sun_family = AF_UNIX;
tolog.sun_path[0] = 0;
strcpy(tolog.sun_path+1, "futurama");
connect(sock, (struct sockaddr*)&tolog, sizeof(struct sockaddr_un));

Если все, что я делаю в приведенном выше коде, это изменение sun_path, чтобы не иметь ведущего \0, все работает отлично.

прямой выход:

t1: socket(PF_FILE, SOCK_STREAM, 0)         = 0
t1: bind(0, {sa_family=AF_FILE, path=@"futurama"}, 110)
t1: listen(0, 5)
t2: socket(PF_FILE, SOCK_STREAM, 0) = 1
t2: connect(1, {sa_family=AF_FILE, path=@"futurama"}, 110 <unfinished ...>
t2: <... connect resumed> )     = -1 ECONNREFUSED (Connection refused)
t1: accept(0,  <unfinished ...>

Я знаю, что соединение происходит до принятия, это не проблема (я пытался убедиться, что accept() вызывается перед connect(), тот же результат. Кроме того, все в порядке, если сокет в любом случае "pathname").

4 ответа

Решение

Пока я писал этот вопрос и перечитывал man-страницу unix(7), эта формулировка привлекла мое внимание:

абстрактный адрес сокета отличается тем, что sun_path[0] является нулевым байтом ('\0'). Все остальные байты в sun_path определяют "имя" сокета

Так что, если я bzero'ed sun_path до того, как вписать в него свое имя, все начнет работать. Я подумал, что это не обязательно прямо. Кроме того, как справедливо указали @davmac и @StoneThrow, число этих "оставшихся байтов" можно уменьшить, указав только достаточную длину структуры адреса сокета, чтобы покрыть байты, которые вы хотите считать своим адресом. Один из способов сделать это - использовать SUN_LEN макрос, однако, первый байт sun_path должен быть установлен в!0, как SUN_LEN использования strlen,

разработка

Если sun_path[0] равно \0, ядро ​​использует всю оставшуюся часть sun_path в качестве имени сокета, независимо от того, завершено ли оно \0 или нет, поэтому весь этот остаток считается. В моем исходном коде я обнулял первый байт, а затем strcpy() имя сокета в sun_path в позиции 1. Какой бы ни был тарабарщина, которая была в sun_path, когда структура была размещена (особенно вероятно, что она содержит бред, поскольку он размещен в стеке) и был включен в длину структуры сокета (как передано системным вызовам), считался именем сокета и отличался в bind() и connect().

ИМХО, strace должен исправить способ отображения абстрактных имен сокетов и отобразить все sun_path от 1 до любой длины структуры, если sun_path[0] является 0

Ключом к созданию сокетов в работе с абстрактным пространством имен является предоставление надлежащей длины командам "связать" и "соединить". Чтобы избежать установки '\0' в конце адреса в sockaddr_un, его следует скопировать с помощью strncpy или аналогичного.

Это уже объясняется в ответе Павла, поэтому я просто приведу пример.

Сервер:

int main(int argc, char** argv)
{
  //to remove warning for unused variables.
  int dummy = argc;
  dummy = (int)argv;

  int fdServer = 0;
  int fdClient = 0;
  int iErr     = 0;
  int n = 0;
  socklen_t addr_len = 0;
  char buff[1024];
  char resp[1024];

  const char* const pcSocketName = "/tmp/test";

  struct sockaddr_un serv_addr; 

  //set the structure with 'x' instead of 0 so that we're able 
  //to see the full socket name by 'cat /proc/net/unix'
  //you may try playing with addr_len and see the actual name 
  //reported in /proc/net/unix
  memset(&serv_addr, 'x', sizeof(serv_addr));
  serv_addr.sun_family = AF_UNIX;
  serv_addr.sun_path[0] = '\0';
  //sizeof(pcSocketName) returns the size of 'char*' this is why I use strlen
  strncpy(serv_addr.sun_path+1, pcSocketName, strlen(pcSocketName));

  fdServer = socket(PF_UNIX, SOCK_STREAM, 0);
  if(-1 == fdServer) {
    printf("socket() failed: [%d][%s]\n", errno, strerror(errno));
    return(-1);
  }

  iErr = bind(fdServer, (struct sockaddr*)&serv_addr, offsetof(struct sockaddr_un, sun_path) + 1/*\0*/ + strlen(pcSocketName));

  if(0 != iErr) {
    printf("bind() failed: [%d][%s]\n", errno, strerror(errno));
    return(-1);
  }

  iErr = listen(fdServer, 1);
  if(0 != iErr) {
    printf("listen() failed: [%d][%s]\n", errno, strerror(errno));
    return(-1);
  }

  addr_len = sizeof(pcSocketName);
  while(1) {
    fdClient = accept(fdServer, (struct sockaddr*) &serv_addr, &addr_len);
    if(0 >= fdClient) {
      printf("accept() failed: [%d][%s]\n", errno, strerror(errno));
      return(-1);
    }

    memset(resp, 0, sizeof(resp));
    memset(buff, 0, sizeof(buff));
    n = recv(fdClient, buff, sizeof(buff), 0);
    if(0 > n) {
      printf("recv() failed: [%d][%s]\n", errno, strerror(errno));
      return(-1);
    }

    printf("[client]: %s\n", buff);
    sprintf(resp, "echo >> %s", buff);
    n = send(fdClient, resp, sizeof(resp), 0);
    if(0 > n) {
      printf("send() failed: [%d][%s]\n", errno, strerror(errno));
      return(-1);
    }
    printf("[server]: %s\n", resp);
  }

  close(fdServer);

  return(0);
}

Клиент:

int main(int argc, char** argv) {
  //to remove warning for unused variables.
  int dummy = argc;
  dummy = (int)argv;

  int fdClient = 0;
  struct sockaddr_un serv_addr;
  int iErr     = 0;
  const char* const pcSocketName = "/tmp/test";

  char buff[1024];

  memset(&serv_addr, 0, sizeof(serv_addr));
  serv_addr.sun_family = AF_UNIX;
  serv_addr.sun_path[0] = '\0';
  strncpy(serv_addr.sun_path+1, pcSocketName, strlen(pcSocketName));

  fdClient = socket(PF_UNIX, SOCK_STREAM, 0);
  if(-1 == fdClient) {
    printf("socket() failed: [%d][%s]\n", errno, strerror(errno));
    return(-1);
  }

  iErr = connect(fdClient, (struct sockaddr*) &serv_addr, offsetof(struct sockaddr_un, sun_path) + 1/*\0*/ + strlen(pcSocketName));
  if(0 != iErr) {
    printf("connect() failed: [%d][%s]\n", errno, strerror(errno));
    return(-1);
  }

  memset(buff, 0, sizeof(buff));
  sprintf(buff, "Hello from client!");


  printf("[client]: %s\n", buff);
  iErr = send(fdClient, buff, sizeof(buff), 0);
  if(0 > iErr){
    printf("write() failed: [%d][%s]\n", errno, strerror(errno));
    return(-1);
  }

  iErr = recv(fdClient, buff, sizeof(buff), 0);
  if(0 > iErr){
    printf("read() failed: [%d][%s]\n", errno, strerror(errno));
    return(-1);
  }

  printf("[server]: %s\n", buff);

  return(0);
}

В моем случае замена strncpy() на snprintf() и увеличение размера копии до UNIX_PATH_MAX решили проблему.

оригинал

strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(SOCKET_PATH));

модифицированный

snprintf(server_addr.sun_path, UNIX_PATH_MAX, SOCKET_PATH);

Надеюсь, поможет.

Не уверен, как определяется SOCKET_PATH, но, если я подозреваю, что это строковый литерал, то sizeof(SOCKET_PATH) будет иметь размер char*, обычно 4 или 8 байтов.

Другие вопросы по тегам