Virtual Net Kernel Module 1.0.0
Модуль ядра Linux на C, который создаёт виртуальный сетевой интерфейс, позволяет задавать IPv4 через `procfs/sysctl` и отвечает на `ping`.
Загрузка...
Поиск...
Не найдено
virtual_net.c
См. документацию.
1#include "virtual_net.h"
2
3#include <linux/etherdevice.h>
4#include <linux/ip.h>
5#include <linux/icmp.h>
6#include <linux/proc_fs.h>
7#include <linux/if_arp.h>
8#include <linux/if_ether.h>
9#include <linux/if.h>
10#include <net/arp.h>
11
22static int __init virtual_net_init(void) {
23 /* 1) Регистрируем sysctl-путь /proc/sys/net/vnet/ip_addr. */
24 vnet_sysctl_header = register_sysctl("net/vnet", vnet_table);
25 if (!vnet_sysctl_header) {
26 pr_err("VNET: не удалось зарегистрировать sysctl-таблицу\n");
27 return -ENOMEM;
28 }
29
30 /* 2) Создаём net_device с приватной областью struct vnet_priv. */
31 vnet_dev = alloc_etherdev(sizeof(struct vnet_priv));
32 if (!vnet_dev) {
33 pr_err("VNET: не удалось выделить сетевое устройство\n");
34 unregister_sysctl_table(vnet_sysctl_header);
35 return -ENOMEM;
36 }
37
38 /* Базовая настройка интерфейса: имя, коллбэки, флаги, MAC. */
39 strscpy(vnet_dev->name, VNET_IFNAME, IFNAMSIZ);
40 vnet_dev->netdev_ops = &vnet_ops;
41 vnet_dev->flags |= IFF_NOARP;
42 eth_hw_addr_random(vnet_dev);
43
44 /* MTU интерфейса (максимальный payload кадра без фрагментации на L3). */
45 vnet_dev->mtu = 1500;
46
47 /* 3) Регистрируем устройство в сетевом стеке ядра. */
48 int err = register_netdev(vnet_dev);
49 if (err) {
50 pr_err("VNET: не удалось зарегистрировать сетевое устройство\n");
51 free_netdev(vnet_dev);
52 unregister_sysctl_table(vnet_sysctl_header);
53 return err;
54 }
55
56 pr_info("VNET: модуль загружен: IP=%s, MAC=%pM\n", vnet_ip_str, vnet_dev->dev_addr);
57
58 /* 4) Переводим интерфейс в состояние UP под RTNL lock. */
59 rtnl_lock();
60 err = dev_change_flags(vnet_dev, vnet_dev->flags | IFF_UP, NULL);
61 rtnl_unlock();
62
63 if (err < 0) {
64 pr_err("VNET: не удалось поднять интерфейс %s (ошибка %d)\n", vnet_dev->name, err);
65 pr_info("VNET: можно поднять интерфейс вручную: sudo ip link set %s up\n", vnet_dev->name);
66 unregister_netdev(vnet_dev);
67 free_netdev(vnet_dev);
68 unregister_sysctl_table(vnet_sysctl_header);
69 return err;
70 }
71 pr_info("VNET: интерфейс %s переведён в состояние UP\n", vnet_dev->name);
72
73 /* 5) Назначаем стартовый IPv4 через /sbin/ip (userspace helper). */
75 if (err) {
76 pr_err("VNET: не удалось назначить IP %s на %s (ошибка %d)\n", vnet_ip_str, vnet_dev->name, err);
77 pr_warn("VNET: можно назначить IP вручную: sudo ip addr add %s/24 dev %s\n", vnet_ip_str, vnet_dev->name);
78 } else {
79 pr_info("VNET: IP %s/24 назначен интерфейсу %s\n", vnet_ip_str, vnet_dev->name);
80 }
81
82 pr_info("VNET: модуль успешно инициализирован, MAC=%pM\n", vnet_dev->dev_addr);
83 pr_info("VNET: изменить IP можно так: echo \"NEW_IP\" | sudo tee /proc/sys/net/vnet/ip_addr\n");
84 return 0;
85}
86
88static void __exit virtual_net_exit(void) {
89 if (vnet_dev) {
90 pr_info("VNET: выгрузка интерфейса: IP=%s, MAC=%pM\n", vnet_ip_str, vnet_dev->dev_addr);
91 unregister_netdev(vnet_dev);
92 free_netdev(vnet_dev);
93 }
95 pr_info("VNET: освобождение sysctl-таблицы\n");
96 unregister_sysctl_table(vnet_sysctl_header);
97 vnet_sysctl_header = NULL;
98 }
99
100 pr_info("VNET: модуль выгружен\n");
101}
102
110static bool str_to_ip(__u32 *ip_out, const char *ip_str, spinlock_t *lock) {
111 /* Защищаем общий буфер адреса от гонок при чтении/записи. */
112 spin_lock_bh(lock);
113 if (in4_pton(ip_str, -1, (u8 *) ip_out, -1, NULL) <= 0) {
114 spin_unlock_bh(lock);
115 return false;
116 }
117 spin_unlock_bh(lock);
118 return true;
119}
120
131static netdev_tx_t vnet_xmit(struct sk_buff *skb, struct net_device *dev) {
132 struct vnet_priv *priv = netdev_priv(dev);
133 __u32 target_ip = 0;
134
135 /* Базовая защита от некорректных аргументов драйверного коллбэка. */
136 if (!skb || !dev) {
137 if (skb)
138 dev_kfree_skb(skb);
139 return NETDEV_TX_OK;
140 }
141
142 /* Получаем текущий адрес интерфейса из sysctl-буфера в бинарном формате. */
143 if (!str_to_ip(&target_ip, vnet_ip_str, &vnet_ip_lock)) {
144 priv->stats.tx_dropped++;
145 dev_kfree_skb(skb);
146 return NETDEV_TX_OK;
147 }
148
149 /* ARP кадры обрабатываем отдельно: отвечаем на запросы к своему IP. */
150 if (skb->protocol == htons(ETH_P_ARP)) {
151 vnet_arp_reply(skb, dev, target_ip);
152 priv->stats.rx_packets++;
153 priv->stats.rx_bytes += skb->len;
154 dev_kfree_skb(skb);
155 return NETDEV_TX_OK;
156 }
157
158 /* Для IPv4 валидируем заголовок, checksum и обрабатываем только ICMP Echo. */
159 if (skb->protocol == htons(ETH_P_IP)) {
160 /* Синхронизируем network_header, чтобы ip_hdr() указывал на корректные данные. */
161 skb_reset_network_header(skb);
162
163 struct iphdr *iph = ip_hdr(skb);
164
165 /* Защита от короткого (битого) пакета: заголовок не помещается в skb. */
166 if (unlikely(skb->len < sizeof(struct iphdr))) {
167 priv->stats.rx_length_errors++;
168 dev_kfree_skb(skb);
169 return NETDEV_TX_OK;
170 }
171
172 /* Проверяем checksum IPv4-заголовка до дальнейшей обработки. */
173 if (ip_fast_csum((u8 *) iph, iph->ihl) != 0) {
174 priv->stats.rx_crc_errors++;
175 dev_kfree_skb(skb);
176 return NETDEV_TX_OK;
177 }
178
179 /* Интересуют только ICMP Echo Request в адрес текущего IP интерфейса. */
180 if (iph->protocol == IPPROTO_ICMP) {
181 /* Убеждаемся, что в пакете есть весь ICMP-заголовок. */
182 if (unlikely(skb->len < iph->ihl * 4 + sizeof(struct icmphdr))) {
183 priv->stats.rx_length_errors++;
184 dev_kfree_skb(skb);
185 return NETDEV_TX_OK;
186 }
187
188 /* Устанавливаем транспортный заголовок для корректного icmp_hdr(). */
189 skb_set_transport_header(skb, skb_network_offset(skb) + (int) skb_network_header_len(skb));
190
191 const struct icmphdr *icmph = icmp_hdr(skb);
192
193 /* Echo request на наш адрес: формируем копию и превращаем её в reply. */
194 if (icmph->type == ICMP_ECHO && iph->daddr == target_ip) {
195 struct sk_buff *new_skb = skb_copy(skb, GFP_ATOMIC);
196
197 if (new_skb) {
198 struct iphdr *new_iph = ip_hdr(new_skb);
199 struct icmphdr *new_icmph = icmp_hdr(new_skb);
200
201 /* Меняем source/destination местами для ответа. */
202 const __u32 tmp = new_iph->saddr;
203 new_iph->saddr = new_iph->daddr;
204 new_iph->daddr = tmp;
205
206 /* После изменения заголовка обязательно пересчитываем IPv4 checksum. */
207 new_iph->check = 0;
208 new_iph->check = ip_fast_csum((u8 *) new_iph, new_iph->ihl);
209
210 /* Тип ICMP меняем с Echo Request на Echo Reply. */
211 new_icmph->type = ICMP_ECHOREPLY;
212
213 /* Пересчитываем ICMP checksum уже для модифицированного пакета. */
214 new_icmph->checksum = 0;
215 new_icmph->checksum = ip_compute_csum((unsigned char *) new_icmph,
216 ntohs(new_iph->tot_len) - new_iph->ihl * 4);
217
218 /* Возвращаем пакет в сетевой стек, как будто он пришёл от интерфейса. */
219 new_skb->dev = dev;
220 new_skb->protocol = eth_type_trans(new_skb, dev);
221
222 /* tx_* считаем для сформированного ответа, rx_* — для принятого стеком. */
223 priv->stats.tx_packets++;
224 priv->stats.tx_bytes += new_skb->len;
225
226 if (netif_rx(new_skb) == NET_RX_SUCCESS) {
227 priv->stats.rx_packets++;
228 priv->stats.rx_bytes += new_skb->len;
229 }
230
231 /* Исходный skb уже не нужен: ответ сформирован на копии. */
232 dev_kfree_skb(skb);
233 return NETDEV_TX_OK;
234 }
235
236 /* Не удалось выделить буфер под ответ: учитываем как tx ошибку. */
237 priv->stats.tx_errors++;
238 dev_kfree_skb(skb);
239 return NETDEV_TX_OK;
240 }
241 }
242 }
243
244 /* Все неподдержанные кадры/протоколы дропаем как simulated NIC. */
245 priv->stats.tx_dropped++;
246 dev_kfree_skb(skb);
247 return NETDEV_TX_OK;
248}
249
255static void vnet_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats) {
256 struct vnet_priv const *priv = netdev_priv(dev);
257 memcpy(stats, &priv->stats, sizeof(*stats));
258}
259
266static void vnet_arp_reply(struct sk_buff const *skb, struct net_device *dev, __u32 target_ip) {
267 struct arphdr *arp;
268 unsigned char const *arp_ptr;
269 unsigned char const *sha;
270 unsigned char const *tha;
271 __u32 sip;
272 __u32 tip;
273 struct sk_buff *reply_skb;
274
275 arp = arp_hdr(skb);
276
277 if (arp->ar_op != htons(ARPOP_REQUEST))
278 return;
279
280 /* Из ARP payload извлекаем MAC/IP отправителя и целевой IP запроса. */
281 arp_ptr = (unsigned char *) (arp + 1);
282 sha = arp_ptr;
283 arp_ptr += dev->addr_len;
284 memcpy(&sip, arp_ptr, 4);
285 arp_ptr += 4;
286 tha = arp_ptr;
287 arp_ptr += dev->addr_len;
288 memcpy(&tip, arp_ptr, 4);
289
290 /* Отвечаем только на ARP-запросы к текущему адресу интерфейса. */
291 if (tip != target_ip)
292 return;
293
294 /* Конструируем и отправляем ARP reply с MAC нашего интерфейса. */
295 reply_skb = arp_create(ARPOP_REPLY, ETH_P_ARP, sip, dev, tip, sha, dev->dev_addr, sha);
296
297 if (!reply_skb) {
298 pr_err("VNET: не удалось сформировать ARP reply\n");
299 return;
300 }
301
302 arp_xmit(reply_skb);
303
304 struct vnet_priv *priv = netdev_priv(dev);
305 priv->stats.tx_packets++;
306 priv->stats.tx_bytes += skb->len;
307}
308
318static int vnet_sysctl_handler(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) {
319 int ret = 0;
320 char old_ip[16];
321
322 /* Сохраняем старый IP, чтобы можно было откатить значение при ошибке. */
323 strcpy(old_ip, vnet_ip_str);
324
325 ret = proc_dostring(table, write, buffer, lenp, ppos);
326 if (ret) {
327 pr_err("VNET: ошибка обработки sysctl-запроса\n");
328 return ret;
329 }
330
331 /* Для операции записи: валидируем новый адрес и применяем его к интерфейсу. */
332 if (write && vnet_dev) {
333 if (strcmp(old_ip, vnet_ip_str) != 0) {
334 __be32 test_ip;
335
336 /* Сначала проверяем формат нового IPv4 адреса. */
337 if (!str_to_ip(&test_ip, vnet_ip_str, &vnet_ip_lock)) {
338 pr_err("VNET: неверный формат IP-адреса: %s\n", vnet_ip_str);
339 strcpy(vnet_ip_str, old_ip);
340 return -EINVAL;
341 }
342
343 pr_info("VNET: IP изменён: %s -> %s\n", old_ip, vnet_ip_str);
344
345 /* Порядок обновления: удалить старый адрес, затем добавить новый. */
346 ret = modify_vif_ip_userspace(VNET_IFNAME, old_ip, false);
347 if (ret) {
348 pr_err("VNET: не удалось удалить старый IP-адрес %s (ошибка %d)\n", old_ip, ret);
349 strcpy(vnet_ip_str, old_ip);
350 return ret;
351 }
352 pr_info("VNET: старый IP-адрес удалён: %s\n", old_ip);
353
354 /* После успешного удаления назначаем новый адрес. */
356 if (ret) {
357 pr_err("VNET: не удалось добавить новый IP-адрес %s (ошибка %d)\n", vnet_ip_str, ret);
358 strcpy(vnet_ip_str, old_ip);
359 return ret;
360 }
361
362 pr_info("VNET: IP-адрес интерфейса обновлён до %s\n", vnet_ip_str);
363 }
364 }
365
366 return 0;
367}
368
376static int modify_vif_ip_userspace(const char *name, const char *ip_str, bool is_add) {
377 char *argv[8];
378 char cmd[256];
379 int i = 0;
380
381 if (!name || !ip_str)
382 return -EINVAL;
383
384 /* Формируем shell-команду для add/del адреса на интерфейсе. */
385 if (is_add) {
386 snprintf(cmd, sizeof(cmd), "/sbin/ip addr add %s/24 dev %s 2>/dev/null", ip_str, name);
387 } else {
388 snprintf(cmd, sizeof(cmd), "/sbin/ip addr del %s/24 dev %s 2>/dev/null", ip_str, name);
389 }
390
391 argv[i++] = "/bin/sh";
392 argv[i++] = "-c";
393 argv[i++] = cmd;
394 argv[i] = NULL;
395
396 pr_info("VNET: выполнение команды: %s\n", cmd);
397
398 /* call_usermodehelper запускает /bin/sh -c "<ip command>" и ждёт завершения. */
399 return call_usermodehelper(argv[0], argv, NULL, UMH_WAIT_PROC);
400}
401
404
406MODULE_AUTHOR("Dmitry Chernikov");
407MODULE_DESCRIPTION("Virtual Net Interface");
Приватные данные интерфейса vnet.
Definition virtual_net.h:58
struct rtnl_link_stats64 stats
Definition virtual_net.h:59
static void vnet_get_stats64(struct net_device *dev, struct rtnl_link_stats64 *stats)
Возвращает статистику интерфейса для ndo_get_stats64.
MODULE_LICENSE("MIT")
MODULE_DESCRIPTION("Virtual Net Interface")
static void vnet_arp_reply(struct sk_buff const *skb, struct net_device *dev, __u32 target_ip)
Формирует и отправляет ARP reply.
static int __init virtual_net_init(void)
Инициализирует модуль: sysctl, интерфейс и стартовый IP.
Definition virtual_net.c:22
module_init(virtual_net_init)
static netdev_tx_t vnet_xmit(struct sk_buff *skb, struct net_device *dev)
Обрабатывает skb интерфейса и эмулирует ответное сетевое поведение.
static void __exit virtual_net_exit(void)
Освобождает ресурсы модуля при выгрузке.
Definition virtual_net.c:88
module_exit(virtual_net_exit)
static bool str_to_ip(__u32 *ip_out, const char *ip_str, spinlock_t *lock)
Преобразует IPv4 строку в 32-битный адрес в network byte order.
static int modify_vif_ip_userspace(const char *name, const char *ip_str, bool is_add)
Изменяет IPv4 адрес интерфейса через userspace-команду ip.
static int vnet_sysctl_handler(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos)
Обработчик sysctl для /proc/sys/net/vnet/ip_addr.
MODULE_AUTHOR("Dmitry Chernikov")
Общие определения для модуля виртуального сетевого интерфейса.
static const struct net_device_ops vnet_ops
Операции сетевого устройства vnet.
Definition virtual_net.h:63
static struct ctl_table vnet_table[]
Таблица sysctl для /proc/sys/net/vnet/ip_addr.
Definition virtual_net.h:69
#define VNET_IFNAME
Имя создаваемого виртуального интерфейса.
Definition virtual_net.h:19
static struct net_device * vnet_dev
Указатель на зарегистрированное сетевое устройство vnet.
Definition virtual_net.h:31
static struct ctl_table_header * vnet_sysctl_header
Дескриптор Зарегистрированной таблицы sysctl.
Definition virtual_net.h:29
static char vnet_ip_str[16]
Текущее значение IPv4 адреса интерфейса в строковом виде.
Definition virtual_net.h:25