Como usar o netlink socket para se comunicar com um módulo do kernel?

Eu estou tentando escrever um módulo de kernel do Linux que se comunica com o processo do usuário usando o netlink. Eu estou usando netlink porque o programa do usuário que eu quero comunicar se comunica usando apenas sockets e eu não posso mudar isso para adicionar ioctl() ou qualquer coisa.

O problema é que eu não consigo descobrir como fazer isso. Eu pesquisei, mas todos os exemplos que encontrei são antigos como este e não são mais válidos para as versões atuais do kernel. Eu também olhei para esta questão SO, mas o exemplo aqui usa libnl para operações de soquete, mas eu quero ficar com funções de soquete padrão (definidas por sys/socket.h ). Então, pode um plz guiar-me aqui para algum tutorial ou guia ou algo que possa me ajudar a entender a interface e o uso do netlink. Eu apreciaria muito um exemplo funcional, nada sofisticado, apenas um exemplo muito básico de como estabelecer uma conexão de um soquete no programa do usuário para um soquete no kernel e então enviar dados do processo do usuário para o kernel e receber de volta do kernel.

Por favor, não me diga para ver o código do kernel. Eu já estou fazendo isso, mas vai demorar muito tempo e eu não tenho muito de sobra.

Update: Após muita tentativa e erro eu tenho o seguinte código que envia mensagem do programa do usuário para o kernel, mas a mensagem do kernel para o programa do usuário, ou seja, usando netlink_unicast() não está funcionando. Não só não está funcionando, a chamada trava os sistemas e então eu tenho que reiniciar a máquina. Pode alguém dar uma olhada e me dizer o que estou fazendo de errado? A chamada netlink_unicast() é comentada no código a seguir. Deve ser descomentado para o kernel a mensagem do programa do usuário.

Programa do usuário

 #include  #include  #define NETLINK_USER 31 #define MAX_PAYLOAD 1024 /* maximum payload size*/ struct sockaddr_nl src_addr, dest_addr; struct nlmsghdr *nlh = NULL; struct iovec iov; int sock_fd; struct msghdr msg; void main() { sock_fd=socket(PF_NETLINK, SOCK_RAW, NETLINK_USER); if(sock_fd<0) return -1; memset(&src_addr, 0, sizeof(src_addr)); src_addr.nl_family = AF_NETLINK; src_addr.nl_pid = getpid(); /* self pid */ /* interested in group 1<nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); nlh->nlmsg_pid = getpid(); nlh->nlmsg_flags = 0; strcpy(NLMSG_DATA(nlh), "Hello"); iov.iov_base = (void *)nlh; iov.iov_len = nlh->nlmsg_len; msg.msg_name = (void *)&dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1; printf("Sending message to kernel\n"); sendmsg(sock_fd,&msg,0); printf("Waiting for message from kernel\n"); /* Read message from kernel */ recvmsg(sock_fd, &msg, 0); printf(" Received message payload: %s\n", NLMSG_DATA(nlh)); close(sock_fd); } 

Código do Kernel

 #include  #include  #include  #include  #include  #include  #include  #include  #include  #define NETLINK_USER 31 struct sock *nl_sk = NULL; static void hello_nl_recv_msg(struct sk_buff *skb) { struct nlmsghdr *nlh; int pid; printk(KERN_INFO "Entering: %s\n", __FUNCTION__); nlh=(struct nlmsghdr*)skb->data; printk(KERN_INFO "Netlink received msg payload: %s\n", (char*)NLMSG_DATA(nlh)); pid = nlh->nlmsg_pid; /*pid of sending process */ NETLINK_CB(skb).dst_group = 0; /* not in mcast group */ NETLINK_CB(skb).pid = 0; /* from kernel */ //NETLINK_CB(skb).groups = 0; /* not in mcast group */ //NETLINK_CB(skb).dst_pid = pid; printk("About to send msg bak:\n"); //netlink_unicast(nl_sk,skb,pid,MSG_DONTWAIT); } static int __init hello_init(void) { printk("Entering: %s\n",__FUNCTION__); nl_sk=netlink_kernel_create(&init_net, NETLINK_USER, 0, hello_nl_recv_msg, NULL, THIS_MODULE); if(!nl_sk) { printk(KERN_ALERT "Error creating socket.\n"); return -10; } return 0; } static void __exit hello_exit(void) { printk(KERN_INFO "exiting hello module\n"); netlink_kernel_release(nl_sk); } module_init(hello_init); module_exit(hello_exit); 

Depois de ler a fonte do kernel, finalmente consegui fazer com que os sockets netlink funcionassem para mim. Abaixo está um exemplo dos fundamentos do soquete Netlink, isto é, abrindo um soquete netlink, lendo e escrevendo nele e fechando-o.

