aboutsummaryrefslogtreecommitdiff
path: root/content/weblog/2023-02-10_genl-intro/index.ru.md
blob: ea922b51c25635c8ad3da86336222a25d30dfa8b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
+++
title = "Введение в Generic Netlink, или Как Общаться с Linux Kernel"
date = 2023-02-10T22:38:08
+++

![Туксу написали](genl-tux.png)

Пишите какой-то код в ядерном пространстве и хотите обращаться к нему из уютного
пользовательского пространства? Вы не пишите ядерный код, но хотите обращаться к
ядру Линукс? Налейте себе немного кофе или чая, присаживаетесь поудобнее, и
читайте далее!

<!-- more -->

Недавно я начал работать в ядерном пространстве Линукса, а точнее разрабатывать
модули ядра. Одно из API которое мне пришлось изучить это Generic Netlink. Как
часто бывает с большинством API Линукс, за исключением чётко
задокументированного кода, документация устаревшая, желает оставлять лучшего,
или вовсе отсутствует.

Соответственно я решил сделать маленький пример для себя чтобы научиться
пользоваться Generic Netlink'ом для общения с ядерным кодом, и вместе с ним
простое вводящее руководство для своих коллег и любого другого человека,
заинтересованный в использовании Generic Netlink, но не очень понимающий откуда
начинать.

В данном руководстве будет рассказано о следующем:

* Регистрация семейств (families) Generic Netlink в ядре.
* Регистрация операции Generic Netlink и их обработка в ядре.
* Регистрация мультикастных групп Generic Netlink в ядре.
* Как отправлять "уведомления" через Generic Netlink из ядра.
* Подключение к Generic Netlink с пользовательской программы.
* Разрешение семейств и мультикастных групп Generic Netlink с пользовательской
  программы.
* Отправка сообщения в семейство Generic Netlink с пользовательской программы.
* Подписка к мультикастной группы Generic Netlink с пользовательской программы.
* Получение сообщений и событий Generic Netlink с пользовательской программы.

## Что такое Netlink?

Netlink — сокетный домен созданный с целью предоставления интерфейса IPC для
ядра Linux, в основном для коммуникации ядро\<-\>пользователь. Netlink был
создан изначально с целью замены устаревшего `ioctl()`, предоставляя более
гибкий и удобный способ общения между ядром и пользовательскими программами.

Коммуникации в Netlink'е происходят через обычные BSD-сокеты используя домен
`AF_NETLINK`. Тем не менее, есть удобные готовые библиотеки которые упрощают
работу с Netlink с пользовательского пространства например, libnl[^1].

Netlink используется напрямую только в уже давно его использующих подсистемах
ядра Linux, через такие семейства Netlink как `NETLINK_ROUTE`,
`NETLINK_SELINUX`, и проч. В ядро больше не добавляются новые семейства Netlink,
и не планируется добавления новых семейств. Основная проблема классического
Netlink в том, что он использует статическое выделения идентификаторов,
ограниченные 32-м уникальным семействам. Это приводит к очень жёсткому
ограничению и может привести к конфликтам между разными модулями которые хотят
выделить себе новое семейство.

## Представляя Generic Netlink

Generic Netlink был создан с целью улучшения Netlink и упрощения работы с ним.
Он не является отдельным доменном, а является расширением Netlink. Более того,
он является семейством Netlink — `NETLINK_GENERIC`.

Generic Netlink существует ещё с 2005 года и является уже довольно известным и
хорошо установившимся интерфейсом для IPC между ядром и пользовательским
пространством. Среди хорошо известных пользователей Generic Netlink подсистемы
802.11, ACPI, Wireguard и другие.

Основные особенности Generic Netlink по сравнению с классическим Netlink'ом
— динамическая регистрация семейств, интроспекция и упрощенная API со стороны
ядра. Данное руководство сосредоточенное в основном на Generic Netlink, так как
это ныне стандартный способ общения с пространством ядра.

![Generic Netlink bus diagram](01-diagram.png)
<figcaption>

Диаграмма шины Generic Netlink
</figcaption>

## Немного теории

Как я выше упомянул, Netlink работает через стандартные BSD-сокеты. Сообщения в
Netlink всегда начинаются с заголовка Netlink, после которого идёт заголовка
протокола, то есть, в случае с Generic Netlink, его заголовок.

