C Netlink多播内核组

C Netlink多播内核组,c,linux-kernel,kernel-module,multicast,netlink,C,Linux Kernel,Kernel Module,Multicast,Netlink,我试图实现的任务实际上非常简单(将字符串“TEST”多播到userland守护进程),但内核模块不编译。它将停止并显示错误: passing argument 4 of ‘genlmsg_multicast_allns’ makes integer from pointer without a cast [enabled by default] 但它不应该只是我定义的多播组吗 以下是“澄清”的代码: #包括 #包括 #包括 #包括 #包括 #包括 #包括 结构sock*nl_sk=NULL;

我试图实现的任务实际上非常简单(将字符串“TEST”多播到userland守护进程),但内核模块不编译。它将停止并显示错误:

passing argument 4 of ‘genlmsg_multicast_allns’ makes integer from pointer without a cast [enabled by default]
但它不应该只是我定义的多播组吗

以下是“澄清”的代码:

#包括
#包括
#包括
#包括
#包括
#包括
#包括
结构sock*nl_sk=NULL;
静态void守护进程(void){
结构sk_buff*skb;
空*味精头;
无符号字符*msg;
结构genl_族my_genl_族={
.id=GENL\u id\u GENERATE,
.hdrsize=0,
.name=“姓氏”,
.version=1,
.maxattr=5
};
结构genl\u多播组my\u mc\u组={
.name=“mc_集团”,
};
msg=“测试”;
skb=genlmsg_new(NLMSG_GOODSIZE,GFP_内核);
msg_head=genlmsg_put(skb,0,0和my_genl_family,0,21);
nla_put(skb,0,sizeof(msg),msg);
genlmsg_端(skb、msg_端);
genlmsg_多播(和my_genl_系列、skb、0、my_mc_组、GFP_内核);
}
静态int\uu init hello\u init(void)
{
printk(“输入:%s\n”,函数);
printk(KERN_INFO“使用套接字调用主函数”\n);
结构netlink\u内核\u cfg={
.组=1,
.flags=NL_CFG_F_NONROOT_RECV,
};
nl_sk=netlink_kernel_create(&init_net,netlink_GENERIC,&cfg);
守护进程();
返回0;
}
静态无效\uuu退出你好\u退出(无效)
{
printk(KERN_INFO“退出hello模块”\n);
netlink内核发布(nl sk);
}
模块_init(hello_init);
模块退出(你好退出);
模块许可证(“GPL”);
谢谢你的帮助

编辑

这是客户端代码:

#include <netlink/netlink.h>
#include <netlink/socket.h>
#include <netlink/msg.h>
#include <netlink/genl/genl.h>
#include <linux/genetlink.h>

/*
 * This function will be called for each valid netlink message received
 * in nl_recvmsgs_default()
 */
static int my_func(struct nl_msg *msg, void *arg)
{
        //struct nl_msg *nlmsg = nlmsg_alloc_size(GENL_HDRLEN+nla_total_size(sizeof(msg))+36);

        printf("Test\n");

        return 0;
}

