Сбой универсальной одноадресной рассылки Netlink от ядра к пользователю (-111)

(Линукс 4.4)

Я пытаюсь заставить модуль ядра отправлять информацию пользовательскому процессу через Generic Netlink. Похоже, что сообщение не было успешно получено пользовательским процессом — функция nlmsg_unicast возвращает значение -111.

Вот что я знаю:

  • Модуль ядра успешно регистрирует семейство Generic Netlink — он печатает сообщение в системном журнале с указанием (автоматически сгенерированного) идентификатора семейства (который всегда равен 26).
  • Пользовательский процесс успешно обнаруживает идентификатор семейства (26).
  • Пользовательский процесс отправляет своего рода команду «Я жив» модулю ядра, который успешно регистрирует (автоматически выбранный) идентификатор порта пользовательского процесса — я знаю из сообщений, напечатанных как пользовательским процессом, так и модулем ядра, что разрешен правильный идентификатор порта.
  • Впоследствии ядро ​​при возникновении события пытается отправить сообщение на разрешенный идентификатор порта через настроенное семейство Generic Netlink.
  • Пользовательский процесс никогда не получает сообщение (он никогда не входит в функцию обратного вызова; фактически, я не думаю, что mnl_socket_recvfrom когда-либо вернется). В модуле ядра функция nlmsg_unicast возвращает значение -111.

Я использую libmnl в пользовательском процессе (как вы могли догадаться по моему намеку на mnl_socket_recvfrom).

uname -a
Linux yaron-VirtualBox 4.4.0-57-generic #78-Ubuntu SMP Пт, 9 декабря, 23:50:32 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

Вот, по сути, мой код отправки в ядре:

    struct sk_buff *msg;
    struct sock *socket;
    struct netlink_kernel_cfg nlCfg = {
        .groups = 1,
        .flags = 0,
        .input = NULL,
        .cb_mutex = NULL,
        .bind = NULL,
        .unbind = NULL,
        .compare = NULL,
    };
    void *msg_head;
    int retval;
    struct net init_net;

            /* Open a socket */
            socket = netlink_kernel_create(&init_net, NETLINK_GENERIC, &nlCfg);
            if (socket == NULL) goto CmdFail;

            /* Allocate space */
            msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
            if (msg == NULL) goto CmdFail;

            /* Generate message header
             * arguments of genlmsg_put: 
             *    struct sk_buff *, 
             *    int portID,  <-- this is sender portID
             *    int netlinkSeqNum,
             *    struct genl_family *, 
             *    int flags, 
             *    u8 command_idx         */
            msg_head = genlmsg_put(msg, 0, ++netlinkSeqNum, &genlFamily, 0, MYFAMILY_CMD_MYMSG);
            if (msg_head == NULL) goto CmdFail;

            /* Add a MYFAMILY_ATTR_MYCMD attribute (command to be sent) */
            retval = nla_put_string(msg, MYFAMILY_ATTR_MYMSG, "Temporary message");
            if (retval != 0) goto CmdFail;

            /* Finalize the message */
            genlmsg_end(msg, msg_head);  /* void inline function - no return value */

            /* Send the message */
            retval = nlmsg_unicast(socket, msg, userNetlinkPortID);
            printk("nlmsg_unicast returned %d\n", retval);
            if (retval != 0) goto CmdFail;

            netlink_kernel_release(socket);

            return;


CmdFail:
    printk(KERN_ALERT "*** Failed to send command !\n");
    netlink_kernel_release(socket);
    return;

Вот, по сути, мой код получения в пользовательском процессе:

char bufferHdr[getpagesize()];
struct nlmsghdr *nlHeader;
struct genlmsghdr *nlHeaderExtraHdr;
int numBytes, seq, ret_val;

// Set up the header.
// Function mnl_nlmsg_put_header will zero out a length of bufferHdr sufficient to hold a Netlink header,
// and initialize the nlmsg_len field in that space to the size of a header.
// It returns a pointer to bufferHdr.
if ( (nlHeader = mnl_nlmsg_put_header(bufferHdr)) != (struct nlmsghdr *) bufferHdr ) {
    perror("mnl_nlmsg_put_header failed");
    exit(EXIT_FAILURE);
}
nlHeader->nlmsg_type = genetlinkFamilyID;

// Function mnl_nlmsg_put_extra_header extends the header, to allow for these extra fields.
if ( (nlHeaderExtraHdr = (struct genlmsghdr *) mnl_nlmsg_put_extra_header(nlHeader, sizeof(struct genlmsghdr))) != (struct genlmsghdr *) (bufferHdr + sizeof(struct nlmsghdr)) ) {
    perror("mnl_nlmsg_put_extra_header failed");
    exit(EXIT_FAILURE);
}
// No command to set

// No attributes to set

// Wait for a message, and process it
while (1) {
    numBytes = mnl_socket_recvfrom(nlSocket, bufferHdr, sizeof(bufferHdr));
    if (numBytes == -1) {
        perror("mnl_socket_recvfrom returned error");
        break;
    }
    // Callback run queue handler - use it to call getMsgCallback
    std::cout << "received a msg, handling it" << std::endl;
    ret_val = mnl_cb_run(bufferHdr, numBytes, seq, portid, getMsgCallback, NULL);
    if (ret_val == -1) {
        //perror("mnl_cb_run failed");
        break;
    } else if (ret_val == 0)
        break;

}

return ret_val;


ДОБАВЛЕНИЕ: Еще немного просмотрев исходный код ядра (на elixir.free-electrons.com), я предполагаю, что мое сообщение даже не доходит до пользовательского процесса; предложения по отладке будут оценены.

Вот что я вижу: nlmsg_unicast вызывает netlink_unicast, который, в свою очередь, вызывает netlink_getsockbyportid, что выглядит так:

static struct sock *netlink_getsockbyportid(struct sock *ssk, u32 portid)
{
    struct sock *sock;
    struct netlink_sock *nlk;

    sock = netlink_lookup(sock_net(ssk), ssk->sk_protocol, portid);
    if (!sock)
        return ERR_PTR(-ECONNREFUSED);

    /* Don't bother queuing skb if kernel socket has no input function */
    nlk = nlk_sk(sock);
    if (sock->sk_state == NETLINK_CONNECTED &&
        nlk->dst_portid != nlk_sk(ssk)->portid) {
        sock_put(sock);
        return ERR_PTR(-ECONNREFUSED);
    }
    return sock;
}

Я предполагаю, что здесь срабатывает одно из двух условий для игры на плоскодонке и возврата -ECONREFUSED.

Любые предложения о том, как я могу отладить, верно ли какое-либо из этих условий? Не похоже, чтобы я мог вызвать netlink_lookup или nlk_sk непосредственно из кода моего модуля — я думаю, что символы не отображаются — ни их подфункции — множество символов похоронено в af_netlink.h и af_netlink.c, и я предполагаю, что символы недоступны при построении вашего внешнего модуля, по крайней мере, обычным способом. (Не похоже, что af_netlink.h доступен как часть дистрибутива.)


person Yaron Shragai    schedule 24.05.2017    source источник