Módulo Kernel

 #include  #include  #include  #include  #define NETLINK_USER 31 struct sock *nl_sk = NULL; static void hello_nl_recv_msg(struct sk_buff *skb) { struct nlmsghdr *nlh; int pid; struct sk_buff *skb_out; int msg_size; char *msg = "Hello from kernel"; int res; printk(KERN_INFO "Entering: %s\n", __FUNCTION__); msg_size = strlen(msg); nlh = (struct nlmsghdr *)skb->data; printk(KERN_INFO "Netlink received msg payload:%s\n", (char *)nlmsg_data(nlh)); pid = nlh->nlmsg_pid; /*pid of sending process */ skb_out = nlmsg_new(msg_size, 0); if (!skb_out) { printk(KERN_ERR "Failed to allocate new skb\n"); return; } nlh = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, msg_size, 0); NETLINK_CB(skb_out).dst_group = 0; /* not in mcast group */ strncpy(nlmsg_data(nlh), msg, msg_size); res = nlmsg_unicast(nl_sk, skb_out, pid); if (res < 0) printk(KERN_INFO "Error while sending bak to user\n"); } static int __init hello_init(void) { printk("Entering: %s\n", __FUNCTION__); //nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, 0, hello_nl_recv_msg, NULL, THIS_MODULE); struct netlink_kernel_cfg cfg = { .input = hello_nl_recv_msg, }; nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg); if (!nl_sk) { printk(KERN_ALERT "Error creating socket.\n"); return -10; } return 0; } static void __exit hello_exit(void) { printk(KERN_INFO "exiting hello module\n"); netlink_kernel_release(nl_sk); } module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); 

Programa do usuário

 #include  #include  #include  #include  #include  #include  #define NETLINK_USER 31 #define MAX_PAYLOAD 1024 /* maximum payload size*/ struct sockaddr_nl src_addr, dest_addr; struct nlmsghdr *nlh = NULL; struct iovec iov; int sock_fd; struct msghdr msg; int main() { sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER); if (sock_fd < 0) return -1; memset(&src_addr, 0, sizeof(src_addr)); src_addr.nl_family = AF_NETLINK; src_addr.nl_pid = getpid(); /* self pid */ bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr)); memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.nl_family = AF_NETLINK; dest_addr.nl_pid = 0; /* For Linux Kernel */ dest_addr.nl_groups = 0; /* unicast */ nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD)); memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD)); nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); nlh->nlmsg_pid = getpid(); nlh->nlmsg_flags = 0; strcpy(NLMSG_DATA(nlh), "Hello"); iov.iov_base = (void *)nlh; iov.iov_len = nlh->nlmsg_len; msg.msg_name = (void *)&dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1; printf("Sending message to kernel\n"); sendmsg(sock_fd, &msg, 0); printf("Waiting for message from kernel\n"); /* Read message from kernel */ recvmsg(sock_fd, &msg, 0); printf("Received message payload: %s\n", NLMSG_DATA(nlh)); close(sock_fd); } 

Tópico relacionado sobre a constante mágica NETLINK_USER 31 : Posso ter mais de 32 sockets netlink no kernelspace?

Apenas no caso de alguém não saber como compilar, google “como compilar e carregar o módulo do kernel”

consulte http://www.cyberciti.biz/tips/compiling-linux-kernel-module.html

Pegue o código fonte do kernel para o qual você irá compilar o módulo contra http://kernel.org

Ou simplesmente atualize seus headers se você estiver executando o kernel pretendido

 # apt-get install kernel-headers-$(uname -r) 

Crie um makefile, por exemplo

 obj-m = hello.o KVERSION = $(shell uname -r) all: make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules clean: make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean 

Faça e você vai ter muitos arquivos. * .ko é o que você vai carregar no seu kernel, rodar

 # insmod hello.ko 

Se você usar o lsmod para verificar todos os módulos carregados, você encontrará o seu, provavelmente verá:

 hello 12575 0 

Em nosso caso, compile e execute o código do usuário:

 gcc hello.c -o hello.o ./hello.o 

Se tudo estiver OK, você receberá a seguinte mensagem usando o código do binW:

 Sending message to kernel Waiting for message from kernel Received message payload: Hello from kernel 

Por fim, remova o módulo usando:

 # rmmod hello 

Isso funciona para mim com o kernel 3.2. Para o kernel 3.6 e acima, ele precisa de uma pequena mudança na function netlink_kernel_create .

  struct netlink_kernel_cfg cfg = { .groups = 1, .input = hello_nl_recv_msg, }; printk("Entering: %s\n", __FUNCTION__); nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg); 

você precisa include o seguinte arquivo de header no código client_side:

 #include  #include  #include