int main(){
        struct nl_sock *sk;

        int gr_id;

        /* Allocate a new socket */
        sk = nl_socket_alloc();

        /*
         * Notifications do not use sequence numbers, disable sequence number
         * checking.
         */
        nl_socket_disable_seq_check(sk);

        /*
         * Define a callback function, which will be called for each notification
         * received
         */
        nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, my_func, NULL);
       /* Connect to netlink generic protocol */
        nl_connect(sk, NETLINK_GENERIC);

        gr_id = genl_family_get_id("family_name");

        /* Subscribe to link notifications group */
        nl_socket_add_memberships(sk, gr_id, 0);

        /*
         * Start receiving messages. The function nl_recvmsgs_default() will block
         * until one or more netlink messages (notification) are received which
         * will be passed on to my_func().
        */
        while (1){
                nl_recvmsgs_default(sk);
        }

        return 0;
}
#包括
#包括
#包括
#包括
#包括
/*
*将为接收到的每个有效netlink消息调用此函数
*在nl_recvmsgs_default()中
*/
静态int my_func(结构nl_msg*msg,void*arg)
{
//结构nl_msg*nlmsg=nlmsg_alloc_size(GENL_HDRLEN+nla_total_size(sizeof(msg))+36);
printf(“测试”);
返回0;
}
int main(){
结构nl_sock*sk;
智力障碍;
/*分配一个新的套接字*/
sk=nl_socket_alloc();
/*
*通知不使用序列号,请禁用序列号
*检查。
*/
nl_插座_禁用_顺序检查(sk);
/*
*定义一个回调函数,该函数将为每个通知调用
*收到
*/
nl_socket_modify_cb(sk,nl_cb_VALID,nl_cb_CUSTOM,my_func,NULL);
/*连接到netlink通用协议*/
nl_连接(sk、NETLINK_通用);
gr_id=genl_family_get_id(“family_name”);
/*订阅链接通知组*/
nl_套接字_添加_成员身份(sk,gr_id,0);
/*
*开始接收消息。函数nl_recvmsgs_default()将阻止
*直到收到一条或多条netlink消息(通知),其中
*将传递到my_func()。
*/
而(1){
nl_recvmsgs_默认值(sk);
}
返回0;
}

这不是对netlink问题的直接回答,而是另一种解决方案。请参阅以上有关netlink限制的评论

UDP套接字可以在Linux上用于在用户模式进程(如守护进程)和内核模式组件(如可加载模块)之间进行通信

守护程序代码my_udp.c:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>

static int rcv_sock;
static int snd_sock;
static struct sockaddr_in rcv_addr_in;
static struct sockaddr_in snd_addr_in;
static pthread_t rcv_thread;

static void *rcv_thread_fn(void *data);