### Сообщения Netlink

Заголовки выглядит следующим образом:

![Netlink header](02-nlmsghdr.png)
<figcaption>

Заголовок Netlink
</figcaption>

![Generic Netlink header](03-genlmsghdr.png)
<figcaption>

Заголовок Generic Netlink
</figcaption>

Или же, если перевести в код C:

```C
struct nlmsghdr {
	__u32		nlmsg_len;
	__u16		nlmsg_type;
	__u16		nlmsg_flags;
	__u32		nlmsg_seq;
	__u32		nlmsg_pid;
};

struct genlmsghdr {
	__u8	cmd;
	__u8	version;
	__u16	reserved;
};
```

Значение полей в заголовках:

* Length — длина всего сообщения, включая заголовок.
* Type — идентификатор семейства Netlink, в нашем случае Generic Netlink.
* Flags — «do» или «dump»; чуть ниже будет подробнее.
* Sequence — номер последовательности; также ниже подробнее.
* Port ID — ставим в него 0, так как мы отправляем с ядра.

и для Generic Netlink:

* Command: идентификатор операции для данного семейства Generic Netlink.
* Version: версия протокола нашего семейства Generic Netlink.
* Reserved: зарезервированное поле, не используется.

Большинство полей довольно простые и понятные и заголовок обычно не заполняется
вручную самим пользователем Netlink. Большинство информации, которая содержится
в заголовках, предоставляется пользователю через API, когда тот обращается к
разным его функциям. Часть из этой информации это такие вещи как флаги и номер
последовательности.

Существуют три вида операций-сообщений, которые чаще всего исполняются через
сокет Netlink: 

* Операция «do»
* Операция «dump»
* И мультикастные сообщения или асинхронные уведомления.

Существует множество разных способов отправить сообщения через Netlink, но это
те, которые чаще всего применяются в Generic Netlink.

Операция «do» это сообщения предназначено для вида операции где пользовательская
программа отправляет сообщение и получает ответ в виде подтверждения
(acknowledgment) ошибки, или возможно даже сообщение с запрашиваемой информации.

Операция «dump» служит для того чтобы получатель «вывалил» или выложил
запрашиваемую информацию. Обычно это информация, которая не помешается в больше
чем в одном сообщении. Пользовательская программа отправляет одно сообщение и
получает несколько сообщений до получения сообщения `NLMSG_DONE`, указывающее на
окончание операции.

Вид операции, то есть, будь это «do» или «dump», задаётся с помощью поля
«flags»:

* `NLM_F_REQUEST | NLM_F_ACK` — do.
* `NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP` — dump.

Пора поговорить про третий вид сообщений — мультикастные сообщения. Они
используются для того, чтобы уведомлять пользователей, которые подписанные на
них через мультикастную группу Generic Netlink.

Как мы уже видели, также существует поле «sequence number» в заголовке Netlink.
Несмотря на это, в отличие от других протоколов коммуникации, Netlink не
манипулирует и не проверяет правильность номера последовательности. Он
предоставлен для удобства пользователей, чтобы было проще отслеживать запросы и
ответы. На практике чаще всего пользовательская программа сама увеличивает с
каждым отправленным сообщением этот номер, а модуль ядра отправляет ответ с тем
же номером. Мультикастные сообщения обычно отправляются с номером
последовательности равным 0.

### Груз сообщения

Netlink предоставляет систему атрибутов для кодирования данных с полезной
информацией об их вид и длины. Атрибуты позволяют проверить правильность данных
и позволяют, проще и не ломая обратную совместимость, расширять протоколы.

Также возможна кодировка своих собственных типов, таких как собственные
структуры в одном атрибуте. Тем не менее, рекомендуется использовать один
атрибут строго на одно поле данных.

Атрибуты кодируются в формате LTV (длина-тип-значение), и они заполнены так
чтобы каждый атрибут был выравнен по 4 байта. Длина полей, как в заголовке как в
атрибуте, всегда включает в себя заголовок, но не заполнение для выравнивания.

![Netlink attribute diagram](04-nlattr.png)
<figcaption>

Диаграмма атрибутов Netlink
</figcaption>

