Как отправить и получить структуру через netlink? - PullRequest
0 голосов
/ 23 марта 2020

Я пытаюсь отправить структуру из пространства пользователя в мой модуль в пространстве ядра, используя netlink, моя структура в пространстве пользователя:

struct test{
  unsigned int length;
  char name[MAX_NAME_LENGTH];
};

, а в пространстве ядра:

struct test{
  __u32 length;
  char name[MAX_NAME_LENGTH];
};

где MAX_NAME_LENGTH - это макрос, определенный как равный 50.

В пользовательском пространстве есть функция main, которая отправляет мою структуру ядру со следующим кодом :

int main(){

    struct iovec iov[2];
    int sock_fd;
    struct sockaddr_nl src_add;
    struct sockaddr_nl dest_add;
    struct nlmsghdr * nl_hdr = NULL;
    struct msghdr msg; 

    struct test message;
    memset(&message, 0, sizeof(struct test));
    message.length = 18;
    strcpy(message.name, "Just a test\0");

    sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);

    if (sock_fd < 0){
        printf("Netlink socket creation failed\n");
        return -1;
    }

    memset(&src_add, 0, sizeof(src_add));
    src_add.nl_family = AF_NETLINK;
    src_add.nl_pid    = getpid();

    memset(&dest_add, 0, sizeof(dest_add));
    dest_add.nl_family = AF_NETLINK;
    dest_add.nl_pid    = 0; // Send to linux kernel
    dest_add.nl_groups = 0; // Unicast

    bind(sock_fd,(struct sockaddr *)&src_add,sizeof(src_add));

    nl_hdr = (struct nlmsghdr *) malloc(NLMSG_SPACE(sizeof(struct test)));

    memset(nl_hdr, 0, NLMSG_SPACE(sizeof (struct test)));

    nl_hdr->nlmsg_len   = NLMSG_SPACE(sizeof(struct test));
    nl_hdr->nlmsg_pid   = getpid();
    nl_hdr->nlmsg_flags = 0;

    iov[0].iov_base = (void *)nl_hdr;
    iov[0].iov_len  = nl_hdr->nlmsg_len;
    iov[1].iov_base = &message;
    iov[1].iov_len  = sizeof(struct test);

    memset(&msg,0, sizeof(msg));
    msg.msg_name    = (void *)&dest_add;
    msg.msg_namelen = sizeof(dest_add);
    msg.msg_iov     = &iov[0];
    msg.msg_iovlen  = 2;

    sendmsg(sock_fd,&msg,0);

    close(sock_fd);

    return 0;
}

И на стороне ядра я зарегистрировал функцию callback, которая будет вызываться каждый раз при получении сообщения, это функция обратного вызова:

static void callback(struct sk_buff *skb){


    struct nlmsghdr *nl_hdr;
    struct test * msg_rcv;

    nl_hdr  = (struct nlmsghdr*)skb->data;
    msg_rcv = (struct test*) nlmsg_data(nl_hdr); 

    printk(KERN_INFO "Priting the length and name in the struct:%u, %s\n",msg_rcv->length, msg_rcv->name);

}

Когда Я запускаю эти коды и вижу вывод dmesg. Я получаю следующее сообщение: Priting the length and name in the struct:0,, так почему же поля структуры, заполненные на стороне пользователя, не отправляются ядру?

Кстати, NETLINK_USER определяется как 31.

1 Ответ

1 голос
/ 25 марта 2020

НЕ ДЕЛАЙТЕ ЭТОГО. В вашем коде есть ошибки при проектировании.

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

1. Делайте что хотите

Вы «хотите» отправить пакет, состоящий из заголовка netlink, за которым следует структура. Другими словами, это:

+-----------------+-------------+
| struct nlmsghdr | struct test |
| (16 bytes)      | (54 bytes)  |
+-----------------+-------------+

Проблема в том, что вы не говорите свою любовь c. Согласно вашему коду iove c пакет выглядит следующим образом:

+-----------------+--------------+-------------+
| struct nlmsghdr | struct test  | struct test |
| (16 bytes)      | (54 bytes)   | (54 bytes)  |
| (data)          | (all zeroes) | (data)      |
+-----------------+--------------+-------------+

Эта строка:

iov[0].iov_len  = nl_hdr->nlmsg_len;

Должна быть такой:

iov[0].iov_len  = NLMSG_HDRLEN;

Потому что ваш первый слот iove c - это просто заголовок Netlink; не весь пакет.

2. Почему то, что вы хотите - плохо

C имеет метод, называемый "заполнение структуры данных". Не пропустите эту лекцию; Я бы сказал, что любой, кто имеет дело с языком C, ДОЛЖЕН читать его как можно скорее: http://www.catb.org/esr/structure-packing/

Суть в том, что компиляторам C разрешено вводить мусор между Члены любой структуры. Таким образом, когда вы объявляете это:

struct test {
    unsigned int length;
    char name[MAX_NAME_LENGTH];
};

Компилятору технически разрешено преобразовывать это во время реализации в нечто вроде

struct test {
    unsigned int length;
    unsigned char garbage[4];
    char name[MAX_NAME_LENGTH];
};

Видите проблему? Если ваш модуль ядра и ваш пользовательский клиентский клиент были сгенерированы разными компиляторами или одним и тем же компилятором, но с немного разными флагами, или даже немного разными версиями одного и того же компилятора , структуры могут отличаться, и ядро ​​будет получать мусор, независимо от того, насколько корректным выглядит ваш код.

В вашем случае проблема усугубляется тем, что вы определяете test.length как unsigned int в пользовательском пространстве, но по какой-то причине вы меняете его на __u32 в пространстве ядра. Ваш код проблематичен c даже до заполнения структуры; если ваше пользовательское пространство определяет целые числа basi c как 64-битные, ошибка также неизбежно сработает.

И есть еще одна проблема: «Кстати, NETLINK_USER определен как 31», говорит мне, что вы следуете учебникам или образцы кода, давно устаревшие или написанные людьми, которые не знают, что делают. Вы знаете, откуда этот 31? Это идентификатор вашей «семьи Netlink». Они определяют его как 31, потому что это максимально возможное значение, которое он может иметь (0-31), и, следовательно, это наиболее маловероятное, чтобы столкнуться с другими семействами Netlink, определенными ядром. (Поскольку они пронумерованы монотонно .) Но большинство небрежных пользователей Netlink следуют учебным пособиям, и поэтому большинство их семейств Netlink идентифицируют себя как 31. Следовательно, ваш модуль ядра не сможет сосуществовать с любым из них. netlink_kernel_create() выгонит вас из-за того, что 31 уже заявлен.

И вам может быть интересно, "ну дерьмо. Доступно всего 32 слота, 23 из них уже заняты ядром и есть неизвестное, но, вероятно, большое количество дополнительных людей, желающих зарегистрировать разные семейства Netlink. Что мне делать?! "

3. Правильный путь

Это 2020. Мы больше не используем Netlink . Мы лучше используем Netlink: Generi c Netlink.

Generi c Netlink использует строки и динамические числа c в качестве идентификаторов семейства и заставляет вас использовать платформу Netlink по умолчанию для атрибутов. (Последнее поощряет вас сериализовать и десериализовывать структуры переносимым способом, что является реальным решением вашей первоначальной проблемы.)

Этот код должен быть виден как клиенту вашего пользовательского пространства, так и модулю ядра:

#define SAMPLE_FAMILY "Sample Family"

enum sample_operations {
    SO_TEST, /* from your "struct test" */
    /* List more here for different request types. */
};

enum sample_attribute_ids {
    /* Numbering must start from 1 */
    SAI_LENGTH = 1, /* From your test.length */
    SAI_NAME, /* From your test.name */

