Не удается подключить к 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 байтов.