Порядок атрибутов в сообщении Netlink, следовательно и в сообщении Generic
Netlink, не гарантирован. По этой причине следует пройтись по ним и
проанализировать.

Netlink предоставляет возможность проверять правильность сообщения используя так
называемые "политики валидации атрибутов", представлены в структуре `struct
nla_policy`. Мне лично кажется немного странным и недостатком то, что это
структура не экспортирована в пользовательское пространство. Получается что
валидация происходит по умолчанию только в пространстве ядра. Проверку также
можно осуществить с стороны пользовательского пространства, и libnl
предоставляет свою собственную `struct nla_policy` но оно отличается от ядерного
что означает что по сути нужно дублировать код для того, чтобы делать проверку с
обеих сторон.

Виды сообщения и операции определяются с так называемыми командами Netlink.
Команда коррелирует к одному виду сообщения и также может коррелировать к одной
операции.

Каждая команда или вид сообщения могут иметь один и более атрибутов.

### Семейства

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

У сокетов есть семейства, для наших нужд мы используем семейство `AF_NETLINK`.
У Netlink'а тоже есть семейства, которых всего 32. Их не планируется расширять и
добавлять новые семейства в ядро Linux; семейство, которым мы пользуемся,
`NETLINK_GENERIC`. И наконец, у нас есть семейства Generic Netlink, в отличие от
семейств Netlink их можно динамически регистрировать и их может быть
зарегистрировано до 1024 в одно и то же время.

Семейства Generic Netlink идентифицируются с помощью строк, например, "nl80211".
Так как семейства регистрируются динамическим образом, это значит что их
идентификационный номер может отличаться от компьютера к компьютеру и от сеанса
к сеансу, поэтому сначала нам нужно разрешить семейство перед тем, как
отправлять сообщения.

Generic Netlink сам по себе предоставляет одно статически выделено семейство,
которое называется `nlctrl`, которое предоставляет команду для разрешения всех
остальных семейств Generic Netlink. Оно также, с недавних пор, предоставляет
команду для интроспекции операций и получения политик с пользовательское
пространства, тем не менее в данной статье мы ей не будем пользоваться.

Ещё один момент, который стоит учитывать, это то, что один сокет Generic Netlink
не привязан к одному только семейству. Сокет Generic Netlink может общаться с
любым семейством, ему лишь нужно предоставить ID семейства в сообщении, размещая
его в поле "type".

### Мультикастные группы

Есть сообщения которые нам нужно отправить асинхронно пользовательским
программам для того, чтобы уведомить их о каком-то событии или просто
предоставить информацию как только она становится доступной. Для этого
существуют мультикастные группы.

Мультикастные группы Generic Netlink, так же как и семейства, регистрируются
динамически с помощью названия, содержащееся в строке, и получают численное ID
после успешной регистрации. То есть, их также необходимо разрешать перед тем как
подписаться на них. Как только программа на них подпишется, она начнёт получать
все сообщения отправленные в группу.

Дабы предотвратить путаницы чисел последовательности с уникастными сообщениями,
и упростить обработку полученных ответов, рекомендуется использовать отдельный
сокет для мультикастных сообщений.

## Пора руки помочить

Это далеко не всё, о чём можно написать про Generic Netlink и особенно
классический Netlink. Тем не менее это были самые важные понятия которые нужные
для работы с Generic Netlink. Как бы то не было, не столь интересно просто знать
о чём-то, сколько применять эти знания на практике.

Я сделал пример применения Generic Netlink, который состоит из двух частей.
Модуль ядра и пользовательская программа.

Модуль ядра предоставляет одну операцию Generic Netlink и одну мультикастную
группу. Структура сообщения одинаковая как для операции `do` как для
мультикастного сообщения. Первое принимает сообщение с строкой текста, выводит
его в журнал ядра, и отправляет обратно своё собственное собщение; второе
отправляет уведомление при получении строки текста из `sysfs`, дублируя его.

Пользовательская программа подключается к Generic Netlink, подписывается к
мультикастной группу, отправляет сообщение нашему семейству и выводит на экран
полученное сообщение.

Далее я разъясню всё по шагам с помощью листингов кода. Полный исходный код для
обеих частей можно найти здесь: <https://git.yaroslavps.com/genltest/>.