    /* This is a special one; don't list any more after this. */
    SAI_COUNT,
#define SAI_MAX (SAI_COUNT - 1)
};

Это модуль ядра:

#include <linux/module.h>
#include <linux/version.h>
#include <net/genetlink.h>
#include "../include/protocol.h"

/*
* A "policy" is a bunch of rules. The kernel will validate the request's fields
* match these data types (and other defined constraints) for us.
*/
struct nla_policy const sample_policy[SAI_COUNT] = {
    [SAI_LENGTH] = { .type = NLA_U32 },
    [SAI_NAME] = { .type = NLA_STRING },
};

/*
* This is the function the kernel calls whenever the client sends SO_TEST
* requests.
*/
static int handle_test_operation(struct sk_buff *skb, struct genl_info *info)
{
    if (!info->attrs[SAI_LENGTH]) {
        pr_err("Invalid request: Missing length attribute.\n");
        return -EINVAL;
    }
    if (!info->attrs[SAI_NAME]) {
        pr_err("Invalid request: Missing name attribute.\n");
        return -EINVAL;
    }

    pr_info("Printing the length and name: %u, '%s'\n",
            nla_get_u32(info->attrs[SAI_LENGTH]),
            (unsigned char *)nla_data(info->attrs[SAI_NAME]));
    return 0;
}

static const struct genl_ops ops[] = {
    /*
    * This is what tells the kernel to use the function above whenever
    * userspace sends SO_TEST requests.
    * Add more array entries if you define more sample_operations.
    */
    {
        .cmd = SO_TEST,
        .doit = handle_test_operation,
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 2, 0)
        /* Before kernel 5.2, each op had its own policy. */
        .policy = sample_policy,
#endif
    },
};

/* Descriptor of our Generic Netlink family */
static struct genl_family sample_family = {
    .name = SAMPLE_FAMILY,
    .version = 1,
    .maxattr = SAI_MAX,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 2, 0)
    /* Since kernel 5.2, the policy is family-wide. */
    .policy = sample_policy,
#endif
    .module = THIS_MODULE,
    .ops = ops,
    .n_ops = ARRAY_SIZE(ops),
};

/* Called by the kernel when the kernel module is inserted */
static int test_init(void)
{
    return genl_register_family(&sample_family);
}

/* Called by the kernel when the kernel module is removed */
static void test_exit(void)
{
    genl_unregister_family(&sample_family);
}

module_init(test_init);
module_exit(test_exit);

А вот клиент клиентского пространства (вам нужно установить libnl-genl-3 - sudo apt install libnl-genl-3-dev в Debian / Ubuntu):

#include <errno.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include "../include/protocol.h"

static struct nl_sock *sk;
static int genl_family;

static void prepare_socket(void)
{
    sk = nl_socket_alloc();
    genl_connect(sk);
    genl_family = genl_ctrl_resolve(sk, SAMPLE_FAMILY);
}

static struct nl_msg *prepare_message(void)
{
    struct nl_msg *msg;

    msg = nlmsg_alloc();
    genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, genl_family, 0, 0, SO_TEST, 1);
    /*
    * The nla_put* functions ensure that your data will be stored in a
    * portable way.
    */
    nla_put_u32(msg, SAI_LENGTH, 18);
    nla_put_string(msg, SAI_NAME, "Just a test");

    return msg;
}

int main(int argc, char **argv)
{
    struct nl_msg *msg;

    prepare_socket();
    msg = prepare_message();

    nl_send_auto(sk, msg); /* Send message */

    nlmsg_free(msg);
    nl_socket_free(sk);
    return 0;
}

Этот код должен работать, начиная с ядра 4.10. (Я тестировал его в 4.15.) До этого API ядра несколько отличался.

Я оставил карманную версию своей тестовой среды (с make-файлами и правильной обработкой ошибок и всем прочим) в моем Dropbox , так что вы можете легко запустить его.

...