int my_udp_init(void)
{
    int sendlen, receivelen;
    int received = 0;

    if ((rcv_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
            perror("socket");
            return -1;
    }

    if ((snd_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
            perror("socket");
            return -1;
    }

    memset(&rcv_addr_in, 0, sizeof(rcv_addr_in));
    rcv_addr_in.sin_family = AF_INET;
    rcv_addr_in.sin_addr.s_addr = inet_addr("127.0.0.1");
    rcv_addr_in.sin_port = htons(MY_IN_PORT);

    receivelen = sizeof(rcv_addr_in);
    if (bind(rcv_sock, (struct sockaddr *) &rcv_addr_in, receivelen) < 0) {
            perror("bind");
            return -1;
    }

    memset(&snd_addr_in, 0, sizeof(snd_addr_in));
    snd_addr_in.sin_family = AF_INET;
    snd_addr_in.sin_addr.s_addr = inet_addr("127.0.0.1");
    snd_addr_in.sin_port = htons(MY_OUT_PORT);

    if (pthread_create(&rcv_thread, NULL, rcv_thread_fn, (void *)"rcv_thread")) {
            return -ENOMEM;
    }

    return 0;
}

void my_udp_cleanup(void)
{
    pthread_join(rcv_thread, NULL);
    close(rcv_sock);
    close(snd_sock);
}

int my_snd_msg(const char *buf, int size)
{
    sendto(snd_sock, buf, size, 0, (struct sockaddr *)&snd_addr_in, sizeof(snd_addr_in));
    return 0;
}

int my_rcv_msg(char *buf, int size)
{
    int cnt = 0;
    if ((cnt = recv(rcv_sock, buf, size, MSG_DONTWAIT)) < 0) {
            if (errno == EAGAIN) {
                    /* This is ok in the non-blocking case. */
                    sleep(1);
                    return 0;
            } else {
                    perror("recv");
                    return -1;
            }
    }
    return cnt;
}

static void *rcv_thread_fn(void *data)
{
    char buffer[64];
    int cnt;

    while (!g_stop) {
            cnt = my_rcv_msg(buffer, 63);
            if (cnt > 0) {
                    printf("message: %s\n", buffer);
            }
    }

    pthread_exit(0);
}
#include <linux/module.h>
#include <linux/init.h>
#include <linux/in.h>
#include <net/sock.h>
#include <linux/skbuff.h>
#include <linux/delay.h>
#include <linux/inet.h>
#include <linux/kthread.h>

static struct work_struct rcv_worker;
static struct socket *in_socket = NULL;
static struct socket *out_socket = NULL;
static struct workqueue_struct *wq = NULL;
static struct task_struct *notify_thread = NULL;

void rcv_work_queue(struct work_struct *data)
{
        int len;
        printk(KERN_INFO "%s: *******\n", __func__);

        while ((len = skb_queue_len(&in_socket->sk->sk_receive_queue)) > 0) {
                struct sk_buff *skb = NULL;

                skb = skb_dequeue(&in_socket->sk->sk_receive_queue);
                printk("message len: %i message: %s\n", skb->len - 8, skb->data+8);
                kfree_skb(skb);
        }
}

static void cb_data(struct sock *sk, int bytes)
{
        printk(KERN_INFO "%s: *******\n", __func__);
        queue_work(wq, &rcv_worker);
}

void send_notification(char *text)
{
        struct sockaddr_in to_addr;
        struct msghdr msg;
        struct iovec iov;
        mm_segment_t oldfs;
        int len = 0;

        if (out_socket->sk == NULL) {
                printk(KERN_ERR "%s: socket skbuff is null\n", __func__);
                return;
        }

        iov.iov_base = text;
        len = strlen(text);
        iov.iov_len = len;

        memset(&to_addr, 0, sizeof(to_addr));
        to_addr.sin_family = AF_INET;
        to_addr.sin_addr.s_addr = in_aton("127.0.0.1");
        to_addr.sin_port = htons(MY_OUT_PORT);

        msg.msg_flags = 0;
        msg.msg_name = &to_addr;
        msg.msg_namelen = sizeof(struct sockaddr_in);
        msg.msg_control = NULL;
        msg.msg_controllen = 0;
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_control = NULL;

        oldfs = get_fs();
        set_fs(KERNEL_DS);
        sock_sendmsg(out_socket, &msg, len);
        set_fs(oldfs);
}

static int k_udp_notify_thread(void *data)
{
        int i = 0;
        while (!kthread_should_stop()) {
                char buf[64];

                sprintf(buf, "test from kernel%d\n", i++);
                send_notification(buf);
                msleep(1000);
        }
        return 0;
}

int k_udp_init(void)
{
        struct sockaddr_in addr_out;
        struct sockaddr_in addr_in;
        int rc = 0;
        printk("%s\n", __func__);
        if (in_socket) {
                printk(KERN_INFO "%s: socket already set up\n", __func__);
                return 0;
        }

        if (sock_create(PF_INET, SOCK_DGRAM, IPPROTO_UDP, &in_socket) < 0) {
                printk( KERN_ERR "%s: failed to create socket\n", __func__);
                return -EIO;
        }
        addr_in.sin_family = AF_INET;
        addr_in.sin_addr.s_addr = in_aton("127.0.0.1");
        addr_in.sin_port = htons( (unsigned short)MY_IN_PORT);
        rc = in_socket->ops->bind(in_socket, (struct sockaddr *)&addr_in, sizeof(addr_in));
        if (rc) {
                printk(KERN_ERR "%s: failed to bind\n", __func__);
                sock_release(in_socket);
                in_socket = NULL;
                return -EIO;
        }
        in_socket->sk->sk_data_ready = cb_data;

        if (sock_create(PF_INET, SOCK_DGRAM, IPPROTO_UDP, &out_socket) < 0) {
                printk( KERN_ERR "%s: failed to create socket\n", __func__);
                sock_release(in_socket);
                in_socket = NULL;
                return -EIO;
        }
        addr_out.sin_family = AF_INET;
        addr_out.sin_addr.s_addr = in_aton("127.0.0.1");
        addr_out.sin_port = htons( (unsigned short)MY_OUT_PORT);
        rc = out_socket->ops->connect(out_socket, (struct sockaddr *)&addr_out, sizeof(addr_out), 0);
        if (rc) {
                printk(KERN_ERR "%s: failed to connect\n", __func__);
                sock_release(in_socket);
                in_socket = NULL;
                sock_release(out_socket);
                out_socket = NULL;
                return -EIO;
        }

        notify_thread = kthread_create(k_udp_notify_thread, NULL, "k_notify_thread");
        if (notify_thread) {
                printk(KERN_INFO "%s: notify thread created\n", __func__);
                wake_up_process(notify_thread);
        } else {
                printk(KERN_ERR "%s: failed to create notify thread\n", __func__);
        }

        INIT_WORK(&rcv_worker, rcv_work_queue);
        wq = create_singlethread_workqueue("k_rcv_wq");
        if (!wq) {
                return -ENOMEM;
        }

        printk(KERN_INFO "%s: success\n", __func__);

        return 0;
}

void k_udp_cleanup(void)
{
        /* Should we check that the thread is still valid (hasn't exited)? */
        if (notify_thread) {
                kthread_stop(notify_thread);
                notify_thread = NULL;
        }

        if (in_socket) {
                sock_release(in_socket);
                in_socket = NULL;
        }

        if (out_socket) {
                sock_release(out_socket);
                out_socket = NULL;
        }
        if (wq) {
                flush_workqueue(wq);
                destroy_workqueue(wq);
                wq = NULL;
        }
}
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
静态内部rcv_插座;
静态int snd_袜子;
rcv_addr_中的静态结构sockaddr_;
snd_addr_中的静态结构sockaddr_;
静态pthread_t rcv_线程;
静态无效*rcv\U螺纹\U fn(无效*数据);
int my_udp_init(void)
{
内部发送,接收;
接收到的int=0;
如果((rcv_sock=socket(PF_INET,sock_DGRAM,IPPROTO_UDP))<0){
佩罗(“插座”);
返回-1;
}
如果((snd_sock=socket(PF_INET,sock_DGRAM,IPPROTO_UDP))<0){
佩罗(“插座”);
返回-1;
}
memset(&rcv_addr_in,0,sizeof(rcv_addr_in));
rcv\u addr\u in.sin\u family=AF\u INET;
rcv_addr_in.sin_addr.s_addr=inet_addr(“127.0.0.1”);
rcv_addr_in.sin_port=htons(我的_in_port);
receivelen=sizeof(rcv\u addr\u in);
if(绑定(rcv_sock,(struct sockaddr*)和rcv_addr_in,receivelen)<0){
佩罗(“绑定”);
返回-1;
}
memset(&snd_addr_in,0,sizeof(snd_addr_in));
snd_addr_in.sin_family=AF_INET;
snd_addr_in.sin_addr.s_addr=inet_addr(“127.0.0.1”);
snd_addr_in.sin_port=htons(MY_OUT_port);
if(pthread_create(&rcv_thread,NULL,rcv_thread_fn,(void*)“rcv_thread”)){
return-ENOMEM;
}
返回0;
}
void my_udp_清理(void)
{
pthread_join(rcv_线程,NULL);
关闭(rcv_短袜);
关闭(snd_袜子);
}
int my_snd_msg(常量字符*buf,int大小)
{
sendto(snd_sock,buf,size,0,(struct sockaddr*)和snd_addr_in,sizeof(snd_addr_in));
返回0;
}
int my_rcv_msg(字符*buf,整数大小)
{
int-cnt=0;
如果((cnt=recv(rcv_sock,buf,size,MSG_DONTWAIT))<0){
if(errno==EAGAIN){
/*这在非阻塞情况下是正常的*/
睡眠(1);
返回0;
}否则{
perror(“recv”);
返回-1;
}
}
返回cnt;
}
静态无效*rcv\U螺纹\U fn(无效*数据)
{
字符缓冲区[64];
int-cnt;
当(!g_停止){
cnt=my_rcv_msg(缓冲区,63);
如果(cnt>0){
printf(“消息:%s\n”,缓冲区);
}
}
pthread_退出(0);
}
内核模块代码k_udp.c:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>

static int rcv_sock;
static int snd_sock;
static struct sockaddr_in rcv_addr_in;
static struct sockaddr_in snd_addr_in;
static pthread_t rcv_thread;

static void *rcv_thread_fn(void *data);

int my_udp_init(void)
{
    int sendlen, receivelen;
    int received = 0;

    if ((rcv_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
            perror("socket");
            return -1;
    }

    if ((snd_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
            perror("socket");
            return -1;
    }

    memset(&rcv_addr_in, 0, sizeof(rcv_addr_in));
    rcv_addr_in.sin_family = AF_INET;
    rcv_addr_in.sin_addr.s_addr = inet_addr("127.0.0.1");
    rcv_addr_in.sin_port = htons(MY_IN_PORT);

    receivelen = sizeof(rcv_addr_in);
    if (bind(rcv_sock, (struct sockaddr *) &rcv_addr_in, receivelen) < 0) {
            perror("bind");
            return -1;
    }

    memset(&snd_addr_in, 0, sizeof(snd_addr_in));
    snd_addr_in.sin_family = AF_INET;
    snd_addr_in.sin_addr.s_addr = inet_addr("127.0.0.1");
    snd_addr_in.sin_port = htons(MY_OUT_PORT);

    if (pthread_create(&rcv_thread, NULL, rcv_thread_fn, (void *)"rcv_thread")) {
            return -ENOMEM;
    }

    return 0;
}

void my_udp_cleanup(void)
{
    pthread_join(rcv_thread, NULL);
    close(rcv_sock);
    close(snd_sock);
}

int my_snd_msg(const char *buf, int size)
{
    sendto(snd_sock, buf, size, 0, (struct sockaddr *)&snd_addr_in, sizeof(snd_addr_in));
    return 0;
}

int my_rcv_msg(char *buf, int size)
{
    int cnt = 0;
    if ((cnt = recv(rcv_sock, buf, size, MSG_DONTWAIT)) < 0) {
            if (errno == EAGAIN) {
                    /* This is ok in the non-blocking case. */
                    sleep(1);
                    return 0;
            } else {
                    perror("recv");
                    return -1;
            }
    }
    return cnt;
}

static void *rcv_thread_fn(void *data)
{
    char buffer[64];
    int cnt;

    while (!g_stop) {
            cnt = my_rcv_msg(buffer, 63);
            if (cnt > 0) {
                    printf("message: %s\n", buffer);
            }
    }

    pthread_exit(0);
}
#include <linux/module.h>
#include <linux/init.h>
#include <linux/in.h>
#include <net/sock.h>
#include <linux/skbuff.h>
#include <linux/delay.h>
#include <linux/inet.h>
#include <linux/kthread.h>

static struct work_struct rcv_worker;
static struct socket *in_socket = NULL;
static struct socket *out_socket = NULL;
static struct workqueue_struct *wq = NULL;
static struct task_struct *notify_thread = NULL;

void rcv_work_queue(struct work_struct *data)
{
        int len;
        printk(KERN_INFO "%s: *******\n", __func__);

        while ((len = skb_queue_len(&in_socket->sk->sk_receive_queue)) > 0) {
                struct sk_buff *skb = NULL;

                skb = skb_dequeue(&in_socket->sk->sk_receive_queue);
                printk("message len: %i message: %s\n", skb->len - 8, skb->data+8);
                kfree_skb(skb);
        }
}

static void cb_data(struct sock *sk, int bytes)
{
        printk(KERN_INFO "%s: *******\n", __func__);
        queue_work(wq, &rcv_worker);
}

void send_notification(char *text)
{
        struct sockaddr_in to_addr;
        struct msghdr msg;
        struct iovec iov;
        mm_segment_t oldfs;
        int len = 0;

        if (out_socket->sk == NULL) {
                printk(KERN_ERR "%s: socket skbuff is null\n", __func__);
                return;
        }

        iov.iov_base = text;
        len = strlen(text);
        iov.iov_len = len;

        memset(&to_addr, 0, sizeof(to_addr));
        to_addr.sin_family = AF_INET;
        to_addr.sin_addr.s_addr = in_aton("127.0.0.1");
        to_addr.sin_port = htons(MY_OUT_PORT);

        msg.msg_flags = 0;
        msg.msg_name = &to_addr;
        msg.msg_namelen = sizeof(struct sockaddr_in);
        msg.msg_control = NULL;
        msg.msg_controllen = 0;
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_control = NULL;

        oldfs = get_fs();
        set_fs(KERNEL_DS);
        sock_sendmsg(out_socket, &msg, len);
        set_fs(oldfs);
}

static int k_udp_notify_thread(void *data)
{
        int i = 0;
        while (!kthread_should_stop()) {
                char buf[64];

                sprintf(buf, "test from kernel%d\n", i++);
                send_notification(buf);
                msleep(1000);
        }
        return 0;
}

int k_udp_init(void)
{
        struct sockaddr_in addr_out;
        struct sockaddr_in addr_in;
        int rc = 0;
        printk("%s\n", __func__);
        if (in_socket) {
                printk(KERN_INFO "%s: socket already set up\n", __func__);
                return 0;
        }

        if (sock_create(PF_INET, SOCK_DGRAM, IPPROTO_UDP, &in_socket) < 0) {
                printk( KERN_ERR "%s: failed to create socket\n", __func__);
                return -EIO;
        }
        addr_in.sin_family = AF_INET;
        addr_in.sin_addr.s_addr = in_aton("127.0.0.1");
        addr_in.sin_port = htons( (unsigned short)MY_IN_PORT);
        rc = in_socket->ops->bind(in_socket, (struct sockaddr *)&addr_in, sizeof(addr_in));
        if (rc) {
                printk(KERN_ERR "%s: failed to bind\n", __func__);
                sock_release(in_socket);
                in_socket = NULL;
                return -EIO;
        }
        in_socket->sk->sk_data_ready = cb_data;

        if (sock_create(PF_INET, SOCK_DGRAM, IPPROTO_UDP, &out_socket) < 0) {
                printk( KERN_ERR "%s: failed to create socket\n", __func__);
                sock_release(in_socket);
                in_socket = NULL;
                return -EIO;
        }
        addr_out.sin_family = AF_INET;
        addr_out.sin_addr.s_addr = in_aton("127.0.0.1");
        addr_out.sin_port = htons( (unsigned short)MY_OUT_PORT);
        rc = out_socket->ops->connect(out_socket, (struct sockaddr *)&addr_out, sizeof(addr_out), 0);
        if (rc) {
                printk(KERN_ERR "%s: failed to connect\n", __func__);
                sock_release(in_socket);
                in_socket = NULL;
                sock_release(out_socket);
                out_socket = NULL;
                return -EIO;
        }

        notify_thread = kthread_create(k_udp_notify_thread, NULL, "k_notify_thread");
        if (notify_thread) {
                printk(KERN_INFO "%s: notify thread created\n", __func__);
                wake_up_process(notify_thread);
        } else {
                printk(KERN_ERR "%s: failed to create notify thread\n", __func__);
        }

        INIT_WORK(&rcv_worker, rcv_work_queue);
        wq = create_singlethread_workqueue("k_rcv_wq");
        if (!wq) {
                return -ENOMEM;
        }

        printk(KERN_INFO "%s: success\n", __func__);

        return 0;
}

void k_udp_cleanup(void)
{
        /* Should we check that the thread is still valid (hasn't exited)? */
        if (notify_thread) {
                kthread_stop(notify_thread);
                notify_thread = NULL;
        }

        if (in_socket) {
                sock_release(in_socket);
                in_socket = NULL;
        }

        if (out_socket) {
                sock_release(out_socket);
                out_socket = NULL;
        }
        if (wq) {
                flush_workqueue(wq);
                destroy_workqueue(wq);
                wq = NULL;
        }
}
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
静态结构工作\u结构rcv_
    #include <netlink/netlink.h>
    #include <netlink/socket.h>
    #include <netlink/msg.h>
    #include <netlink/genl/genl.h>

    static struct nl_sock *sk = NULL;

    /**
     * Attributes and commands have to be the same as in kernelspace, so you might
     * want to move these enums to a .h and just #include that from both files.
     */
    enum attributes {
            ATTR_DUMMY,
            ATTR_HELLO,
            ATTR_FOO,

            /* This must be last! */
            __ATTR_MAX,
    };

    enum commands {
            COMMAND_HELLO,

            /* This must be last! */
            __COMMAND_MAX,
    };

    static int fail(int error, char *func_name)
    {
            printf("%s() failed.\n", func_name);
            return error;
    }

    static int nl_fail(int error, char *func_name)
    {
            printf("%s (%d)\n", nl_geterror(error), error);
            return fail(error, func_name);
    }

    /*
     * This function will be called for each valid netlink message received
     * in nl_recvmsgs_default()
     */
    static int cb(struct nl_msg *msg, void *arg)
    {
            struct nlmsghdr *nl_hdr;
            struct genlmsghdr *genl_hdr;
            struct nlattr *attrs[__ATTR_MAX];
            int error;

            printf("The kernel module sent a message.\n");

            nl_hdr = nlmsg_hdr(msg);
            genl_hdr = genlmsg_hdr(nl_hdr);

            if (genl_hdr->cmd != COMMAND_HELLO) {
                    printf("Oops? The message type is not Hello; ignoring.\n");
                    return 0;
            }

            error = genlmsg_parse(nl_hdr, 0, attrs, __ATTR_MAX - 1, NULL);
            if (error)
                    return nl_fail(error, "genlmsg_parse");

            /* Remember: attrs[0] is a throwaway. */

            if (attrs[1])
                    printf("ATTR_HELLO: len:%u type:%u data:%s\n",
                                    attrs[1]->nla_len,
                                    attrs[1]->nla_type,
                                    (char *)nla_data(attrs[1]));
            else
                    printf("ATTR_HELLO: null\n");

            if (attrs[2])
                    printf("ATTR_FOO: len:%u type:%u data:%u\n",
                                    attrs[2]->nla_len,
                                    attrs[2]->nla_type,
                                    *((__u32 *)nla_data(attrs[2])));
            else
                    printf("ATTR_FOO: null\n");

            return 0;
    }

    static int do_things(void)
    {
            struct genl_family *family;
            int group;
            int error;

            /* Socket allocation yadda yadda. */
            sk = nl_socket_alloc();
            if (!sk)
                    return fail(-1, "nl_socket_alloc");

            nl_socket_disable_seq_check(sk);

            error = nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, cb, NULL);
            if (error)
                    return nl_fail(error, "nl_socket_modify_cb");

            error = genl_connect(sk);
            if (error)
                    return nl_fail(error, "genl_connect");

            /* Find the multicast group identifier and register ourselves to it. */
            group = genl_ctrl_resolve_grp(sk, "PotatoFamily", "PotatoGroup");
            if (group < 0)
                    return nl_fail(group, "genl_ctrl_resolve_grp");

            printf("The group is %u.\n", group);

            error = nl_socket_add_memberships(sk, group, 0);
            if (error) {
                    printf("nl_socket_add_memberships() failed: %d\n", error);
                    return error;
            }

            /* Finally, receive the message. */
            nl_recvmsgs_default(sk);

            return 0;
    }

    int main(void)
    {
            int error;

            error = do_things();

            if (sk)
                    nl_socket_free(sk);

            return error;
    }