### Пространство ядра

Использовать Generic Netlink с ядерного пространства довольно просто.
Единственное что нам нужно чтобы начать работу, это подключить хедер
`net/genetlink.h`. Вот все заголовочные, которые нам нужны для нашего модуля:

```C
#include <linux/module.h>
#include <net/genetlink.h>
```

Также нам понадобятся некоторые определения и энумерации, которые также должны
быть доступны пользовательской программе. Мы их поместим в заголовочный файл с
названием `genltest.h`:

```C
#define GENLTEST_GENL_NAME "genltest"
#define GENLTEST_GENL_VERSION 1
#define GENLTEST_MC_GRP_NAME "mcgrp"

/* Attributes */
enum genltest_attrs {
	GENLTEST_A_UNSPEC,
	GENLTEST_A_MSG,
	__GENLTEST_A_MAX,
};

#define GENLTEST_A_MAX (__GENLTEST_A_MAX - 1)

/* Commands */
enum genltest_cmds {
	GENLTEST_CMD_UNSPEC,
	GENLTEST_CMD_ECHO,
	__GENLTEST_CMD_MAX,
};

#define GENLTEST_CMD_MAX (__GENLTEST_CMD_MAX - 1)
```

В нём мы определили название нашего семейства, версия его протокола, название
нашей мультикастной группы, и атрибуты, которыми мы будем пользоваться в наших
сообщениях.

Обратно в нашем ядерном коде, мы создаём политику валидации для нашей команды
«echo»:

```C
/* Attribute validation policy for our echo command */
static struct nla_policy echo_pol[GENLTEST_A_MAX + 1] = {
	[GENLTEST_A_MSG] = { .type = NLA_NUL_STRING },
};
```

Делаем массив с нашими операциями:

```C
/* Operations for our Generic Netlink family */
static struct genl_ops genl_ops[] = {
	{
		.cmd	= GENLTEST_CMD_ECHO,
		.policy = echo_pol,
		.doit	= echo_doit,
	 },
};
```

Массив с нашими мультикастными группами:

```C
/* Multicast groups for our family */
static const struct genl_multicast_group genl_mcgrps[] = {
	{ .name = GENLTEST_MC_GRP_NAME },
};
```

И наконец структуру, описывающую наше семейство, содержащее всего, что мы сейчас
определяли:

```C
/* Generic Netlink family */
static struct genl_family genl_fam = {
	.name	  = GENLTEST_GENL_NAME,
	.version  = GENLTEST_GENL_VERSION,
	.maxattr  = GENLTEST_A_MAX,
	.ops	  = genl_ops,
	.n_ops	  = ARRAY_SIZE(genl_ops),
	.mcgrps	  = genl_mcgrps,
	.n_mcgrps = ARRAY_SIZE(genl_mcgrps),
};
```

При инициализации нашего модуля, нам необходимо зарегистрировать наше семейство
в Generic Netlink. Для этого нам необходимо передать нашу структуру с помощью
функции:

```C
ret = genl_register_family(&genl_fam);
if (unlikely(ret)) {
	pr_crit("failed to register generic netlink family\n");
	// etc...
}
```

И конечно не забыть освободить всё что с ним связано при выходе:

```C
if (unlikely(genl_unregister_family(&genl_fam))) {
	pr_err("failed to unregister generic netlink family\n");
}
```

Как вы наверняка уже заметили, мы предоставили функцию `echo_doit` для обратного
вызова для нашей команды "echo". Она выглядит следующим образом:

```C
/* Handler for GENLTEST_CMD_ECHO messages received */
static int echo_doit(struct sk_buff *skb, struct genl_info *info)
{
	int		ret = 0;
	void	       *hdr;
	struct sk_buff *msg;

	/* Check if the attribute is present and print it */
	if (info->attrs[GENLTEST_A_MSG]) {
		char *str = nla_data(info->attrs[GENLTEST_A_MSG]);
		pr_info("message received: %s\n", str);
	} else {
		pr_info("empty message received\n");
	}

	/* Allocate a new buffer for the reply */
	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
	if (!msg) {
		pr_err("failed to allocate message buffer\n");
		return -ENOMEM;
	}

	/* Put the Generic Netlink header */
	hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq, &genl_fam, 0,
			  GENLTEST_CMD_ECHO);
	if (!hdr) {
		pr_err("failed to create genetlink header\n");
		nlmsg_free(msg);
		return -EMSGSIZE;
	}
	/* And the message */
	if ((ret = nla_put_string(msg, GENLTEST_A_MSG,
				  "Hello from Kernel Space, Netlink!"))) {
		pr_err("failed to create message string\n");
		genlmsg_cancel(msg, hdr);
		nlmsg_free(msg);
		goto out;
	}

	/* Finalize the message and send it */
	genlmsg_end(msg, hdr);

	ret = genlmsg_reply(msg, info);
	pr_info("reply sent\n");

out:
	return ret;
}
```

Если вкратце, для обработки команды вида «do» нужно:

1. Извлечь данные входящего сообщения со структуры `genl_info`.
2. Выделить память для ответа.
3. Вставить заголовок Generic Netlink в буфере сообщения; обратите внимания на
   то, что мы используем тот же ID порта и номер последовательности что и во
   входящем сообщении, так как это ответное сообщение.
4. Вставить все атрибуты с полезным грузом.
5. Отправить ответ.

Теперь посмотрим как отправить мультикастное сообщение. Я использовал sysfs в
этом примере дабы сделать его более интересным. Я создал «kobj», который
называется `genltest`, который содержит атрибут `ping`, с которого мы собственно
продублируем то, что в него будет записано. Для краткости, я упущу со статьи код
с sysfs, просто предоставлю функцию, которая формирует и отправляет сообщение:

```C
/* Multicast ping message to our genl multicast group */
static int echo_ping(const char *buf, size_t cnt)
{
	int		ret = 0;
	void	       *hdr;
	/* Allocate message buffer */
	struct sk_buff *skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);

	if (unlikely(!skb)) {
		pr_err("failed to allocate memory for genl message\n");
		return -ENOMEM;
	}

	/* Put the Generic Netlink header */
	hdr = genlmsg_put(skb, 0, 0, &genl_fam, 0, GENLTEST_CMD_ECHO);
	if (unlikely(!hdr)) {
		pr_err("failed to allocate memory for genl header\n");
		nlmsg_free(skb);
		return -ENOMEM;
	}

	/* And the message */
	if ((ret = nla_put_string(skb, GENLTEST_A_MSG, buf))) {
		pr_err("unable to create message string\n");
		genlmsg_cancel(skb, hdr);
		nlmsg_free(skb);
		return ret;
	}

	/* Finalize the message */
	genlmsg_end(skb, hdr);

	/* Send it over multicast to the 0-th mc group in our array. */
	ret = genlmsg_multicast(&genl_fam, skb, 0, 0, GFP_KERNEL);
	if (ret == -ESRCH) {
		pr_warn("multicast message sent, but nobody was listening...\n");
	} else if (ret) {
		pr_err("failed to send multicast genl message\n");
	} else {
		pr_info("multicast message sent\n");
	}

	return ret;
}
```

Процесс довольно похож на операцию «do», с тем отличием что мы не отвечаем на
запрос, а отправляем асинхронное сообщение. В связи с этим, мы задаём 0 в
качестве последовательного номера, так как он нам в данном случае не нужен, и
также задаём 0 для ID порта, порт/PID ядра. Также мы отправляем сообщение через
функцию `genlmsg_multicast()`.

На этом всё со стороны ядра. Теперь взглянем на пользовательскую часть.

### Пользовательское пространство

Netlink является сокетным семейством, что делает возможным пользоваться им через
стандартное API сокетов. Достаточно открыть сокет и далее писать и читать
сообщения через него, примерно так:

```C
int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);

/* Format request message... */
/* ... */

/* Send it */
send(fd, &req, sizeof(req), 0);
/* Receive response */
recv(fd, &resp, BUF_SIZE, 0);

/* Do something with response... */
/* ... */
```

Тем не менее лучше использовать libnl[^1] или похожую библиотеку, так как они
предоставляют более удобный интерфейс к Generic Netlink, что предотвращает
некоторые ошибки, и реализует код, который бы всего равно пришлось написать. Эту
же библиотеку я буду использовать для своего примера.

Для начала нам нужно подключить некоторые хедеры с libnl:

```C
#include <netlink/socket.h>
#include <netlink/netlink.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/family.h>
```

А также совместный хедер, который мы создали когда писали ядерный модуль:

```C
#include "../ks/genltest.h"
```

В примере я также сделал маленький макрос для вывода ошибок:

```C
#define prerr(...) fprintf(stderr, "error: " __VA_ARGS__)
```

Как я упомянул ранее, проще использовать отдельные сокеты под юникаст и
мультикаст сообщения. Поэтому здесь открываем два сокета для общения через
Generic Netlink: 

```C
/* Allocate netlink socket and connect to generic netlink */
static int conn(struct nl_sock **sk)
{
	*sk = nl_socket_alloc();
	if (!sk) {
		return -ENOMEM;
	}

	return genl_connect(*sk);
}

/*
 * ...
 */

struct nl_sock *ucsk, *mcsk;

/*
 * We use one socket to receive asynchronous "notifications" over
 * multicast group, and another for ops. We do this so that we don't mix
 * up responses from ops with notifications to make handling easier.
 */
if ((ret = conn(&ucsk)) || (ret = conn(&mcsk))) {
	prerr("failed to connect to generic netlink\n");
	goto out;
}
```

Далее нам нужно разрешить ID семейства Generic Netlink, к которому мы хотим
подключиться:

```C
/* Resolve the genl family. One family for both unicast and multicast. */
int fam = genl_ctrl_resolve(ucsk, GENLTEST_GENL_NAME);
if (fam < 0) {
	prerr("failed to resolve generic netlink family: %s\n",
	      strerror(-fam));
	goto out;
}
```

Сокет (Generic) Netlink никак не ассоциируется с семейством, а ID семейства нам
пригодится позже для отправки сообщения.

Библиотека libnl может отслеживать номер последовательности за нас, но нам это
не нужно для мультикастных сообщений, поэтому и выключаем его:

```C
nl_socket_disable_seq_check(mcsk);
```

Нам также необходимо разрешить название мультикастной группы. В данном случае мы
сразу задействуем полученный ID для того, чтобы подписаться к данной группе и
начать получать уведомления:

```C
/* Resolve the multicast group. */
int mcgrp = genl_ctrl_resolve_grp(mcsk, GENLTEST_GENL_NAME,
				  GENLTEST_MC_GRP_NAME);
if (mcgrp < 0) {
	prerr("failed to resolve generic netlink multicast group: %s\n",
	      strerror(-mcgrp));
	goto out;
}
/* Join the multicast group. */
if ((ret = nl_socket_add_membership(mcsk, mcgrp) < 0)) {
	prerr("failed to join multicast group: %s\n", strerror(-ret));
	goto out;
}
```

Нам нужно поменять обратный вызов на наш, для того, чтобы мы могли получить и
обработать входящие сообщения:

```C
/* Modify the callback for replies to handle all received messages */
static inline int set_cb(struct nl_sock *sk)
{
	return -nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM,
				    echo_reply_handler, NULL);
}

/*
 * ...
 */

if ((ret = set_cb(ucsk)) || (ret = set_cb(mcsk))) {
	prerr("failed to set callback: %s\n", strerror(-ret));
	goto out;
}
```

Как вы могли заметить, обратный вызов одинаков для обеих сокетов, так как мы по
сути получаем одинаковое по типу сообщение как для операции «do» так для
асинхронных сообщений. Функция выглядит следующим образом:

```C
/*
 * Handler for all received messages from our Generic Netlink family, both
 * unicast and multicast.
 */
static int echo_reply_handler(struct nl_msg *msg, void *arg)
{
	int		   err	   = 0;
	struct genlmsghdr *genlhdr = nlmsg_data(nlmsg_hdr(msg));
	struct nlattr	  *tb[GENLTEST_A_MAX + 1];

	/* Parse the attributes */
	err = nla_parse(tb, GENLTEST_A_MAX, genlmsg_attrdata(genlhdr, 0),
			genlmsg_attrlen(genlhdr, 0), NULL);
	if (err) {
		prerr("unable to parse message: %s\n", strerror(-err));
		return NL_SKIP;
	}
	/* Check that there's actually a payload */
	if (!tb[GENLTEST_A_MSG]) {
		prerr("msg attribute missing from message\n");
		return NL_SKIP;
	}

	/* Print it! */
	printf("message received: %s\n", nla_get_string(tb[GENLTEST_A_MSG]));

	return NL_OK;
}
```

В целом ничего особенно, по большей части очень похоже на то, что мы делали в
ядерной части. Основное отличие в том, что в ядерном коде сообщение уже было
распарсенное за нас ядерным API, а здесь нам даётся выбор пройти по атрибутам
вручную или спарсить их всех в массив с помощью функции библиотеки.

Далее, нам надо отправить сообщение в ядерное пространство:

```C
/* Send (unicast) GENLTEST_CMD_ECHO request message */
static int send_echo_msg(struct nl_sock *sk, int fam)
{
	int	       err = 0;
	struct nl_msg *msg = nlmsg_alloc();
	if (!msg) {
		return -ENOMEM;
	}

	/* Put the genl header inside message buffer */
	void *hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, fam, 0, 0,
				GENLTEST_CMD_ECHO, GENLTEST_GENL_VERSION);
	if (!hdr) {
		return -EMSGSIZE;
	}

	/* Put the string inside the message. */
	err = nla_put_string(msg, GENLTEST_A_MSG,
			     "Hello from User Space, Netlink!");
	if (err < 0) {
		return -err;
	}
	printf("message sent\n");

	/* Send the message. */
	err = nl_send_auto(sk, msg);
	err = err >= 0 ? 0 : err;

	nlmsg_free(msg);

	return err;
}

/*
 * ...
 */

/* Send unicast message and listen for response. */
if ((ret = send_echo_msg(ucsk, fam))) {
	prerr("failed to send message: %s\n", strerror(-ret));
}
```

Также не сильно отличается от ядерного API. Теперь нам следует единовременное
слушать для получения ответа и "бесконечно" для получения уведомлений:


```C
printf("listening for messages\n");
nl_recvmsgs_default(ucsk);

/* Listen for "notifications". */
while (1) {
	nl_recvmsgs_default(mcsk);
}
```

Не забываем перед выходом программы закрывать и освобождать наши сокеты:

```C
/* Disconnect and release socket */
static void disconn(struct nl_sock *sk)
{
	nl_close(sk);
	nl_socket_free(sk);
}

/*
 * ...
 */

disconn(ucsk);
disconn(mcsk);
```

На этом всё!

## Подводя итоги

Старые способы взаимодействия с ядром и его различными подсистемами через такие
интерфейса как «sysfs» и в особенности «ioctl» имеют немало недостатков и в них
отсутствуют некоторые очень нужные в современном мире функции, такие как
асинхронные операции и структурированные форматы данных.

Netlink, и его расширенная форма, Generic Netlink, предоставляют очень гибкий
способ общения с ядром, и они решают многие недостатки и проблемы интерфейсов
былых времён. Это не идеальное решение (например, не совсем по-Юниксовому), но
это однозначно лучший способ который есть, на данным момент, для того, чтобы
передавать в и из ядра более сложные вещи чем простые параметры.

## Post scriptum

Когда я начал изучать Generic Netlink, версия 6.0 ядра ещё не была выпущенной
(stable). Ввиду того что только недавно в этой же версии была добавлена в
документацию замечательная страница-введение в Netlink[^2], и того что я смотрел
в версии документации «latest» (на тот момент v5.9), я её не заметил до того,
как я уже начал писать свою статью.

Не уверен, начал ли я бы писать эту статью, если бы я раньше увидел ту страницу
ядерских доков. Тем не менее в конечном итоге я считаю что оно того стоило, так
как это статья получилась неплохим добавлением к странице официальных доков,
для желающих сразу помочить руки, особенно, учитывая что здесь предоставляется
живой пример.

Тем не менее, несомненно прочитайте официальную документацию ядра! Та страница
покрывает некоторые моменты, о которых я мог не упомянуть.

[^1]: Оригинальный сайт с информацией <https://www.infradead.org/~tgr/libnl/>,
  актуальное репо: <https://github.com/thom311/libnl>

[^2]: Хорошая вводная статья по Netlink, с самих доков ядра:
  <https://kernel.org/doc/html/latest/userspace-api/netlink/intro.html>