Рубрики
Конспект

Сети linux. Модели и приложения / Конспект

Уровни OSI

1. Физический 
2. Канальный 
3. Сетевой
4. Транспортный
5. Сеансовый
6. Представительный
7. Прикладной

Уровни OSI TCP/IP


1. Канальный (модуль ядра, драйвер устройства и сетевой интерфейс) L2
2. Сетевой(IP, ICMP, IGMP) L3
3. Транспортный (UDP, TCP, SCTP, DCCP) L4
4. Прикладной (http, https, telnet, ftp, e-mail, smtp, pop3, imap4 и т.д.)

MAC / ARP:

L2
В масштабе сегмента локальной сети аппаратные адаптеры обмениваются пакетами только по MAC-адресам.
Отдельного ARP протокола для ipv6 не существует.(NDP)
Протоколы канального уровня(L2) - немаршрутизируемые
Для выхода за пределы L2 должны быть как то инкапсулированы в пакет маршрутизируемого протокола.
Чаще всего этим занимается протокол IP 
Для этого применяется ARP, RARP для IPV4 и NDP для ipv6. RFC-826
Преобразованием MAC в IP занимается ядро ОС.

macchanger - программа для работы с MAC адресами
macchanger eth0 -s - показать MAC адрес eth0 


inxi - программа покажет ваше железо
inxi -Nxxx - покажет вашу сетевую карту
inxi -C - ваш процессор
inxi -D - ваши диски

Посмотреть таблицу MAC:
arp -en
arp -vn

ip -4 neigh show
ip -6 neigh show

IP адреса:

L3
Каждый сетевой интерфейс имеет свой IP-адрес.
Конечные точки коммуникации знают о друг друге только по их IP адресу

Маски и подсети:

Разделение на классы
A - 0.0.0.0 - 127.255.255.255 - сверх большие подсети 
B - 128.0.0.0 - 192.255.255.255 - большие подсети
C - 192.0.0.0 - 223.255.255.255 - малые подсети
D - 224.0.0.0 - 239.255.255.255 - групповые операции
E - 240.0.0.0 - 247.255.255.255 - резерв

!!! 25 ноября 2019 года В RFC1219 классы с введение общих масок утратили свой смысл кроме групповых операций (класс D)


Широковещательный и групповой обмен:

Всего существует три типа IP-адресов
персональный / unicast
широковещательный / broadcast
групповой / multicast

Широковещательные и групповые запросы применимы только к UDP и SCTP - подобные типы запросов 
позволяют приложению разослать одно сообщение нескольким получателям одновременно.

TCP - протокол, ориентированный на соединение, с его помощью устанавливается соединение между двумя сетевыми интерфейсами
(по указанному их IP-адресу с использованием одного адресата на каждом хосте(который идентифицируется по номеру порта))

Широковещательный адрес подсети(subnet-directed broadcast address) имеет идентификатор хоста, определяемый маской и установленный во все единицы
(самый старший адрес диапазона адресов подсети, а самый младший адрес этого диапазона есть адрес самой подсети):
192.168.1.5 - host
255.255.255.0 - netmask
192.168.1.255 - broadcast

Групповой адрес (multicast group address) состоит из четырех старших битов IP,
установленных в 11110,  идентификатора группы (28 младших битов, определяющих конкретную группу).
224.0.0.0 до 239.255.255.255

Для работы с мультикас существует специальный протокол IGMP RFC1112
Internet Management Protocol

Частные адреса:

Никогда не маршрутизируются шлюзами IP.
Предназначены для организации локальных сетей.

10.0.0.0 - 10.255.255.255 (10/8)
172.16.0.0 - 172.31.255.255 (172.16/12)
192.168.0.0 - 192.168.255.255 (192.168./16)

Решение проблемы с передачей трафика из частных сетей:
1. На сетевом уровне
NAT, Network Address Translation
(еще эту технологи называют Masquerading, Network Masquerading, Native Adress Translation)
Для того что бы такие сети могли общается с внешним миром использую такие технологии как NAT 
При этом хост IP-шлюза подменяет в маршрутизируемых пакетах IP-адрес отправителя, подставляя IP-адрес собственного интерфейса, 
после получения пакета производится обратная операция.
Проекты реализующую NAT (от старых к новым)
ipfw, ipfilter, iptables
Сейчас чаще всего встретите iptables (nftable)

2. На уровне протоколов прикладного уровня - прокси (proxy и посредники)
При этом программа прокси-сервера ретранслирует запрос хоста с частным IP-адресом от своего имени в интерфейс с глобальным IP-адресом
squid / tinyproxy

IPv6

6июня 2012 года крупнейшие провайдеры единовременно перешли к использованию - параллельно с IPv4 - IPv6
IPv6 имеет длину 128Бит (вместо 32 у IPv4)
Важнейшей отличие от IPv4:
Здесь нету отличия между глобальными хостами подключенным к интернету и локальными хостами находящимися в лан.
Здесь любой хост равнозначно доступен из вне, если он не закрыт фаерволом.
Нет масок.(есть префикс, но это другое)

IPv6 три типа адресов:
unicast - идентификатор одиночного интерфейса. 
Пакет, посланный по униксту доставляется интерфейсу по указанному адресу.
anycast - идентификатор набора интерфейсов
Пакет, посланный по эникастному адресу, доставляется одному из интерфейсов, указанному в адресе (зависит от настройки маршрутизации)
multicast - идентификатор набора интерфейса(обычно принадлежит разным узлам)
Пакет доставляется всем инерфесам, заданным этим адресом.


Префикс адреса
В отличии от предыдущей версии протокола, в IPv6 не применяются маски подсети, т.к они получились бы очень длинными, - вместо этого используется префикс,
который записывается так же через слеш после адреса.
Например префикс /64 означает что из 128 битов первые 64 - это сеть, а оставшаяся часть ( 64) -это хосты

Сам адрес записываю в шестнадцатеричном виде. 
[8 хексетов] X [по 16 бит] = [128 битов]


Зарезервированные диапазоны:
fe80::/10 называются link-local
Любой адрес из этого диапазона предназначен для авто конфигурирования ОС
Пакеты отправленные с такого адреса, не пропустит ни один маршрутизатор ограничивая их распространение в пределах сегмента локальной сети 
Протокол ipv6 спроектирован так что link-local адрес быть обязан.

2000://3 уникальные юникаст-адресов в Интернете. 
Выдает и регулирует IANA.

ff00::/8 - это адрес мультикаста. 
Например можно получить отклик всех интерфейсов
ping6 -c2 ff02::1%eno1


Работа с адресами IPv6
Пример адреса:
2001:0DB8:AA10:0001:0000:0000:0000:00FB
Можно убрать начальные 0
2001:DB8:AA10:1:0000:0000:0000:FB
Можно сократить еще нули
2001:DB8:AA10:1::FB

Для петли:
0000:0000:0000:0000:0000:0000:0000:0001
::1

Локальные адреса
Глобальный адрес IPv6  (global address) Действует в интернете. Выдает IANA.
Локальные адреса IPv6 (unique local address) Используются внутри организации.
Локальные адреса канала связи(link-local address) не маршрутизируются (fe80::/10) Действуют в рамках одного сегмента.


Пинг
Для использования команды ping6 требуется указывать суфикс интерфейса (%номер_ipa_a или %имя_фейса)
ping -6 -c3 fe80::f1:f1%eno1
в качестве cуфикса можно указывать номер интерфейса выдаваемый командой ping 
ping -6 -c3 fe80::f1:f1%2


curl [221:58c9:9a6:99be:f3d:clac:2b5b:9771]
wget https://curl [221:58c9:9a6:99be:f3d:clac:2b5b:9771]:80/index.php

Адресные переменные в программном коде / функция inet_pton{}

Функция inet_pton()  преобразовывает символьные изображения IP адреса в структуру адреса

int inet_pton(int af, const char *src, void *dst);

af - семейство адресов, которые может быть записано только одной из символьных констант:
     AF_INET или AF_INET6 означает IP-адрес IPv4 или IPv6 ()
src - символьная строка, в которой записан исходный IP-адрес
     Для IPv4 записывается в привычной точечной нотации: d.d.d.d, где d  десятичное число от 0-255
     Для IPv6 несколько форматов, h:h:h:h:h:h:h:h или h:h:h:h:h:h:d.d.d.d, где h - это 4 знаков шестнадцатеричные числа (0-ffff)
     кроме того, нулевые адресные группы IPv6, как обычно могут пропускаться, например ::FFFF:204.152.189.116;
dst - результат преобразования, структура адреса, вид который зависит от семейства адресов:
для AF_INET это (определяется в ):
     struct in_addr{
          __br32 s_addr;
     };
для AF_INET6 это (определяется в :
     struct in6_addr P
             union {
                     __u8   u6_addr8[16]
            ...
            } in6_u;

В ядре LInux определено весьма много поддерживаемых семейств адресов, что сильно усложняет поиск информации:
cat socket.h | grep 'AF_' | wc -l
             
Возвращаемые значения функции inet_pton:
1 =  успешное преобразование.
0 = Строка src не содержит строчное представление в специфицированном семействе адресов.
-1 = в качестве параметра af указано недопустимое семейство адресов, при этом глобальная переменная errno устанавливается в EAFNOSUPPORT

Адресные переменные в программном коде / функция inet_ntop()

Функция inet_ntop делает в точности на оборот что делала функция inet_pton
Преобразовывает структуру сетевого адреса в символьное изображение IP-адреса.
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
af -  семейство адресов AF_INET или AF_INET6
dst - строка, результат преобразования, это же значение возвращается inet_ntop() при успешном выполнение
size - длина запрашиваемой строки результата, специфицируемая при вызове, это может быть символьная константа типа INET6_ADDRSTRLEN


В заголовочном файле  представлено еще весьма много полезных функций

Код программы adr для преобразования ip адреса

cat > adr.c << "EOF"
#include 
#include 
#include 
#include 

int main(int argc, char *argv[]) {
   unsigned char buf[sizeof(struct in6_addr)];
   int domain, s;
   char str[INET6_ADDRSTRLEN];
   if (argc != 3) {
      fprintf(stderr, "Usage: %s {i4|i6|} stringn", argv[0]);
      exit(EXIT_FAILURE);
   }
   domain = (strcmp(argv[1], "i4") == 0) ? AF_INET :
            (strcmp(argv[1], "i6") == 0) ? AF_INET6 :
            atoi(argv[1]);
   s = inet_pton(domain, argv[2], buf);
   if (s <= 0) {
      if (s == 0)
         fprintf(stderr, "Not in presentation format");
      else
         perror("inet_pton");
         exit(EXIT_FAILURE);
   }
   if (inet_ntop(domain, buf, str, INET6_ADDRSTRLEN) == NULL) {
      perror("inet_ntop");
      exit(EXIT_FAILURE);
   }
   printf("%sn", str);
   exit(EXIT_SUCCESS);
}
EOF

Для компиляции используй:
gcc -o adr adr.c 
Примеры:
./adr i6 0:0:0:0:0:0:0:0
./adr i6 1:0:0:0:0:0:0:9
./adr 2001:DB8:AA10:1:0000:0000:0000:FB
./adr i6 0:0:0:0:0:FFFF:204.152.189.116
./adr i4 204.152.189.116

DNS / NS / разрешение имен

Человеку сложно запомнить цифры, запомнить слова проще, собственно это и привело к появлению DNS.
Domain Name System

До зарождения DNS был файл /etc/hosts
Заполнялся вручную.

Программы:
nslookup qnx.org.ru 1.1.1.1
nslookup qnx.org.ru 8.8.8.8

host rus-linux.net

dig @8.8.4.4 b14esh.com


Разрешение имен в программном коде:

cat > gclie.c << "EOF"
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUF_SIZE 500

int main(int argc, char *argv[]) {
   struct addrinfo hints;
   struct addrinfo *result, *rp;
   int sfd, s, j;
   size_t len;
   ssize_t nread;
   char buf[BUF_SIZE];
   if (argc < 3) {
      fprintf(stderr, "Usage: %s host port msg...n", argv[0]);
      exit(EXIT_FAILURE);
   }
   /* Obtain address(es) matching host/port */
   memset(&hints, 0, sizeof(struct addrinfo));
   hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
   hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
   hints.ai_flags = 0;
   hints.ai_protocol = 0;          /* Any protocol */
   s = getaddrinfo(argv[1], argv[2], &hints, &result);
   if (s != 0) {
      fprintf(stderr, "getaddrinfo: %sn", gai_strerror(s));
      exit(EXIT_FAILURE);
   }
   /* getaddrinfo() returns a list of address structures.
      Try each address until we successfully connect(2).
      If socket(2) (or connect(2)) fails, we (close the socket
      and) try the next address. */
   for (rp = result; rp != NULL; rp = rp->ai_next) {
      sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
      if (sfd == -1) continue;
      if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
         break;                    /* Success */
      close(sfd);
   }
   if (rp == NULL) {               /* No address succeeded */
      fprintf(stderr, "Could not connectn");
      exit(EXIT_FAILURE);
   }
   freeaddrinfo(result);           /* No longer needed */
   /* Send remaining command-line arguments as separate
      datagrams, and read responses from server */
   for (j = 3; j < argc; j++) {
      len = strlen(argv[j]) + 1;
      /* +1 for terminating null byte */
      if (len + 1 > BUF_SIZE) {
         fprintf(stderr, "Ignoring long message in argument %dn", j);
         continue;
      }
      if (write(sfd, argv[j], len) != len) {
         fprintf(stderr, "partial/failed writen");
         exit(EXIT_FAILURE);
      }
      nread = read(sfd, buf, BUF_SIZE);
      if (nread == -1) {
         perror("read");
         exit(EXIT_FAILURE);
      }
      printf("Received %ld bytes: %sn", (long)nread, buf);
   }
   exit(EXIT_SUCCESS);
}
EOF



cat > gserv.c << "EOF"
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUF_SIZE 500

int main(int argc, char *argv[]) {
   struct addrinfo hints;
   struct addrinfo *result, *rp;
   int sfd, s;
   struct sockaddr_storage peer_addr;
   socklen_t peer_addr_len;
   ssize_t nread;
   char buf[BUF_SIZE];
   if (argc != 2) {
      fprintf(stderr, "Usage: %s portn", argv[0]);
      exit(EXIT_FAILURE);
   }
   memset(&hints, 0, sizeof(struct addrinfo));
   hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
   hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
   hints.ai_flags = AI_PASSIVE;     /* For wildcard IP address */
   hints.ai_protocol = 0;          /* Any protocol */
   hints.ai_canonname = NULL;
   hints.ai_addr = NULL;
   hints.ai_next = NULL;
   s = getaddrinfo(NULL, argv[1], &hints, &result);
   if (s != 0) {
      fprintf(stderr, "getaddrinfo: %sn", gai_strerror(s));
      exit(EXIT_FAILURE);
   }
   /* getaddrinfo() returns a list of address structures.
      Try each address until we successfully bind(2).
      If socket(2) (or bind(2)) fails, we (close the socket
      and) try the next address. */
   for (rp = result; rp != NULL; rp = rp->ai_next) {
      sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
      if (sfd == -1)
      continue;
      if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
         break;                   /* Success */
      close (sfd);
   }
   if (rp == NULL) {               /* No address succeeded */
      fprintf(stderr, "Could not bindn");
      exit(EXIT_FAILURE);
   }
   freeaddrinfo(result);          /* No longer needed */
   /* Read datagrams and echo them back to sender */
   for (;;) {
      peer_addr_len = sizeof(struct sockaddr_storage);
      nread = recvfrom(sfd, buf, BUF_SIZE, 0,
                       (struct sockaddr*)&peer_addr, &peer_addr_len);
      if (nread == -1)
         continue;                /* Ignore failed request */
      char host[NI_MAXHOST], service[NI_MAXSERV];
      s = getnameinfo((struct sockaddr*) &peer_addr,
                      peer_addr_len, host, NI_MAXHOST,
                      service, NI_MAXSERV, NI_NUMERICSERV);
      if (s == 0)
         printf("Received %ld bytes from %s:%sn",
                (long)nread, host, service);
      else
         fprintf(stderr, "getnameinfo: %sn", gai_strerror(s));
      if (sendto(sfd, buf, nread, 0,
                 (struct sockaddr*)&peer_addr,
                 peer_addr_len) != nread)
         fprintf(stderr, "Error sending responsen");
   }
}
EOF



Для компиляции используй:
gcc -o gclie gclie.c
gcc -o gserv gserv.c 


Запуск:
./gserv 6000 
./gclie localhost 6000 privet
./gclie 127.0.0.1 6000 privet

Что тут происходи:

Функция  
#include 
struct hostent *gethostbyname(const char *name);
Функция возвращает информации о хосте по его имени
struct hostent {
 char *h_name;        /* Official name of host. */
 char **h_aliases;    /* Alias list. */
 int h_addrtype;      /* Host address type AF_INET or AF_INET6 */
 int h_length;        /* Length of address 4 or 16 */
 char **h_addr_list;  /* List of addresses from name server. */

Функция gethostbyaddr() возвращает информацию о хосте по IP-адресу

Функция getaddrinfo()ведена более поздним POSIX, скрывает все зависимости в параметрах от типа протокола


 

Пример Makefile для adr, gclie, gserv

cat > Makefile << "EOF"
CC = gcc -Wall

SRC = adr gclie gserv

all:    $(SRC)

clean disclean:
	rm -f $(SRC)
EOF

# Исправить восемь пробелов на TAB
sed -i 's/        /tt/g' Makefile 

Для компиляции используй:
make

Сетевые интерфейсы:

Посмотреть текущие сетевые интерфейсы:
ip link
ifconfig 

В каталоге /proc
ls /proc/sys/net/ipv4/conf
ls /proc/sys/net/ipv4/conf/eth0/
ls -w80 /proc/sys/net/ipv4/conf/eth0/

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

Пример именования интерфейсов и их назначения(ip link / ip a / ifconfig):

cipsec0 - Виртуальная частная сеть, VPN, "Cisco Systems VPN Client от Cisco Systems", работает через реальный физический канал.

eml - интерфейс физического проводного Ethernet-адаптера 

eth0- интерфейс физического проводного Ethernet-адаптера 

enp1- интерфейс физического проводного Ethernet-адаптера 

wlan0 - интерфейс физического беспроводного Wi-Fi-адаптера 

wlo1 - интерфейс физического беспроводного Wi-Fi-адаптера 

ppp0 - это может быть беспроводной 3G CMDA модем на USB-шине

lo - логический петлевой интерфейс

Проверка доступности ip адреса
ping 10.0.0.1


Основные команды для работы c Ethernet
ifconfig --help
ip help
ip a 
ip addr show dev eth0
ip link
ip addr help

Основные команды для работы с Wi-Fi
iwconfig
iw wlan0 info
rfkill
rfkill list
iw phy0 info

Графическая программа для управления сетью NetworkManager

Консольная программа для управления NetworkManager
nmcli --help

Таблица маршрутизации (роутинг)

route -n
route -n -6
ip route --help
ip route show
ip -6 route show 

Если все рассматриваемые ранее параметры: IP-адрес, маска, префикс и д.р. - это атрибуты сетевого интерфейса,
то таблица роутинга - это атрибут сетевого хоста в целом, это таблица ядра операционной системы.
0.0.0.0 - default - шлюз последней надежды

Алиасные IP-адреса / дополнительный IP адрес из другой сети

ifconfig eno1:1 10.0.0.2/24 up
команда ip позволяет сразу навесить скольго угодна ip на один интерфейс.
ip a a 10.10.0.1/24 dev eno1
ip a a 10.10.1.1/24 dev eno1
ip a a 10.10.2.1/24 dev eno1

Петлевой интерфейс:

На любом компьютере хосте, даже если он никак не использует сеть (не подключен к сети),
присутствует петлевой интерфейс (loopback, интерфейс lo)
под петлевой интерфейс выделена группа 127/8 и ::1

Петлевой интерфейс позволяет программам на компьютере использовать стек TCP/IP локально.


Переименование сетевого интерфейса

ip link set dev eno1 name eth0

Альтернативные имена (ядро выше 5.4.0)

ip link property add dev eth0 altname eno99

Порты транспортного уровня:

Каждому протоколу более высоких уровней (SSH,RDP,FTP,HTTP,HTTPS, etc) соответствует свой стандартный порт.
Порты бываю: TCP или UDP
Порты разделены на три:
общеизвестные / системны 0-1024 (всегда требуются права root)
зарегистрированные / пользовательские 1024-49151 (зарегистрировала комиссия IANA официально)
динамические / частные 49152-65535

Посмотреть порты:
cat /etc/services 

Посмотреть сколько портов знает система:
grep -v ^$ /etc/services | grep -v ^# | wc -l

Номера портов можно и используется разные, например для HTTP никто не может веб серверу запретить использовать порт 8888 вместо 80. 



Датаграммная передача - UDP
Потоковая передача - UDP

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

Потоковый протокол TCP ориентирован на надежную доставку информации.
В случае потери пакетов или их искажения на тракте передающая сторона делает многократные попытки повторения передачи.

Протокол UDP определен в RFC 768.
Протокол TCP посвящены RFC 790, 791, 793, 1025, 1323.

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

Инструменты диагностики:

посмотреть линки:
ip link
Проверить таблицу маршрутизации:
route -n

Инструменты наблюдения:

ping 8.8.8.8

traceroute 8.8.8.8

netstat -i

netstat -t 

ss -tplnu

arp

nslookup google.com

nslookup 8.8.8.8

host ya.ru

dig google.com

tcpdump -i eth0

id 

who

ps -uaxf 

Инструменты тестирования:

scp test root@10.10.10.10:
sftp name@6.6.6.6:/home/name/1.txt

nc -l 1234
nc 127.0.0.1 1234

iperf -s 
iperf -c 10.10.10.1
iperf -c 10.10.10.1 -P4

iperf3 -s 
iperf3 -c 10.10.10.1
iperf3 -c 10.10.10.1 -P4

Сервисы сети и systemd

Все сетевые продукты состоят из трех основных компонентов
1) Протокол
2) Клиент
3) Сервис


mc (Midnight Commnader)

Есть очень удобный способ подключится по ssh к удаленной машине
Называется он "Shell-соединение"
перед использованием этого способа убедитесь что у вас подключается по ssh.

TAB - перемещение между правой и левой лакацией
Enter - Свободное перемещение по каталогам
F5 - копировать
F6 - перемещать
F3 - просматривать
F4 - редактировать

SSH

Графическая сессия 
ssh -X user@server xclock
ssh -Y user@server VirtualBox

Не все так может быть запущенно, но многие программы работают.
Не которые программы сами умеют подключатся через shell.
Например mc, virt-manager и etc

Протокол DHCP

DHCP
Dynamic Host Configuration Protocol

Протокол DHCP - это клиент-серверный протокол, который автоматически доставляет хосту в момент его загрузки IP-адрес (из определенного пула адресов)
и другие связанные сведения о конфигурации, такие как маска подсети и шлюз по умолчанию

RFC2131 и 2132 - определяют протокол DHCP в качестве стандарта "Internet Engineering Task Force" (IETF)
основанного на более раннем протоколе загрузке BOOTP

В сети не может быть более одного DHCP сервера, так как могут возникнуть проблемы раздачи IP-адресов

Самый популярный в linux DHCP сервер это isc-dhcp-server
Чаще всего встретите после него это dnsmasq

Разрешение имен: служба DNS

Domain name system

Сам DNS-протокол базируется на UDP-, либо на TCP-транспорте, на ранних этапах это было только UDP
Используется по умолчанию порт 53 (TCP/UDP)

Самый известный DNS-резолвер bind

Самый просто и кеширующий DHCP/DNS-сервер dnsmasq

Самый частый в systemd это systemd-resolved.services (чаще всего выключен, 
но настраивается очень просто, нужно отредактировать "DNS=" в файле "/etc/systemd/resolved.conf" 
и включить демон командой "sytemctl enable systemd-resolved.services && sytemctl start systemd-resolved.services") 
resolvectl status


# для debian11-12 может пригодится для восстановления systemd-resolved
#  ln -s /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf

Немного про конфигурацию dnsmasq

ls /etc/dnsmasq.d/*.conf

конфиги могут быть разнесены по файла 
dhcp.conf
dns.conf
так и могут быть в одном файле dnsmasq.conf

dhcp.conf
---------
interface=eth1                                           # - интерфейс на котором будет работать dhcp
dhcp-range=192.168.25.2,192.168.25.150,255.255.255.0,12h # - объявляем диапазон для аренды
dhcp-option=option:router,192.168.25.1                   # - default gateway
log-dhcp                                                 # - включить логи
---------

dns.conf
--------
listen-address=127.0.0.1                # - принимаем запросы только из петли
interface=eth1                          # - слушаем только на этом интерфейсе
domain-needed                           # - никогда не присылать адреса без доменной части
bogus-priv                              # - никогда не пересылать адреса из не маршрутизированного пространства
stop-dns-rebind                         # - отклонять ответы от вышестоящих DNS-серверов
rebind-localhost-ok                     # - отключить проверку для 127.0.0.0/8
strict-order                            # - пересылать запросы с первого по порядку
no-resolv                               # - не использовать /etc/resolv.conf
no-hosts                                # - не читать /etc/hosts
domain=localdomain
local-ttl=7200
neg-ttl=14400
max-ttl=86400
server=192.168.25.1
server=8.8.8.8
server=8.8.4.4
--------

iptables

iptables --help

Просмотр правил основных цепочек INPUT  FORWARD  OUTPUT
iptables -L -v -n
iptables-save

Просмотр правил трансляции адресов NAT:
iptables -t nat -L -line-numbers

Разрешить все входящие пакеты (таблицу filter можно не указывать она дефолтная):
iptables -f filter -I OUTPUT 1 -j ACCEPT

Запретить все исходящие пакеты из подсети 10.10.10.0/24
iptables -t filter -A INPUT -s 10.10.10.0/24 -j DROP

Разрешение SSH:
iptables -A INPUT -p tcp --dport 22 -J ACCEPT

Разрешить диапазон входящих портов TCP:
iptables -A -p tcp --dport 3000:4000 -j ACCEPT

Разрешить ICMP:
iptables -A INPUT -p icmp -j ACCEPT

Systemd и сокетная активация сервисов на примере pure-ftpd

!!! Врятли у вас получится сделать это на современных дистрибутивах, так как файлы для запуска сервиса будут установлены автоматически.
apt install pure-ftpd

cat > /usr/lib/systemd/system/pure-ftpd.socket << "EOF"
[Unit]
Descrtiption=pure-ftp FTP server Activation Socket
Conflicts=pure-ftpd.service

[Socket]

ListenStream=21
Accept=true

[Install]
WantedBy=socket.target

EOF

cat > /usr/lib/systemd/system/pure-ftpd@.service << "EOF"
[Unit]
Descriptiom=pure-ftpd FTP Server
After=network-online.target

[Service]
ExecStart=-/usr/sbin/pure-ftpd
StandartInput=socket

EOF

systemctl status pure-ftpd
systemctl stop pure-ftpd
systemctl status pure-ftpd
systemct start pure-ftpd.socket
systemct status pure-ftpd.socket

Прокси серверы:

Преодоление текториальных ограничений 
Защита компьютера клиента от атак с наружи
Позволяет сохранить анонимность клиента путем подмены IP
Проксирование на уровне протоков HTTP, HTTPS
Проксирование на уровне L4 UDP, TCP  SOCKS4 и SOCKS5

SOCKS5 - допускает возможность UDP, есть аутентификация

Самый распространённый  SOCKS5 прокси сервер это Dante
apt search Dante
apt install dante-client - научит любое приложение ходить в прокси
apt install dante-serve - прокси сервер

Пока вырубим его 
systemctl disable danted
systemctl stop danted

Простой конфиг без авторизации 
vim /etc/danted.conf
--------------------
logoutput: stderr
internal: eth0 port = 1080
external: eth0 
socksmethod: none
user.privileged: proxy
user.unprivileged: nobody
user.libwrap: nobody
client pass {
       from: 0/0 to: 0/0
       log: connect error
}
socks pass {
      from: 0/0 to: 0/0
      log: connect error
}
--------------------
тут:
internal - входящий интерфейс
external - исходящий 
port - порт прокси
socksmethod - собственно нету авторизации 

Для авторизации изменить конфиг так:
socksmethod: username 
socks pass {
      from: 0/0 to: 0/0
      log: connect disconnect error ioop
      group: proxy
}

и создать пользователя
useradd --shell /usr/sbin/nologin proxy_user_01
passwd proxy_user_01
usermod -a -G proxy proxy_user_01

Прокси через SSH

ssh -N -D 5555 10.10.10.22
curl -s  -x socks4://127.0.0.1:5555 ya.ru
curl -s  -x socks5://127.0.0.1:5555 ya.ru

Прокси squid

#Debian 12 install squid

Устанавливаем:
apt install squid

Сохраняем оригинальный конфиг squid:
cp /etc/squid/squid.conf /etc/squid/squid.conf.backup
( можно вот так вот обработать  cat squid.conf.bak | grep -v "^#" | grep -v "^$" > squid.conf  тем самым убрав комментарии) 

Приводим конфиг к такому виду:
cat > /etc/squid/squid.conf << "EOF"
acl localnet src 0.0.0.1-0.255.255.255  # RFC 1122 "this" network (LAN)
acl localnet src 10.0.0.0/8             # RFC 1918 local private network (LAN)
acl localnet src 100.64.0.0/10          # RFC 6598 shared address space (CGN)
acl localnet src 169.254.0.0/16         # RFC 3927 link-local (directly plugged) machines
acl localnet src 172.16.0.0/12          # RFC 1918 local private network (LAN)
acl localnet src 192.168.0.0/16         # RFC 1918 local private network (LAN)
acl localnet src fc00::/7               # RFC 4193 local private network range
acl localnet src fe80::/10              # RFC 4291 link-local (directly plugged) machines
acl SSL_ports port 443
acl Safe_ports port 80          # http
acl Safe_ports port 21          # ftp
acl Safe_ports port 443         # https
acl Safe_ports port 70          # gopher
acl Safe_ports port 210         # wais
acl Safe_ports port 1025-65535  # unregistered ports
acl Safe_ports port 280         # http-mgmt
acl Safe_ports port 488         # gss-http
acl Safe_ports port 591         # filemaker
acl Safe_ports port 777         # multiling http
acl CONNECT method CONNECT
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localhost manager
http_access deny manager
include /etc/squid/conf.d/*
http_access allow localhost
http_access deny all
http_port 3128
coredump_dir /var/spool/squid
refresh_pattern ^ftp:           1440    20%     10080
refresh_pattern ^gopher:        1440    0%      1440
refresh_pattern -i (/cgi-bin/|?) 0     0%      0
refresh_pattern .               0       20%     4320
EOF

И этот конфиг приводим к такому виду:
cat > /etc/squid/conf.d/debian.conf << EOF
#
# Squid configuration settings for Debian
#

# Logs are managed by logrotate on Debian
logfile_rotate 0

# For extra security Debian packages only allow
# localhost to use the proxy on new installs
#
http_access allow localnet
EOF

Запускаем в зависимости от статуса: 
systemctl status squid
systemctl restart squid
systemctl start squid
systemctl enable squid



Мониторинг за прокси

apt install sockstat
sockstat -h 
sockstat -4 

Фазы соединения TCP

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

UGR - указатель срочности (urgent pointer) данных
ACK - номер подтверждения необходимо принять в рассмотрение (acknowledgment)
PSH - получатель должен передать эти данные приложению как можно скорее (push)
RST - сбросить соединение (reset)
YN - синхронизирующий номер последовательности установления соединения
FIN - отправитель закончил пересылку данных

Что бы установить TCP соединения, пересылаются три сегмента:
1. Запрашивающая сторона(клиент) отправляет сегмент (флаг) SYN, указывающий номер порта сервера, к которому клиент хочет подключится,
и исходный номер последовательности клиента ISN
2. Сервер отвечает своим сегментом SYN, содержащий номер последовательности сервера. Сервер также подтверждает риход SYN от клиента
с использованием флага ACK(указыва номер ISN клиента плюс один)
3. Клиент должен подтвердить приход SYN от сервера с использованием флага ACK(ISN сервера плюс один)
Этих трех сегментов достаточно для установления соединения.
Часто называют трехразовым рукопожатием (three-way handshake).


Любая сторона может разорвать соединение послав пакет с флагом FIN.
Чаще всего это делает сам клиент.
И это также сопровождается трёхразовым рукопожатием.



Пример работы с UDP TCP в коде :

cat > common.h << "EOF" 
#ifndef __common_h
#define  __common_h

#include 
#include 
#include 
#include 
#include 

#include 
#include  /* basic socket definitions */
#include 
#include 

#include "inet.h"

#define MAXLINE 4096                /* max text line length */
static char sendline[MAXLINE],     /* write buffer */
            recvline[MAXLINE + 1]; /* reaad buffer */

void  err_dump(const char *, ...);
void  err_sys(const char *, ...);

void str_cli(register FILE*, register int sockfd);
void str_echo(int sockfd);

void dg_cli(FILE* fp, int sockfd,
            struct sockaddr* pserv_addr,
            int servlen);
void dg_echo(int sockfd, 
             struct sockaddr* pcli_addr,  
             int maxclilen);
#endif
EOF


cat > dgcli.c  << "EOF"
/* Read the contents of the FILE *fp, write each line to the
 * datagram socket, then read a line back from the datagram
 * socket and write it to the standard output.
 * Return to caller when an EOF is encountered on the input file.
 */
#include "inet.h"

void dg_cli(FILE* fp, int sockfd,
            struct sockaddr* pserv_addr, // ptr to appropriate sockaddr_XX structure 
            int servlen) {               // actual sizeof(*pserv_addr) 
   int n;
   while (fgets(sendline, MAXLINE, fp) != NULL) {
      n = strlen(sendline);
      if (sendto(sockfd, sendline, n, 0, pserv_addr, servlen) != n)
         err_dump("dg_cli: sendto error on socket");
      // Now read a message from the socket and 
      // write it to our standard output.
      n = recvfrom(sockfd, recvline, MAXLINE, 0,
                   (struct sockaddr*)0, (socklen_t*)0);
      if (n < 0)
         err_dump("dg_cli: recvfrom error");
      recvline[n] = 0;    /* null terminate */
      fputs(recvline, stdout);
   }
   if (ferror(fp))
      err_dump("dg_cli: error reading file");
}
EOF


cat > dgecho.c << "EOF" 
/* Read a datagram from a connectionless socket and write it back to
 * the sender.
 * We never return, as we never know when a datagram client is done.
 */
#include "inet.h"

void dg_echo(int sockfd, 
             struct sockaddr* pcli_addr,  /* ptr to appropriate sockaddr_XX structure */
             int maxclilen) {             /* sizeof(*pcli_addr) */
   for (;;) {
      socklen_t clilen = maxclilen;
      int n = recvfrom(sockfd, recvline, MAXLINE, 0, pcli_addr, &clilen);
      if (n < 0) err_dump("dg_echo: recvfrom error");
      if (sendto(sockfd, recvline, n, 0, pcli_addr, clilen) != n)
         err_dump("dg_echo: sendto error");
   }
}




cat > udpcli.c << "EOF"
/* Example of client using UDP protocol. */
#include "inet.h"

int main(int argc, char* argv[]) {
   int sockfd;
   /* Open a UDP socket (an Internet datagram socket). */
   if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
      err_dump("client: can't open datagram socket");
   struct sockaddr_in serv_addr;
   /* Fill in the structure "serv_addr" with the address
    * of the server that we want to send to. */
   bzero((char*)&serv_addr, sizeof(serv_addr));
   serv_addr.sin_family      = AF_INET;
   serv_addr.sin_addr.s_addr = inet_addr(SERV_HOST_ADDR);
   serv_addr.sin_port        = htons(SERV_UDP_PORT);
   dg_cli(stdin, sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
   close(sockfd);
   exit(0);
}
EOF

cat > udpserv.c << "EOF"
// Example of server using UDP protocol.
#include	"inet.h"

int main(int argc, char* argv[] ) {
   /* Open a UDP socket (an Internet datagram socket). */
   int sockfd;
   if((sockfd = socket(AF_INET, SOCK_DGRAM, 0 ) ) < 0 )
      err_dump("server: can't open datagram socket" );
   /* Bind our local address so that the client can send to us. */
   struct sockaddr_in serv_addr;
   bzero((char*)&serv_addr, sizeof(serv_addr ) );
   serv_addr.sin_family      = AF_INET;
   serv_addr.sin_addr.s_addr = htonl(INADDR_ANY );
   serv_addr.sin_port        = htons(SERV_UDP_PORT );
   if(bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr ) ) < 0 )
      err_dump("server: can't bind local address" );
   dg_echo(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr ) );
}
EOF



cat > error.c << "EOF"
#include "common.h"
#include      /* ANSI C header file */
#include      /* for syslog() */

int daemon_proc;        /* set nonzero by daemon_init() */

static void err_doit(int, int, const char *, va_list);

/* Fatal error related to a system call.
 * Print a message and terminate. */
void err_sys(const char *fmt, ...) {
   va_list ap;
   va_start(ap, fmt);
   err_doit(1, LOG_ERR, fmt, ap);
   va_end(ap);
   exit(1);
}

/* Fatal error related to a system call.
 * Print a message, dump core, and terminate. */
void err_dump(const char *fmt, ...) {
   va_list ap;
   va_start(ap, fmt);
   err_doit(1, LOG_ERR, fmt, ap);
   va_end(ap);
   abort();             /* dump core and terminate */
   exit(1);             /* shouldn't get here */
}

/* Print a message and return to caller.
 * Caller specifies "errnoflag" and "level". */
static void err_doit(int errnoflag, int level, const char *fmt, va_list ap) {
   int errno_save, n;
   char buf[MAXLINE];
   errno_save = errno;                     /* value caller might want printed */
#ifdef HAVE_VSNPRINTF
   vsnprintf(buf, sizeof(buf), fmt, ap);   /* this is safe */
#else
   vsprintf(buf, fmt, ap);                 /* this is not safe */
#endif
   n = strlen(buf);
   if (errnoflag)
      snprintf(buf + n, sizeof(buf) - n, ": %s", strerror(errno_save));
   strcat(buf, "n");
   if (daemon_proc) {
      syslog(level, buf, "cli-serv : %s");
   } else {
      fflush(stdout);                        /* in case stdout and stderr are the same */
      fputs(buf, stderr);
      fflush(stderr);
   }
   return;
}
EOF



cat > inet.h << "EOF" 
/* Definitions for TCP and UDP client/server programs. */
#include "common.h"

#define SERV_UDP_PORT 60000
#define SERV_TCP_PORT 60000
//#define SERV_HOST_ADDR "192.168.1.13" /* host addr for server */
#define SERV_HOST_ADDR "127.0.0.1"      /* host addr for server */

ssize_t readline(int fd, void *vptr, size_t maxlen);
ssize_t writen(int fd, const void *vptr, size_t n);
EOF


cat  > Makefile << "EOF"
MYLIB   = error.o writen.o readline.o 
#LIBS   =
CFLAGS  = -O -Wall -Wno-unused-variable 

all:    $(MYLIB) tcp udp unixstr unixdg sockopt 
        rm -f *.o

lib:    $(MYLIB)

# Internet stream version (TCP protocol).
tcp:    tcpserv tcpcli

tcpcli.o tcpserv.o: inet.h common.h

tcpserv:        tcpserv.o strecho.o
                $(CC) $(CFLAGS) -o $@ tcpserv.o strecho.o $(MYLIB)

tcpcli:         tcpcli.o strcli.o
                $(CC) $(CFLAGS) -o $@ tcpcli.o strcli.o $(MYLIB)

# Internet datagram version (UDP protocol).
udp:    udpserv udpcli

udpcli.o udpserv.o: inet.h common.h

udpserv:        udpserv.o dgecho.o
                $(CC) $(CFLAGS) -o $@ udpserv.o dgecho.o $(MYLIB)

udpcli:         udpcli.o dgcli.o
                $(CC) $(CFLAGS) -o $@ udpcli.o dgcli.o $(MYLIB)

# UNIX stream version.
unixstr: unixstrserv unixstrcli

unixstrcli.o unixstrserv.o: unix.h common.h

unixstrserv:    unixstrserv.o strecho.o
                $(CC) $(CFLAGS) -o $@ unixstrserv.o strecho.o $(MYLIB)

unixstrcli:     unixstrcli.o strcli.o
                $(CC) $(CFLAGS) -o $@ unixstrcli.o strcli.o $(MYLIB)

# UNIX datagram version.
unixdg: unixdgserv unixdgcli

unixdgcli.o unixdgserv.o: unix.h common.h

unixdgserv:     unixdgserv.o dgecho.o
                $(CC) $(CFLAGS) -o $@ unixdgserv.o dgecho.o $(MYLIB)

unixdgcli:      unixdgcli.o dgcli.o
                $(CC) $(CFLAGS) -o $@ unixdgcli.o dgcli.o $(MYLIB)

sockopt:        sockopt.o common.h
                $(CC) $(CFLAGS) -o $@ sockopt.o $(MYLIB)

clean disclean:
        -rm -f *.o core a.out temp*.* 
                tcpserv tcpcli udpserv udpcli 
                unixstrserv unixstrcli unixdgserv unixdgcli 
                s.unixdg s.unixstr sockopt 
EOF

# Исправить восемь пробелов на TAB
sed -i 's/        /tt/g' Makefile 


cat > readline.c << "EOF"
#include "common.h"

static ssize_t my_read(int fd, char *ptr) {
   static int read_cnt = 0;
   static char *read_ptr;
   static char read_buf[MAXLINE];
   if (read_cnt <= 0) {
again:
      if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
         if (errno == EINTR) goto again;
         return(-1);
      } else if (read_cnt == 0)
         return(0);
      read_ptr = read_buf;
   }
   read_cnt--;
   *ptr = *read_ptr++;
   return(1);
}

ssize_t readline(int fd, void *vptr, size_t maxlen) {
   int n, rc;
   char c, *ptr;
   ptr = vptr;
   for (n = 1; n < maxlen; n++) {
      if ((rc = my_read(fd, &c)) == 1) {
         *ptr++ = c;
         if (c == 'n') break;   /* newline is stored, like fgets() */
      } else if (rc == 0) {
         if(n == 1) return(0);   /* EOF, no data read */
         else break;             /* EOF, some data was read */
      } else return(-1);         /* error, errno set by read() */
   }
   *ptr = 0;                     /* null terminate like fgets() */
   return(n);
}
EOF



cat > sockopt.c << "EOF" 
/* Example of getsockopt() and setsockopt(). */
#include "common.h"
#include 
#include   /* for SOL_SOCKET and SO_xx values */
#include   /* for IPPROTO_TCP value */
#include  /* for TCP_MAXSEG value */

int main() {
   int sockfd, maxseg, sendbuff; //, optlen;
   if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
      err_sys("can't create socket");
   /* Fetch and print the TCP maximum segment size. */

   socklen_t optlen = sizeof(maxseg);
   if (getsockopt(sockfd, IPPROTO_TCP, TCP_MAXSEG, (char*)&maxseg, &optlen) < 0)
      err_sys("TCP_MAXSEG getsockopt error");
   printf("TCP maxseg = %dn", maxseg);
   /* Set the send buffer size, then fetch it and print its value. */
   sendbuff = 16384; /* just some number for example purposes */
   if (setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF,
                  (char*)&sendbuff, sizeof(sendbuff)) < 0)
      err_sys("SO_SNDBUF setsockopt error");
   optlen = sizeof(sendbuff);
   if (getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF,
                  (char*)&sendbuff, &optlen) < 0)
      err_sys("SO_SNDBUF getsockopt error");
   printf("send buffer size = %dn", sendbuff);
}
EOF


cat > strcli.c << "EOF" 
/* Read the contents of the FILE *fp, write each line to the
 * stream socket (to the server process), then read a line back from
 * the socket and write it to the standard output.
 * Return to caller when an EOF is encountered on the input file.
 */
#include "common.h"

void str_cli(register FILE* fp, register int sockfd) {
   int n;
   while (fgets(sendline, MAXLINE, fp) != NULL) {
      n = strlen(sendline);
      if (writen(sockfd, sendline, n) != n)
         err_sys("str_cli: writen error on socket");
      /* Now read a line from the socket and 
       * write it to our standard output. */
      n = readline(sockfd, recvline, MAXLINE);
      if (n < 0) err_dump("str_cli: readline error");
      recvline[n] = 0;    /* null terminate */
      fputs(recvline, stdout);
   }
   if (ferror(fp))
      err_sys("str_cli: error reading file");
}
EOF



cat > strecho.c << "EOF"
/* Read a stream socket one line at a time, and write each line back
 * to the sender.
 * Return when the connection is terminated.
 */
#include "common.h"

void str_echo(int sockfd) {
   int n;
   for (;;) {
      n = readline(sockfd, recvline, MAXLINE);
      if (n == 0) return;      /* connection terminated */
      else if (n < 0) err_dump("str_echo: readline error");
      if (writen(sockfd, recvline, n) != n)
         err_dump("str_echo: writen error");
   }
}
EOF


cat > tcpcli.c << "EOF"
/* Example of client using TCP protocol. */
#include "inet.h"

int main(int argc, char* argv[]) {
   int sockfd;                    // TCP сокет 
   struct sockaddr_in serv_addr;  // заполнить структуру адреса сервера
   bzero((char*)&serv_addr, sizeof(serv_addr));
   serv_addr.sin_family      = AF_INET;
   serv_addr.sin_addr.s_addr = inet_addr(SERV_HOST_ADDR);
   serv_addr.sin_port        = htons(SERV_TCP_PORT);
   /* Open a TCP socket (an Internet stream socket). */
   if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
      err_sys("client: can't open stream socket");
   /* Connect to the server. */
   if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
      err_sys("client: can't connect to server");
   str_cli(stdin, sockfd);        // цикл обменов с сервером
   close(sockfd);
   exit(0);
}
EOF

cat > tcpserv.c << "EOF"
/* Example of server using TCP protocol. */
#include "inet.h"

int main(int argc, char* argv[]) {
   int sockfd;                   // TCP сокет 
   if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
      err_dump("server: can't open stream socket");
   struct sockaddr_in serv_addr; // инициализировать униадресом 
   bzero((char*)&serv_addr, sizeof(serv_addr));
   serv_addr.sin_family      = AF_INET;
   serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
   serv_addr.sin_port        = htons(SERV_TCP_PORT);
   if (bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
      err_dump("server: can't bind local address");
   listen(sockfd, 5);
   for (;;) {                    // цикл по подключения
      socklen_t clilen = sizeof(serv_addr);
      int childpid, newsockfd;
      newsockfd = accept(sockfd, (struct sockaddr*)&serv_addr, &clilen);
      if (newsockfd < 0)         // ожидать соединения
         err_dump("server: accept error");
      if ((childpid = fork()) < 0) 
         err_dump("server: fork error");
      else if (childpid == 0) {  // обрабатывать в дочернем процессе
         close(sockfd);          // закрыть копию прослушивающего сокета
         str_echo(newsockfd);    // ретранслировать полученные данные
         exit(0);                // завершить дочерний процесс
      }
      close(newsockfd);          // в роительском процессе
   }
}
EOF


cat > unixdgcli.c << "EOF"
/* Example of client using UNIX domain datagram protocol. */
#include "unix.h"

int main(int argc, char* argv[]) {
   int sockfd, clilen, servlen;
   char *mktemp();
   struct sockaddr_un cli_addr, serv_addr;
   /* Fill in the structure "serv_addr" with the address
    * of the server that we want to send to. */
   bzero((char*)&serv_addr, sizeof(serv_addr));
   serv_addr.sun_family = AF_UNIX;
   strcpy(serv_addr.sun_path, UNIXDG_PATH);
   servlen = sizeof(serv_addr.sun_family) + strlen(serv_addr.sun_path);
   /* Open a socket (a UNIX domain datagram socket). */
   if ((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
      err_dump("client: can't open datagram socket");
   /* Bind a local address for us.
    * In the UNIX domain we have to choose our own name (that
    * should be unique).  We'll use mktemp() to create a unique
    * pathname, based on our process id.
    */
   bzero((char*)&cli_addr, sizeof(cli_addr)); /* zero out */
   cli_addr.sun_family = AF_UNIX;
   strcpy(cli_addr.sun_path, UNIXDG_TMP);
   mktemp(cli_addr.sun_path);
   clilen = sizeof(cli_addr.sun_family) + strlen(cli_addr.sun_path);
   if (bind(sockfd, (struct sockaddr*)&cli_addr, clilen) < 0)
      err_dump("client: can't bind local address");
   dg_cli(stdin, sockfd, (struct sockaddr*)&serv_addr, servlen);
   close(sockfd);
   unlink(cli_addr.sun_path);
   exit(0);
}
EOF


cat > unixdgserv.c << "EOF"
/* Example of server using UNIX domain datagram protocol. */
#include "unix.h"

int main(int argc, char* argv[]) {
   int sockfd, servlen;
   struct sockaddr_un serv_addr, cli_addr;
   /* Open a socket (a UNIX domain datagram socket). */
   if ((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0)
      err_dump("server: can't open datagram socket");
   /* Bind our local address so that the client can send to us. */
   unlink(UNIXDG_PATH); /* in case it was left from last time */
   bzero((char*)&serv_addr, sizeof(serv_addr));
   serv_addr.sun_family = AF_UNIX;
   strcpy(serv_addr.sun_path, UNIXDG_PATH);
   servlen = sizeof(serv_addr.sun_family) + strlen(serv_addr.sun_path);
   if (bind(sockfd, (struct sockaddr*)&serv_addr, servlen) < 0)
      err_dump("server: can't bind local address");
   dg_echo(sockfd, (struct sockaddr*)&cli_addr, sizeof(cli_addr));
}
EOF

cat > unix.h  << "EOF"
/*
 * Definitions for UNIX domain stream and datagram client/server programs.
 */

#include 
#include 
#include 
#include 
#include "common.h"

#define UNIXSTR_PATH "./s.unixstr"
#define UNIXDG_PATH  "./s.unixdg"
#define UNIXDG_TMP   "/tmp/dg.XXXXXX"

char *pname;
EOF


cat > unixstrcli.c << "EOF"
/* Example of client using UNIX domain stream protocol. */
#include "unix.h"

int main(int argc, char* argv[]) {
   int sockfd, servlen;
   struct sockaddr_un serv_addr;
   /* Fill in the structure "serv_addr" with the address
    * of the server that we want to send to.     */
   bzero((char*)&serv_addr, sizeof(serv_addr));
   serv_addr.sun_family = AF_UNIX;
   strcpy(serv_addr.sun_path, UNIXSTR_PATH);
   servlen = strlen(serv_addr.sun_path) + sizeof(serv_addr.sun_family);
   /* Open a socket (an UNIX domain stream socket). */
   if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
      err_sys("client: can't open stream socket");
   /* Connect to the server. */
   if (connect(sockfd, (struct sockaddr*)&serv_addr, servlen) < 0)
      err_sys("client: can't connect to server");
   str_cli(stdin, sockfd);   /* do it all */
   close(sockfd);
   exit(0);
}
EOF


cat > unixstrserv.c << "EOF"
/* Example of server using UNIX domain stream protocol. */
#include "unix.h"

int main(int argc, char* argv[]) {
   int sockfd, newsockfd, childpid, servlen;
   struct sockaddr_un cli_addr, serv_addr;
   /* Open a socket (a UNIX domain stream socket). */
   if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
      err_dump("server: can't open stream socket");
   /* Bind our local address so that the client can send to us. */
   bzero((char*)&serv_addr, sizeof(serv_addr));
   serv_addr.sun_family = AF_UNIX;
   strcpy(serv_addr.sun_path, UNIXSTR_PATH);
   servlen = strlen(serv_addr.sun_path) + sizeof(serv_addr.sun_family);
   if (bind(sockfd, (struct sockaddr*)&serv_addr, servlen) < 0)
      err_dump("server: can't bind local address");
   listen(sockfd, 5);
   for (; ;) {
      /* Wait for a connection from a client process.
       * This is an example of a concurrent server. */
      socklen_t clilen = sizeof(cli_addr);
      newsockfd = accept(sockfd, (struct sockaddr*)&cli_addr, &clilen);
      if (newsockfd < 0) err_dump("server: accept error");
      if ((childpid = fork()) < 0) err_dump("server: fork error");
      else if (childpid == 0) {    /* child process */
         close(sockfd);            /* close original socket */
         str_echo(newsockfd);      /* process the request */
         exit(0);
      }
      close(newsockfd);            /* parent process */
   }
}
EOF


cat > writen.c << "EOF"
#include "common.h"

/* Write "n" bytes to a descriptor */
ssize_t writen(int fd, const void *vptr, size_t n) { 
   size_t nleft;
   ssize_t nwritten;
   nleft = n;
   while (nleft > 0) {
      if ((nwritten = write(fd, vptr, nleft)) <= 0) {
         if (errno == EINTR) nwritten = 0;     /* and call write() again */
         else return(-1);                     /* error */
      } 
      nleft -= nwritten;
      vptr  += nwritten;
   }
   return(n);
}
EOF



Для компиляции используй:
make



Использование:
./udpserv
./udpcli

./tcpserv
./tcpcli

./unixdgserv
./unixdgcli

./unixstrserv
./unixstrcli

./sockopt






Драйверы и сетевые устройства в ядре Linux

Linux - операционная система с монолитным ядром.
!!! Альтернативой системы с монолитным ядром является микро ядерные операционные системы (которых создано очень мало)

Проблемы у монолитного ядра, это расширения функциональности (Решёное погрузкой модулей) 
Такие модули загружаясь  связываются с API ядра  и его структурами данных по абсолютным адресам размещения. 
Адреса всех модулей можно посмотреть в псевдо файле /proc/kallsyms и их кол-во может достигать десятков тысяч (с каждой новой версией ядра становится все больше)
cat /proc/kallsyms

Подсчитать количество:
cat /proc/kallsyms | wc -l

Пример что значит строки в выводе "cat /proc/kallsyms | head" :
cat /proc/kallsyms | head -n1
ffff800008000000 T _text
ffff800008000000 - это абсолютный адрес для вызова этой функции ядра
T - сегмент текста (т.е. кода)
_text - внешнее имя драйвера (модуля)

ОС с микро ядерной архитектурой:

Операционные системы с микроядерной архитектурой включают:

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

QNX — коммерческая реальная ОС с микроядром, широко используемая в автомобильной и встраиваемой электронике.

L4 — семейство микроядер, которое используется в исследовательских и промышленных системах, 
таких как системы безопасности и встраиваемые системы. 
Есть несколько версий L4, например, L4Ka::Pistachio, Fiasco.

GNU Hurd — проект свободной операционной системы, использующий микроядро Mach.
Hurd разрабатывался как часть проекта GNU для замены Unix-подобных систем.

Mach — изначально разработанное микроядро, которое повлияло на разработку других микроядер. 
Использовалось как основа для NeXTSTEP и позднее стало частью macOS в виде гибридного ядра XNU.

Integrity — реальная ОС с микроядром, используемая в авиационной и медицинской технике.

Genode — платформа, основанная на микроядре, ориентированная на безопасность и высокую модульность встраиваемых систем.

Сборка своего модуля ядра hello_printk.c


Обратите внимание что данный Makefile создан с использованием макросов разработчиками ядра  
и он будет повторятся с минимальными изменениями для любых модулей ядра. 
cat  > Makefile << "EOF" 
CONFIG_MODULE_SIG=n

CURRENT = $(shell uname -r)
KDIR = /lib/modules/$(CURRENT)/build
PWD = $(shell pwd)
DEST = /lib/modules/$(CURRENT)/misc
TARGET = hello_printk

obj-m      := $(TARGET).o

all: default clean
#all: default

default: 
        $(MAKE) -C $(KDIR) M=$(PWD) modules

install:
        cp -v $(TARGET).ko $(DEST)
#      /sbin/depmod -v | grep $(TARGET)
        /sbin/depmod
        /sbin/insmod $(TARGET).ko
        /sbin/lsmod | grep $(TARGET)

uninstall:
        /sbin/rmmod $(TARGET)
        rm -v $(DEST)/$(TARGET).ko
        /sbin/depmod

clean:
        @rm -f *.o .*.cmd .*.flags *.mod.c *.order
        @rm -f .*.*.cmd *.symvers *~ *.*~
        @rm -fR .tmp* *.mod
        @rm -rf .tmp_versions

disclean: clean
        @rm -f *.ko
EOF

# Исправить восемь пробелов на TAB
sed -i 's/        /tt/g' Makefile 

cat >  hello_printk.c << "EOF" 
#include 

MODULE_LICENSE("GPL");
MODULE_AUTHOR("LALA >");

static int __init hello_init(void) {
   printk("Hello, world!n");
   return 0;
}

static void __exit hello_exit(void) {
   printk("Goodbye, world!n");
}

module_init(hello_init);
module_exit(hello_exit);
EOF

Для сборки используем:
make

!!!Обратите внимание что начиная с версии ядра 2.6 имя модуля ядра изменилось на ".o" на ".ko"
Формат файла модуля является обычный объектный файл ELF, но дополненный в таблице внешних имен дополнительными именами
(__mod_author5, __mod_license4, __mod_srcversion23, __module_depends, __mod_vermagic5 и прочими)

Узнать информацию о собранном модуле можно командой modinfo:
file hello_print.ko
modinfo ./hello_print.ko

Загрузить:
!!! insmod - не проверяет зависимости, по этому ее использование опасно
modprobe  ./hello_print.ko

Для выгрузки:
rmmod hello_print

Модули ядра, точки входа и завершения:

Любой модуль должен иметь объявление функции входа(инициализации) модуля и его завершения (не обязательно, может и отсутствовать).

Функция инициализации будет вызываться после проверки и соблюдения всех условий достаточных для инициализации.
Выполнение команды insmod для модуля.
Функция инициализации имеет прототип и объявляется именно как функция инициализации макросом module_init():
static int __int hello_int(void) {
...
}
module_init(hello_init);


Функция завершения будет вызывается при выполнение команды rmmod
функция завершения имеет прототип module_exit():
static void __exit hello_exit(void){
...
}
module_exit(hello_exit);
Обратите внимание что функция завершения по своему прототипу не имеет возвращаемого значения 
и поэтому не может сообщить о невозможности каких=либо действий, когда уже начала выполнятся.
Идея состоит в том что система сама проверит возможность завершения , и если это не возможно
то просто не выполнит эту функцию.


Существует еще один не документированный способ описания этих функций:
а именно init_module() и clean_module()
выглядят они так:
int init_module(void) {
...
}
void cleanup_module(void) {
...
}
при такой записи необходимость в макросах module_int() и module_xit отпадает и 
задействовать квалификатор static с такими записями нельзя!
такая запись ухудшает читаемость кода

Модули ядра, вывод диагностики модуля:

Для диагностики модуля используется функция printk()
По функционалу похож на функцию printf()

Сам вызов printk() и сопутствующие ему константы  определения вы найдете в файле:
/lib/modules/`uname -r`/build/include/linux/kernel.h
asmlinkage int printk(const char * fmt, ...) 

#define KERN_EMERG    "<0>"
#define KERN_ALERT    "<1>"
#define KERN_CRIT     "<2>"
#define KERN_ERR      "<3>"
#define KERN_WARNING  "<4>"
#define KERN_NOTICE   "<5>"
#define KERN_INFO     "<6>"
#define KERN_DEBUG    "<7>"


зависимости модулей прописаны в файле (информацию о них modprobe черпает из него)
cat /lib/modules/`uname -r`/modules.dep
Если modprobe получает универсальный идентификатор, то она сначала пытается найти его в файлах /etc/modprobe.conf (утарело)
и в каталоге /etc/modprobe.d/*.conf

!!! inmod никак не проверяет зависимости, если обнаруживает не разрешенные имена, то завершает работу аварийно.

depmod - показывает зависимости

Параметры загрузки модуля

Из командной строки передаются через массив argv[]
Для каждого параметра определяется переменная-параметр, далее эти переменные используются в макросе module_param().
Подобный макрос следует записать для каждого предусмотренного параметра, и он должен последовательно определить:
1) имя параметра и переменной
2) тип значения этой переменной
3) права доступа к параметру (отображаемое как путевое имя в системе псевдо каталог /sys)

Значения параметрам могут быть установлены во время загрузки модуля через insmod и modprobe
modprobe также может прочитать файл /etc/modprobe.conf

Обработка входных параметров модуля обеспечивается макросами  описанными в 
И там множество!
Но чаще всего вы встретите  module_param() и module_param_array()


Пример кода:
cat  > mod_params.c << "EOF"
#include 
#include 
#include 

MODULE_LICENSE("GPL");
MODULE_AUTHOR("OLA laA >");

static int iparam = 0;        // целочисленный параметр
module_param(iparam, int, 0);

static int k = 0;             // имена параметра и переменной различаются
module_param_named(nparam, k, int, 0);

static bool bparam = true;    // логический инверсный парамер
module_param(bparam, invbool, 0);

static char* sparam;          // строчный параметр
module_param(sparam, charp, 0);

#define FIXLEN 5
static char s[FIXLEN] = "";   // имена параметра и переменной различаются
module_param_string(cparam, s, sizeof(s), 0); // копируемая строка

static int aparam[] = { 0, 0, 0, 0, 0 };      // параметр - целочисленный массив
static int arnum = sizeof(aparam)/ sizeof(aparam[0]);
module_param_array(aparam, int, &arnum, S_IRUGO | S_IWUSR);

static int __init mod_init(void) {
   int j;
   char msg[40] = "";
   printk("========================================n");
   printk("iparam = %dn", iparam);
   printk("nparam = %dn", k);
   printk("bparam = %dn", bparam);
   printk("sparam = %sn", sparam);
   printk("cparam = %s {%ld}n", s, (long)strlen(s));
   sprintf(msg, "aparam [ %d ] = ", arnum );
   for(j = 0; j < arnum; j++) sprintf(msg + strlen(msg), " %d ", aparam[j]);
   printk("%sn", msg );
   printk("========================================n");
   return -1;
}
         
module_init(mod_init);

EOF

cat  > Makefile << "EOF" 
CURRENT = $(shell uname -r)
KDIR = /lib/modules/$(CURRENT)/build
PWD = $(shell pwd)

TARGET = mod_params

obj-m      := $(TARGET).o

all: default clean

default: $(TARGET).c 
        $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
        @rm -f *.o .*.cmd .*.flags *.mod.c *.order
        @rm -f .*.*.cmd *.symvers *~ *.*~ TODO.*
        @rm -fR .tmp* *.mod
        @rm -rf .tmp_versions

disclean: clean
        @rm -f $(TARGET).ko 

EOF


# Исправить восемь пробелов на TAB
sed -i 's/        /tt/g' Makefile 


Примеры использования:
insmod mod_params.ko
insmod ./mod_params.ko iparam=3 sparam=zxcv aparam=5,4,3
insmod ./mod_params.ko iparam=3 sparam=zxcv aparam=5,4,3,2,1,0
insmod ./mod_params.ko iparam=qwerty
insmod ./mod_params.ko iparam=3 nparam=4 sparam=str1 cparam=str2 aparam=5,4,3
insmod mod_params.ko sparam='new'
insmod mod_params.ko iparam=3 nparam=4 bparam=1 sparam=str1 cparam=str2 aparam=5,4,3

Ошибка?
echo $?
Если последняя команда выполнилась и 0 нет ошибки, если > 0 есть ошибка 

Ну и вывод смотрим с помощью dmesg 
dmesg 
dmesg -w
dmesg | tail -n9

Модуль ядра, подсчет ссылок использования:

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

!!!
Модуль, счетчик ссылок использования которого ненулевой, не может быть выгружен командой rmmod.
Некорректное обращение к несуществующему модулю гарантирует крах всей системы.

Пример:
lsmod | grep i2c
i2c_algo_bit           20480  1 i915
i2c_i801               45056  0
i2c_smbus              20480  1 i2c_i801
i2c_mux                16384  1 i2c_i801
i2c_dev                28672  0

Взглянем на:
i2c_smbus              20480  1 i2c_i801 
Значение ссылок  равно 1 
Далее i2c_i801 перечислены использующие его модули (модули которые на него ссылаются) 
До тех пор пока модуль i2c_i801 не будет удален из системы, модуль i2c_smbus будет невозможно удалить.

функции вызова определены в файле 
int try_module _get(struct module *module) - увеличить счетчик ссылок для модуля (возвращает признак успешности операции)
void module_put(struct module *module) - уменьшает счетчик ссылок для модуля
unsigned int module_refcount(struct module *mod) - возвратить значение счетчика ссылок для модуля

В качестве параметра всех этих вызовов, как правило, передается константный указатель THIS_MODULE, так что вызовы выглядят примерно так:
try_module_get(THIS_MODULE);

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

Структура данных сетевого стека:

Сетевая реализация построена так, чтобы не зависеть от конкретики протоколов.
Основной структурой данных является struct net_device, описывает сетевое устройство.

Основной структурой обмениваемых данных между сетевыми устройствами являются "буферы сокетов"
Определена в 
struct sk_buff
данные управления "head"
данные пакета "data"

Буферы  сокетов всегда указывают в очереди( struct sk_queue_head ) посредством своих первых полей "next" и  "prev"

Пример полей структуры:
typedef unsigned char *sk_buff data_t;
struct sk_buff {
  stuckt sk_buff *next;
  struct sk_buff *prev;
...
  sk_buff_data_t transport_header;
  sk_buff_data_t network_header;
  sk_buff_data_t mac_header;
...
unsigned char *head,
              *data;
...
};

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


Экземпляры данных типа struct sk_buff:
ПОЛУЧАТЕЛЬ:
Возникают при поступлении очередного сетевого пакета из внешней физической среды распространения данных.
Об этом событии извещает прерывание IRQ, генерируемое сетевым адаптером.
При этом создается экземпляр буфера сокета, заполняется данными из поступающего пакета и далее передается вверх от сетевого слоя к слою до приложения 
прикладного уровня, которое является получателем пакета.
На этом экземпляр данных буфера сокета уничтожается.
ОТПРАВИТЕЛЬ:
Возникает в среде приложения прикладного уровня, которое является отправителем пакета данных.
Пакет отправителя данных помещается в созданный буфер сокета, который начинает перемещается вниз от сетевого слоя к слою достижения канального уровня L2.
На этом уровне осуществляется физическая передача данных пакета через сетевой адаптер в среду распространения.
В случае успешного завершения передачи  (подтверждается прерыванием, генерируем сетевым адаптером , часто по той же линии IRQ, что и при приеме пакета)
буфер сокета уничтожается.
При отсутствии подтверждения отправки(IRQ) обычно делается несколько повторных попыток, прежде чем принять решение об ошибке канала.

Путь пакета сквозь стек протоколов / Прием: традиционный подход


Традиционный подход состоит в том, что каждый приходящий сетевой пакет порождает аппаратное прерывание 
по линии IRQ адаптера, что и служит сигналом  на прием очередного сетевого пакета и создание буфера сокета для 
его сохранения и обработки принятых данных.
Порядок действий:
1. Читая конфигурационную область PCI-адаптера сети при инициализации модуля,
определяем линию прерывания IRQ, которая будет обслуживать сетевой обмен:
char irq; 
pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &byte);
2. При инициализации сетевого интерфейса для этой линии IRQ устанавливается обработчик прерывания my_interrupt():
reques_irq(int)irq, my_interrupt, IRQF_SHARED, "my_interrupt", &my_dev_id);
3. В обработчике прерывания по приему нового пакета в сеть - здесь нужен
анализ причины по чтению некоего аппаратного флага) создается (или запрашивается из пула используемых) новый экземпляр буфера сокетов:
static irqreturn_t my_interrupt(int irq, void *dev_id) {
   ...
   struct sk_buff *skb = kmalloc(sizeof(struct sk_buff), ...);
   // заполнение данных *skb чтением из портов сетевого адаптера
   netif_rx(skb);
   return IRQ_HANDLED;
}
Все эти действия выполняются не в самом обработчике верхней половины прерываний от сетевого адаптера, 
а в обработчике отложенного прерывания NET_RX_SOFTIRQ для этой линии.
Последним действием является передача заполненного сокетного буфера вызову netifrx() (или netif_receive_skb()), 
который и запустит процесс движения его (буфера) по структуре сетевого стека (отметит отложенное программное прерывание NET_RX_SOFTIRQ для исполнения).

Путь пакета сквозь стек протоколов / Прием: высокоскоростной интерфейс

Особенность сетевых интерфейсов в том, что их активность носит взрывной характер.
В один момент времени может не быть трафика совсем(обмен ARP пакетами не в счёт), в другой его много.

!!!
Современные сетевые интерфейсы имеют скорости 10+ Гбит, и уже при более скромных скоростях традиционный подход становится нецелесообразным:
Новые приходящие пакеты создают вложенные запросы IRQ несколько уровней при еще не обслуженном приеме текущего IRQ;
Асинхронное обслуживание каждого IRQ в плотном потоке создает слишком большие накладные расходы.

Поэтому был добавлен набор API для обработки таких  плотных потоков пакетов, 
поступающих с высокоскоростных интерфейсов, который и получил название NAPI (New API).
Идея состоит в том,  чтобы прием пакетов осуществлять не методом прерывания, а методом программного опроса (poling)
точнее комбинацией этих двух возможностей:

 При поступлении первого пакета "пачки" инициируется прерывание IRQ адаптера(все начинается как в традиционном методе);

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

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

 Считывание и обработка  пакетов происходит обработчике прерываний в его отстроченной (нижней) части.

 Каждому принятому в опросе пакету генерируется сокетный буфер для продвижения его по стеку сетевых протоколов вверх

 После завершения цикла программного опроса по его результатам устанавливается состояния завершения:
NAPI_STATE_DISABLE(если не осталось считанных пакетов в кольцевом буфере адаптера) или NAPI_STATE_SCHED(что говорит,
что устройство адаптера должно продолжать опрашиваться, когда ядро в следующий раз перейдет к циклу опросов 
в отложенном обработчике прерываний)

 Если результатом является NAPI_STATE_DISABLE, то после завершения цикла программного опроса восстанавливается разрешение 
генерации прерываний по линии IRQ приема пакетов(записью в порты сетевого адаптера)



1. Реализатор обязан создать и зарегистрировать специфичную функцию для модуля функции опроса(poll-функцию), используя вызов ()
static inline void netif_napi_add(struct net_device *dev,
                                  struct napi_struct *napi,
                                  int (*poll)(struct napi_struct *,int),
                                  int weight);
dev - сетевой интерфейс
poll - функция программного опроса
weight - относительный вес  10MIb,100Mib  = 16  10Gib и 100Gib = 64
napi - указатель на специальную структуру, со значениями state = NAPI_STATE_DISABLE  state = NAPI_STATE_SCHED  

struct napi_struct {
  struct list_head poll_list;
  unsingned long state;
  int weight;
  int (*poll) (struct napi_struct*, int);
};

2. Зарегистрированная функция программного опроса ( полностью зависимая от задачи и реализуемая в коде модуля имеет подобный вид;
static int my_card_poll(struct napi_struct *napi, int budget) {
       int work_done; // число реально обработанных в цикле опроса сетевых пакетов
       work_done = my_card_input(budget, ...); // реализационно специфический прием пакетов
       if (work_done < budget ) {
          netif_rx_complete(netdev, napi);
          my_card_enable_irq(...); // разрешить IRQ приема
       }
       retun work_done;
}

my_card_input() - пытается аппаратно считать budget сетевых пакетов
Для каждого считанного сетевого пакета создается сокетный буфер и вызывает netif_receive_skb()
если кольцевой буфер исчерпается ранее budget то будет вызвана netif_rx_complete() что отменит отложенное програмное прерывание  NET_RX_SOFTIRQ

3. Обработчик аппаратного прерывания линии IRQ сетевого адаптера:
static irqretur_t my_interrupt(int irq, void *dev_id) {
   struct net_debice *netdev = dev_id;
   if (likely(netif_rx_schedule_prep(netdev, ...))) {
     my_card_disable_irq(...); // запретить IRQ приема
     __netif_rx_schedule(netdev, ...);
   }
    return IRQ_HANDLED;
}

Ядро должно быть уведомлено что новая порция сетевых пакетов готова для обработки.
Для этого:
вызов netif_rx_schedule_prep() устанавливает состояние NAPI_STATE_SCHED
если он успешен, то вызовом __netif_rx_schedule() устройство помещается в список программного опроса  в цикле обработки отложенного программного прерывания NET_RQ_SOFTIRQ


Опрос не должен потреблять более одного системного тика(глобальная переменная jiffiels), иначе это будет искажать диспетчеризацию потоков ядра
Бюджет не должен быть больше глобального установленного ограничения
cat /proc/sys/net/core/netdev_budget

Передача пакета:

ndo (Net Device Operation)
При инициализации сетевого интерфейса создается таблица операций сетевого интерфейса, одно из полей которой -
- ndo_start_xmit - определяет функцию передачи пакета в сеть:
struct net_device_ops ndo = {
   .ndo_open = my_open,
   .ndo_stop = my_close,
   .ndo_start_xmit = stub_start_xmit,
};

При вызове stub_start_xmit должна обеспечить аппаратную передачу полученного сокета в сеть, после чего уничтожает(возвращает в пул) буфер сокета:
static int stab_start_xmit(struct sk)buff *skb, struct net_device *dev) {
       // ... апаратное обслуживание передачи
       dev _kfree_skb(skb);
       return 0;
}

Реально чаще уничтожение отправлено буфера будет происходить не при инициализации операции, а при ее успешном завершении, что отслеживается той же IRQ,
что и прием пакетов из сети. 


Часто задаваемый вопрос:
а где же в этом процессе место (код), где создается информация для буферов?, где потребляется информация из принимаемых сокетных буферов?
Ответ прост:
Интерфейс из этого прикладного уровня в стек протоколов ядра обеспечивается известным POSIX API сокетов прикладного уровня.

Драйверы: сетевой интерфейс

Задача сетевого интерфейса - быть местом в котором:
Создаются экземпляры структуры "struct sk_buff" по каждому принятому из интерфейса пакету (здесь нужно принимать во внимание возможность сегментации IP-пакетов),
далее созданный экземпляр структуры продвигается по стеку протоколов вверх до получателя пользовательского пространства, где он и уничтожается.
Исходящие экземпляры структуры "struct sk_buff", порожденные где-то на верхних уровнях протоколов пользовательского пространства, должны отправляться
(чаще всего каким-то аппаратным механизмом), а сами экземпляры структуры после этого - уничтожатся.

Пример кода:
cat > network.c << "EOF"
/*
 * (c) Copyright Jerry Cooperstein, 2009
 * (c) Copyright Oleg Tsiliuric, 2011
 */
#include 
#include 
#include 

static struct net_device *dev;

static int my_open(struct net_device *dev) {
   printk(KERN_INFO "! Hit: my_open(%s)n", dev->name);
   /* start up the transmission queue */
   netif_start_queue(dev);
   return 0;
}

static int my_close(struct net_device *dev) {
   printk(KERN_INFO "! Hit: my_close(%s)n", dev->name);
   /* shutdown the transmission queue */
   netif_stop_queue(dev);
   return 0;
}

/* Note this method is only needed on some; without it
   module will fail upon removal or use. At any rate there is a memory
   leak whenever you try to send a packet through in any case*/
static int stub_start_xmit(struct sk_buff *skb, struct net_device *dev) {
   dev_kfree_skb(skb);
   return 0;
}

static struct net_device_ops ndo = {
   .ndo_open = my_open,
   .ndo_stop = my_close,
   .ndo_start_xmit = stub_start_xmit,
};

static void my_setup(struct net_device *dev) {
   /* Fill in the MAC address with a phoney */
   char *pa = (char*)dev->dev_addr;
   for(int j = 0; j < ETH_ALEN; j++) 
      *pa++ = (char)j;
   ether_setup(dev);
   dev->netdev_ops = &ndo;
}

static int __init my_init(void) {
   printk(KERN_INFO "! Loading stub network module:....");
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0))
   dev = alloc_netdev(0, "fict%d", my_setup);
#else
   dev = alloc_netdev(0, "fict%d", NET_NAME_ENUM, my_setup);
#endif
   if(register_netdev(dev)) {
      printk(KERN_INFO "! Failed to registern");
      free_netdev(dev);
      return -1;
   }
   printk(KERN_INFO "! Succeeded in loading %s!n", dev_name(&dev->dev));
   return 0;
}

static void __exit my_exit(void) {
   printk(KERN_INFO "! Unloading stub network modulen");
   unregister_netdev(dev);
   free_netdev(dev);
}

module_init(my_init);
module_exit(my_exit);

MODULE_AUTHOR("Bill Shubert");
MODULE_AUTHOR("Jerry Cooperstein");
MODULE_AUTHOR("Tatsuo Kawasaki");
MODULE_AUTHOR("Oleg Tsiliuric");
MODULE_DESCRIPTION("LDD:1.0 s_24/lab1_network.c");
MODULE_LICENSE("GPL v2");

EOF

cat > Makefile << "EOF"
CURRENT = $(shell uname -r)
KDIR = /lib/modules/$(CURRENT)/build
PWD = $(shell pwd)
DEST = /lib/modules/$(CURRENT)/misc

EXTRA_CFLAGS += -std=gnu99

TARGET1 = devices
TARGET2 = network
TARGET3 = transmit_simple
TARGET4 = transmit
TARGET5 = receive
TARGET6 = mulnet

obj-m   := $(TARGET1).o $(TARGET2).o $(TARGET3).o 
           $(TARGET4).o $(TARGET5).o $(TARGET6).o

all: default clean

default:
        $(MAKE) -C $(KDIR) M=$(PWD) modules

install:
        cp -v $(TARGET).ko $(DEST)
        /sbin/depmod -v | grep $(TARGET)
        /sbin/insmod $(TARGET).ko
        /sbin/lsmod | grep $(TARGET)

uninstall:
        /sbin/rmmod $(TARGET)
        rm -v $(DEST)/$(TARGET).ko
        /sbin/depmod

clean:
        @rm -f *.o .*.cmd .*.flags *.mod.c *.order
        @rm -f .*.*.cmd *.symvers *~ *.*~
        @rm -fR .tmp* .*.d .*.cmd *.mod
        @rm -rf .tmp_versions

disclean: clean
        @rm -f *.ko

EOF

# Исправить восемь пробелов на TAB
sed -i 's/        /tt/g' Makefile 


cat > devices.c << "EOF"
#include 
#include 
#include 

static int __init my_init(void) {
   struct net_device *dev;
   printk(KERN_INFO "Hello: module loaded at 0x%pxn", my_init);
   dev = first_net_device(&init_net);
   printk(KERN_INFO "Hello: dev_base address=0x%pxn", dev);
   while (dev) { 
      char smac[ETH_ALEN*3];
      struct rtnl_link_stats64 stat = {};
      for(int j = 0; j < ETH_ALEN; j++)
         sprintf(smac + j * 3, "%02x%c",
                 dev->dev_addr[j], ETH_ALEN - 1 != j ? ':' : '' );
      if (dev->netdev_ops->ndo_get_stats64 != NULL)
         dev->netdev_ops->ndo_get_stats64(dev, &stat);
      printk(KERN_INFO "name=%-6s irq=%-3d MAC=%s "
                       "rx_bytes=%-8llu tx_bytes=%-8llu n",
                       dev->name, dev->irq, smac,
                       stat.rx_bytes, stat.tx_bytes);
      dev = next_net_device(dev);
    }
    return -1;
}

module_init(my_init);

MODULE_AUTHOR("Oleg Tsiliuric");
MODULE_LICENSE("GPL v2");

EOF


cat > transmit_simple.c << "EOF"
/*
 * (c) Copyright Jerry Cooperstein, 2009
 * (c) Copyright Oleg Tsiliuric, 2011-2022
 */

/*
 * Building a Transmitting Network Driver (simple solution)
 *
 * Extend your stub network device driver to include a transmission
 * function, which means supplying a method for
 * dev->hard_start_xmit().
 *
 * While you are at it, you may want to add other entry points to see
 * how you may exercise them.
 *
 * Once again, you should be able to exercise it with:
 *
 *   insmod transmit_simple.ko
 *   ifconfig mynet0 up 192.168.3.197
 *   ping -I mynet0 localhost
 *       or
 *   ping -bI mynet0 192.168.3
 *
 * Make sure your chosen address is not being used by anything else.
 */
#include 
#include 
#include 
#include 

static int debug = 0;               // уровень отладочного вывода
module_param(debug, int, 0);

static struct net_device *dev;

static int my_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) {
   struct netdev_queue *pnq = skb_get_tx_queue(dev, skb);
//static inline struct netdev_queue *skb_get_tx_queue(const struct net_device *dev,
//                            const struct sk_buff *skb)
   pnq->trans_start = jiffies;
   if(debug > 0)
      printk(KERN_INFO "! my_hard_start_xmit(%s) - sending packet :n",
             dev->name);   
   if(debug > 1) {
      char line[3 * 16 + 1]; //, *plin = (char*)&line; 
      /* print out 16 bytes per line */
      for(int i = 0; i < skb->len; ++i) {
         int pos = i % 16;
         if((i > 0) && (i & 0xf) == 0)
            printk(KERN_INFO "! %sn", line);
         sprintf((char*)line + pos * 3, "%02x ", skb->data[i]);
      }
      printk(KERN_INFO "n");
   }
   dev_kfree_skb(skb);
   return 0;
}

static int my_open(struct net_device *dev) {
   printk(KERN_INFO "! Hit: my_open(%s)n", dev->name);
   /* start up the transmission queue */
   netif_start_queue(dev);
   return 0;
}

static int my_close(struct net_device *dev) {
   printk(KERN_INFO "! Hit: my_close(%s)n", dev->name);
   /* shutdown the transmission queue */
   netif_stop_queue(dev);
   return 0;
}

static struct net_device_ops ndo = {
   .ndo_open = my_open,
   .ndo_stop = my_close,
   .ndo_start_xmit = my_hard_start_xmit,
};

static void my_setup(struct net_device *dev ) {
   /* Fill in the MAC address with a phoney */
   char *pa = (char*)dev->dev_addr;
   for(int j = 0; j < ETH_ALEN; j++) 
      *pa++ = (char)j;
   ether_setup(dev);
   dev->netdev_ops = &ndo;
   dev->flags |= IFF_NOARP;
}

static int __init my_init(void) {
   printk(KERN_INFO "! Loading transmitting network module:....");
   printk(KERN_INFO "! debug level = %d", (int)debug);
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0))
   dev = alloc_netdev(0, "mynet%d", my_setup);
#else
   dev = alloc_netdev(0, "mynet%d", NET_NAME_ENUM,  my_setup);
#endif
   if(register_netdev(dev)) {
      printk(KERN_INFO "! Failed to registern");
      free_netdev(dev);
      return -1;
   }
   printk(KERN_INFO "! Succeeded in loading %s!n", dev_name(&dev->dev));
   return 0;
}

static void __exit my_exit(void) {
   printk(KERN_INFO "! Unloading transmitting network modulen");
   unregister_netdev(dev);
   free_netdev(dev);
}

module_init(my_init);
module_exit(my_exit);

MODULE_AUTHOR("Bill Shubert");
MODULE_AUTHOR("Jerry Cooperstein");  // "LDD:1.0 s_26/lab1_transmit_simple.c"  
MODULE_AUTHOR("Oleg Tsiliuric");
MODULE_DESCRIPTION("");
MODULE_LICENSE("GPL v2");

EOF


cat > transmit.c  << "EOF"
/*
 * (c) Copyright Jerry Cooperstein, 2009
 * (c) Copyright Oleg Tsiliuric, 2011
 */
#include 
#include 
#include 
#include 

static struct net_device *dev;
static struct net_device_stats *stats;

static int my_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) {
   struct netdev_queue *pnq = skb_get_tx_queue(dev, skb);
   pnq->trans_start = jiffies;   
   printk(KERN_INFO "! my_hard_start_xmit(%s) - Sending packet :n", dev->name);
//   printk(KERN_INFO "! Sending packet :n");
   for(int i = 0; i < skb->len; ++i) {
      /* print out 16 bytes per line */
      char line[3 * 16 + 1];
      int pos = i % 16;
      if((i > 0) && (i & 0xf) == 0)
         printk(KERN_INFO "! %sn", line);
      sprintf((char*)line + pos * 3, "%02x ", skb->data[i]);
   }
   printk(KERN_INFO "n");
   dev_kfree_skb(skb);
   ++stats->tx_packets;
   return 0;
}

static int my_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) {
   printk(KERN_INFO "! my_do_ioctl(%s)n", dev->name);
   return -1;
}

static struct net_device_stats *my_get_stats(struct net_device *dev) {
   printk(KERN_INFO "! my_get_stats(%s)n", dev->name);
   return stats;
}

/* This is where ifconfig comes down and tells us who we are, etc.
 * We can just ignore this. */
static int my_config(struct net_device *dev, struct ifmap *map) {
   printk(KERN_INFO "! my_config(%s)n", dev->name);
   if(dev->flags & IFF_UP) 
      return -EBUSY;
   return 0;
}

static int my_change_mtu(struct net_device *dev, int new_mtu) {
   printk(KERN_INFO "! my_change_mtu(%s)n", dev->name);
   return -1;
}

static int my_open(struct net_device *dev) {
   printk(KERN_INFO "! Hit: my_open(%s)n", dev->name);
   /* start up the transmission queue */
   netif_start_queue(dev);
   return 0;
}

static int my_close(struct net_device *dev) {
   printk(KERN_INFO "! Hit: my_close(%s)n", dev->name);
   /* shutdown the transmission queue */
   netif_stop_queue(dev);
   return 0;
}

static struct net_device_ops ndo = {
   .ndo_open = my_open,
   .ndo_stop = my_close,
   .ndo_start_xmit = my_hard_start_xmit,
   .ndo_do_ioctl = my_do_ioctl,
   .ndo_get_stats = my_get_stats,
   .ndo_set_config = my_config,
   .ndo_change_mtu = my_change_mtu,
};

static void my_setup(struct net_device *dev) {
   /* Fill in the MAC address with a phoney */
   char *pa = (char*)dev->dev_addr;
   for(int j = 0; j < ETH_ALEN; j++) 
      *pa++ = (char)j;
   ether_setup(dev);
   dev->netdev_ops = &ndo;
   dev->flags |= IFF_NOARP;
   stats = &dev->stats;
   /* Just for laughs, let's claim that we've seen 50 collisions. */
   stats->collisions = 50;
}

static int __init my_init(void) {
   printk(KERN_INFO "! Loading transmitting network module:....");
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0))
   dev = alloc_netdev(0, "mynet%d", my_setup);
#else
   dev = alloc_netdev(0, "mynet%d", NET_NAME_ENUM, my_setup);
#endif
   if(register_netdev(dev)) {
      printk(KERN_INFO "! Failed to registern");
      free_netdev(dev);
      return -1;
   }
   printk(KERN_INFO "! Succeeded in loading %s!n", dev_name(&dev->dev));
   return 0;
}

static void __exit my_exit(void) {
   printk(KERN_INFO "! Unloading transmitting network modulen");
   unregister_netdev(dev);
   free_netdev(dev);
}

module_init(my_init);
module_exit(my_exit);

MODULE_AUTHOR("Bill Shubert");
MODULE_AUTHOR("Jerry Cooperstein");  // LDD:1.0 s_26/lab1_transmit.c
MODULE_AUTHOR("Oleg Tsiliuric");
MODULE_DESCRIPTION("");
MODULE_LICENSE("GPL v2");

EOF




cat > receive.c << "EOF"
/*
 * (c) Copyright Jerry Cooperstein, 2009
 * (c) Copyright Oleg Tsiliuric, 2011-2022
 *
 * Adding Reception
 *
 * Extend your transmitting device driver to include a reception
 * function.
 *
 * You can do a loopback method in which any packet sent out is
 * received.
 *
 * Be careful not to create memory leaks!
 *
 */

#include 
#include 
#include 
#include 

static struct net_device *dev;
static struct net_device_stats *stats;

static void my_rx(struct sk_buff *skb, struct net_device *dev) {
   /* just a loopback, already has the skb */
   printk(KERN_INFO "! I'm receiving a packetn");
   ++stats->rx_packets;
   netif_rx(skb);
}

static int my_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) {
   struct netdev_queue *pnq;
   printk(KERN_INFO "! my_hard_start_xmit(%s)n", dev->name);
   pnq = skb_get_tx_queue(dev, skb);
   pnq->trans_start = jiffies;   
   printk(KERN_INFO "! Sending packet :n");
   /* print out 16 bytes per line */
   for(int i = 0; i < skb->len; ++i) {
      if((i & 0xf) == 0)
         printk(KERN_INFO "n  ");
      printk(KERN_INFO "%02x ", skb->data[i]);
   }
   printk(KERN_INFO "n");
   ++stats->tx_packets;
   /* loopback it */
   my_rx(skb, dev);
   return 0;
}

static int my_do_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) {
   printk(KERN_INFO "! my_do_ioctl(%s)n", dev->name);
   return -1;
}

static struct net_device_stats *my_get_stats(struct net_device *dev) {
   printk(KERN_INFO "! my_get_stats(%s)n", dev->name);
   return stats;
}

/*
 * This is where ifconfig comes down and tells us who we are, etc.
 * We can just ignore this.
 */
static int my_config(struct net_device *dev, struct ifmap *map) {
   printk(KERN_INFO "! my_config(%s)n", dev->name);
   if(dev->flags & IFF_UP) 
      return -EBUSY;
   return 0;
}

static int my_change_mtu(struct net_device *dev, int new_mtu) {
   printk(KERN_INFO "! my_change_mtu(%s)n", dev->name);
   return -1;
}

static int my_open(struct net_device *dev) {
   printk(KERN_INFO "! Hit: my_open(%s)n", dev->name);
   /* start up the transmission queue */
   netif_start_queue(dev);
   return 0;
}

static int my_close(struct net_device *dev) {
   printk(KERN_INFO "! Hit: my_close(%s)n", dev->name);
   /* shutdown the transmission queue */
   netif_stop_queue(dev);
   return 0;
}

static struct net_device_ops ndo = {
   .ndo_open = my_open,
   .ndo_stop = my_close,
   .ndo_start_xmit = my_hard_start_xmit,
   .ndo_do_ioctl = my_do_ioctl,
   .ndo_get_stats = my_get_stats,
   .ndo_set_config = my_config,
   .ndo_change_mtu = my_change_mtu,
};

static void my_setup(struct net_device *dev) {
   /* Fill in the MAC address with a phoney */
   char *pa = (char*)dev->dev_addr;
   for(int j = 0; j < ETH_ALEN; j++) 
      *pa++ = (char)j;
   ether_setup(dev);
   dev->netdev_ops = &ndo;
   dev->flags |= IFF_NOARP;
   stats = &dev->stats;
   /* Just for laughs, let's claim that we've seen 50 collisions. */
   stats->collisions = 50;
}

static int __init my_init(void) {
   printk(KERN_INFO "! Loading transmitting network module:....");

#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0))
   dev = alloc_netdev(0, "mynet%d", my_setup);
#else
   dev = alloc_netdev(0, "mynet%d", NET_NAME_ENUM, my_setup);
#endif
   if(register_netdev(dev)) {
      printk(KERN_INFO "! Failed to registern");
      free_netdev(dev);
      return -1;
   }
   printk(KERN_INFO "! Succeeded in loading %s!nn", dev_name(&dev->dev));
   return 0;
}

static void __exit my_exit(void) {
   printk(KERN_INFO "! Unloading transmitting network modulenn");
   unregister_netdev(dev);
   free_netdev(dev);
}

module_init(my_init);
module_exit(my_exit);

MODULE_AUTHOR("Bill Shubert");
MODULE_AUTHOR("Jerry Cooperstein");  // LDD:1.0 s_26/lab2_receive.c 
MODULE_AUTHOR("Oleg Tsiliuric");
MODULE_DESCRIPTION("");
MODULE_LICENSE("GPL v2");
EOF



cat >  mulnet.c  << "EOF"
/*
 * (c) Copyright Jerry Cooperstein, 2009
 * (c) Copyright Oleg Tsiliuric, 2011-2022
 */
#include 
#include 
#include 

static uint num = 1;               // число создаваемых интерфейсов
module_param(num, uint, 0);
static char* title;               // префикс имени интерфейсов
module_param(title, charp, 0);
static int digit = 1;             // числовые суфиксы (по умолчанию)
module_param(digit, int, 0);
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0))
static int mode = 1;              // режим нумерации интерфейсов
module_param(mode, int, 0);
#endif

#define MAX_LINK 5
static struct net_device* adev[MAX_LINK] = {}; //{ NULL, NULL, NULL, NULL, NULL, NULL };

static int my_open(struct net_device *dev) {
   printk(KERN_INFO "! my_open(%s)n", dev->name);
   /* start up the transmission queue */
   netif_start_queue(dev);
   return 0;
}

static int my_close(struct net_device *dev) {
   printk(KERN_INFO "! my_close(%s)n", dev->name);
   /* shutdown the transmission queue */
   netif_stop_queue(dev);
   return 0;
}

/* Note this method is only needed on some; without it
   module will fail upon removal or use. At any rate there is a memory
   leak whenever you try to send a packet through in any case*/
static int stub_start_xmit(struct sk_buff *skb, struct net_device *dev) {
   dev_kfree_skb(skb);
   return 0;
}

static struct net_device_ops ndo = {
   .ndo_open = my_open,
   .ndo_stop = my_close,
   .ndo_start_xmit = stub_start_xmit,
};

static int ipos;

static void __init my_setup(struct net_device *dev) {
   /* Fill in the MAC address with a phoney */
   char *pa = (char*)dev->dev_addr;
   for(int j = 0; j < ETH_ALEN; j++) 
      *pa++ = (char)(ipos + j);
   ether_setup(dev);
   dev->netdev_ops = &ndo;
}

static int __init my_init(void) {
   char prefix[20];
//   if(num > sizeof(adev) / sizeof(adev[0])) {
   if( num > MAX_LINK ) {
      printk(KERN_INFO "! link number error");
      return -EINVAL;
   }
// NET_NAME_UNKNOWN, NET_NAME_ENUM, NET_NAME_PREDICTABLE, NET_NAME_USER, NET_NAME_RENAMED 
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0))
   if(mode < 0 || mode > NET_NAME_RENAMED) {
      printk(KERN_INFO "! unknown name assign mode");
      return -EINVAL;
   }
#endif
   printk(KERN_INFO "! loading network module for %d links", num);
   sprintf(prefix, "%s%s", (NULL == title ? "fict" : title), "%d");
   for(ipos = 0; ipos < num; ipos++) {
      if(!digit)
         sprintf(prefix, "%s%c", (NULL == title ? "fict" : title), 'a' + ipos);
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0))
      adev[ipos] = alloc_netdev(0, prefix, my_setup);
#else
      adev[ipos] = alloc_netdev(0, prefix, NET_NAME_UNKNOWN + mode, my_setup);
#endif
      if(register_netdev(adev[ipos])) {
         printk(KERN_INFO "! failed to register");
         for(int j = ipos; j >= 0; j--) {
            if(j != ipos) unregister_netdev(adev[ipos]);
            free_netdev(adev[ipos]);
         }
         return -ELNRNG;
      }
   }
   printk(KERN_INFO "! succeeded in loading %d devices!", num);
   return 0;
}

static void __exit my_exit(void) {
   printk(KERN_INFO "! unloading network module");
   for(int i = 0; i < num; i++) {
      unregister_netdev(adev[i]);
      free_netdev(adev[i]);
   }
} 

module_init(my_init);
module_exit(my_exit);

MODULE_AUTHOR("Bill Shubert");
MODULE_AUTHOR("Jerry Cooperstein");
MODULE_AUTHOR("Tatsuo Kawasaki");
MODULE_AUTHOR("Oleg Tsiliuric");
MODULE_DESCRIPTION("LDD:1.0 s_24/lab1_network.c");
MODULE_LICENSE("GPL v2");

EOF


Пробуем собрать фиктивный адаптер
make 
insmod ./network.ko
dmesg | tail n4 
ip link show dev fict0

Основная структура "struct net_device" описания сетевого интерфейса находится в файле 
Оттуда же и берется MAC "00:01:02:03:04:05" (генерируется)

Виртуальный сетевой интерфейс и пример кода

cat > Makefile << "EOF"
CURRENT = $(shell uname -r)
KDIR = /lib/modules/$(CURRENT)/build
PWD = $(shell pwd)

TARGET0 = virt
TARGET1 = virt1
TARGET2 = virt2

obj-m := $(TARGET0).o $(TARGET1).o  $(TARGET2).o

all: default clean

default:
        $(MAKE) -C $(KDIR) M=$(PWD) modules 

clean:
        @rm -f *.o .*.cmd .*.flags *.mod.c *.order
        @rm -f .*.*.cmd *.symvers *~ *.*~ TODO.*
        @rm -fR .tmp* *.mod
        @rm -rf .tmp_versions

disclean: clean
        @rm -f *.ko
EOF

# Исправить восемь пробелов на TAB
sed -i 's/        /tt/g' Makefile 



cat > virt.c << "EOF"
#include 
#include 
#include 
#include 
#include 
#include 

#define ERR(...) printk(KERN_ERR "! "__VA_ARGS__)
#define LOG(...) printk(KERN_INFO "! "__VA_ARGS__)

static char* link = "eth0";
module_param(link, charp, 0);

static char* ifname = "virt"; 
module_param(ifname, charp, 0);

static struct net_device *child = NULL;

struct priv {
   struct net_device_stats stats;
   struct net_device *parent;
};

static rx_handler_result_t handle_frame(struct sk_buff **pskb) {
   struct sk_buff *skb = *pskb;
   if(child) {
      struct priv *priv = netdev_priv(child);
      priv->stats.rx_packets++;
      priv->stats.rx_bytes += skb->len;
      LOG("rx: injecting frame from %s to %s", skb->dev->name, child->name);
      skb->dev = child;
      /* netif_receive_skb(skb);
      return RX_HANDLER_CONSUMED; */
      return RX_HANDLER_ANOTHER;
   }
   return RX_HANDLER_PASS;
}

static int open(struct net_device *dev) {
   netif_start_queue(dev);
   LOG("%s: device opened", dev->name);
   return 0;
}

static int stop(struct net_device *dev) {
   netif_stop_queue(dev);
   LOG("%s: device closed", dev->name);
   return 0;
}

static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev) {
   struct priv *priv = netdev_priv(dev);
   priv->stats.tx_packets++;
   priv->stats.tx_bytes += skb->len;
   if(priv->parent) {
      skb->dev = priv->parent;
      skb->priority = 1;
      dev_queue_xmit(skb);
      LOG("tx: injecting frame from %s to %s", dev->name, skb->dev->name);
      return 0;
   }
   return NETDEV_TX_OK;
}

static struct net_device_stats *get_stats(struct net_device *dev) {
   return &((struct priv*)netdev_priv(dev))->stats;
}

static struct net_device_ops crypto_net_device_ops = {
   .ndo_open = open,
   .ndo_stop = stop,
   .ndo_get_stats = get_stats,
   .ndo_start_xmit = start_xmit,
};

// #define MAX_ADDR_LEN    32  
// #define ETH_ALEN        6   /* Octets in one ethernet addr   */ 

static void setup(struct net_device *dev) {
   int j;
   ether_setup(dev);
   memset(netdev_priv(dev), 0, sizeof(struct priv));
   dev->netdev_ops = &crypto_net_device_ops;
   for(j = 0; j < ETH_ALEN; ++j) // fill in the MAC address with a phoney 
      dev->dev_addr[j]= (char)j;
}

int __init init(void) {
   int err = 0;
   struct priv *priv;
   char ifstr[40];
   sprintf(ifstr, "%s%s", ifname, "%d");
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0))
   child = alloc_netdev(sizeof(struct priv), ifstr, setup);
#else
   child = alloc_netdev(sizeof(struct priv), ifstr, NET_NAME_UNKNOWN, setup);
#endif
   if(child == NULL) {
      ERR("%s: allocate error", THIS_MODULE->name); return -ENOMEM;
   }
   priv = netdev_priv(child);
   priv->parent = __dev_get_by_name(&init_net, link); // parent interface  
   if(!priv->parent) {
      ERR("%s: no such link: %s", THIS_MODULE->name, link);
      err = -ENODEV; goto err;
   }
   if(priv->parent->type != ARPHRD_ETHER && priv->parent->type != ARPHRD_LOOPBACK) {
      ERR("%s: illegal net type", THIS_MODULE->name);
      err = -EINVAL; goto err;
   }
   /* also, and clone its IP, MAC and other information */
   memcpy(child->dev_addr, priv->parent->dev_addr, ETH_ALEN);
   memcpy(child->broadcast, priv->parent->broadcast, ETH_ALEN);
   if((err = dev_alloc_name(child, child->name))) {
      ERR("%s: allocate name, error %i", THIS_MODULE->name, err);
      err = -EIO; goto err;
   }
   register_netdev(child);
   rtnl_lock();
   netdev_rx_handler_register(priv->parent, &handle_frame, NULL);
   rtnl_unlock();
   LOG("module %s loaded", THIS_MODULE->name);
   LOG("%s: create link %s", THIS_MODULE->name, child->name);
   LOG("%s: registered rx handler for %s", THIS_MODULE->name, priv->parent->name);
   return 0;
err:
   free_netdev(child);
   return err;
}

void __exit virt_exit(void) {
   struct priv *priv = netdev_priv(child);
   if(priv->parent) {
      rtnl_lock();
      netdev_rx_handler_unregister(priv->parent);
      rtnl_unlock();
      LOG("unregister rx handler for %sn", priv->parent->name);
   }
   unregister_netdev(child);
   free_netdev(child);
   LOG("module %s unloaded", THIS_MODULE->name);
}

module_init(init);
module_exit(virt_exit);

MODULE_AUTHOR("Oleg Tsiliuric");
MODULE_AUTHOR("Nikita Dorokhin");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("3.2");

EOF



cat > virt1.c << "EOF"
#include 
#include 
#include 
#include 
#include 
#include 

#define ERR(...) printk(KERN_ERR "! "__VA_ARGS__)
#define LOG(...) printk(KERN_INFO "! "__VA_ARGS__)

static char* link = "eth0";
module_param(link, charp, 0);

static char* ifname = "virt"; 
module_param(ifname, charp, 0);

static struct net_device *child = NULL;

static struct net_device_stats stats;

struct priv {
   struct net_device *parent;
};

static rx_handler_result_t handle_frame(struct sk_buff **pskb) {
   struct sk_buff *skb = *pskb;
   if(child) {
      stats.rx_packets++;
      stats.rx_bytes += skb->len;
      LOG("rx: injecting frame from %s to %s", skb->dev->name, child->name);
      skb->dev = child;
      /* netif_receive_skb(skb);
      return RX_HANDLER_CONSUMED; */
      return RX_HANDLER_ANOTHER;
   }
   return RX_HANDLER_PASS;
}

static int open(struct net_device *dev) {
   netif_start_queue(dev);
   LOG("%s: device opened", dev->name);
   return 0;
}

static int stop(struct net_device *dev) {
   netif_stop_queue(dev);
   LOG("%s: device closed", dev->name);
   return 0;
}

static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev) {
   struct priv *priv = netdev_priv(dev);
   stats.tx_packets++;
   stats.tx_bytes += skb->len;
   if(priv->parent) {
      skb->dev = priv->parent;
      skb->priority = 1;
      dev_queue_xmit(skb);
      LOG("tx: injecting frame from %s to %s", dev->name, skb->dev->name);
      return 0;
   }
   return NETDEV_TX_OK;
}

static struct net_device_stats *get_stats(struct net_device *dev) {
   return &stats;
}

static struct net_device_ops crypto_net_device_ops = {
   .ndo_open = open,
   .ndo_stop = stop,
   .ndo_get_stats = get_stats,
   .ndo_start_xmit = start_xmit,
};

// #define MAX_ADDR_LEN    32  
// #define ETH_ALEN        6   /* Octets in one ethernet addr   */ 

static void setup(struct net_device *dev) {
   int j;
   ether_setup(dev);
   memset(netdev_priv(dev), 0, sizeof(struct priv));
   dev->netdev_ops = &crypto_net_device_ops;
   for(j = 0; j < ETH_ALEN; ++j) // fill in the MAC address with a phoney 
      dev->dev_addr[j] = (char)j;
}

int __init init(void) {
   int err = 0;
   struct priv *priv;
   char ifstr[40];
   sprintf(ifstr, "%s%s", ifname, "%d");
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0))
   child = alloc_netdev(sizeof(struct priv), ifstr, setup);
#else
   child = alloc_netdev(sizeof(struct priv), ifstr, NET_NAME_UNKNOWN, setup);
#endif
   if(child == NULL) {
      ERR("%s: allocate error", THIS_MODULE->name); return -ENOMEM;
   }
   priv = netdev_priv(child);
   priv->parent = __dev_get_by_name(&init_net, link); // parent interface  
   if(!priv->parent) {
      ERR("%s: no such link: %s", THIS_MODULE->name, link);
      err = -ENODEV; goto err;
   }
   if(priv->parent->type != ARPHRD_ETHER && priv->parent->type != ARPHRD_LOOPBACK) {
      ERR("%s: illegal net type", THIS_MODULE->name);
      err = -EINVAL; goto err;
   }
   /* also, and clone its IP, MAC and other information */
   memcpy(child->dev_addr, priv->parent->dev_addr, ETH_ALEN);
   memcpy(child->broadcast, priv->parent->broadcast, ETH_ALEN);
   if((err = dev_alloc_name(child, child->name))) {
      ERR("%s: allocate name, error %i", THIS_MODULE->name, err);
      err = -EIO; goto err;
   }
   register_netdev(child);
   rtnl_lock();
   netdev_rx_handler_register(priv->parent, &handle_frame, NULL);
   rtnl_unlock();
   LOG("module %s loaded", THIS_MODULE->name);
   LOG("%s: create link %s", THIS_MODULE->name, child->name);
   LOG("%s: registered rx handler for %s", THIS_MODULE->name, priv->parent->name);
   return 0;
err:
   free_netdev(child);
   return err;
}

void __exit virt_exit(void) {
   struct priv *priv = netdev_priv(child);
   if(priv->parent) {
      rtnl_lock();
      netdev_rx_handler_unregister(priv->parent);
      rtnl_unlock();
      LOG("unregister rx handler for %sn", priv->parent->name);
   }
   unregister_netdev(child);
   free_netdev(child);
   LOG("module %s unloaded", THIS_MODULE->name);
}

module_init(init);
module_exit(virt_exit);

MODULE_AUTHOR("Oleg Tsiliuric");
MODULE_AUTHOR("Nikita Dorokhin");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("3.2");

EOF


cat > virt2.c << "EOF"
#include 
#include 
#include 
#include 
#include 
#include 

#define ERR(...) printk(KERN_ERR "! "__VA_ARGS__)
#define LOG(...) printk(KERN_INFO "! "__VA_ARGS__)

static char* link = "eth0";
module_param(link, charp, 0);

static char* ifname = "virt"; 
module_param(ifname, charp, 0);

static struct net_device *child = NULL;

struct priv {
   struct net_device *parent;
};

static rx_handler_result_t handle_frame(struct sk_buff **pskb) {
   struct sk_buff *skb = *pskb;
   if(child) {
      child->stats.rx_packets++;
      child->stats.rx_bytes += skb->len;
      LOG("rx: injecting frame from %s to %s", skb->dev->name, child->name);
      skb->dev = child;
      /* netif_receive_skb(skb);
      return RX_HANDLER_CONSUMED; */
      return RX_HANDLER_ANOTHER;
   }
   return RX_HANDLER_PASS;
}

static int open(struct net_device *dev) {
   netif_start_queue(dev);
   LOG("%s: device opened", dev->name);
   return 0;
}

static int stop(struct net_device *dev) {
   netif_stop_queue(dev);
   LOG("%s: device closed", dev->name);
   return 0;
}

static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev) {
   struct priv *priv = netdev_priv(dev);
   child->stats.tx_packets++;
   child->stats.tx_bytes += skb->len;
   if(priv->parent) {
      skb->dev = priv->parent;
      skb->priority = 1;
      dev_queue_xmit(skb);
      LOG("tx: injecting frame from %s to %s", dev->name, skb->dev->name);
      return 0;
   }
   return NETDEV_TX_OK;
}

static struct net_device_stats *get_stats(struct net_device *dev) {
   return &dev->stats;
}

static struct net_device_ops crypto_net_device_ops = {
   .ndo_open = open,
   .ndo_stop = stop,
   .ndo_get_stats = get_stats,
   .ndo_start_xmit = start_xmit,
};

// #define MAX_ADDR_LEN    32  
// #define ETH_ALEN        6   /* Octets in one ethernet addr   */ 

static void setup(struct net_device *dev) {
   int j;
   ether_setup(dev);
   memset(netdev_priv(dev), 0, sizeof(struct priv));
   dev->netdev_ops = &crypto_net_device_ops;
   for(j = 0; j < ETH_ALEN; ++j) // fill in the MAC address with a phoney 
      dev->dev_addr[j] = (char)j;
}

int __init init(void) {
   int err = 0;
   struct priv *priv;
   char ifstr[40];
   sprintf(ifstr, "%s%s", ifname, "%d");
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0))
   child = alloc_netdev(sizeof(struct priv), ifstr, setup);
#else
   child = alloc_netdev(sizeof(struct priv), ifstr, NET_NAME_UNKNOWN, setup);
#endif
   if(child == NULL) {
      ERR("%s: allocate error", THIS_MODULE->name); return -ENOMEM;
   }
   priv = netdev_priv(child);
   priv->parent = __dev_get_by_name(&init_net, link); // parent interface  
   if(!priv->parent) {
      ERR("%s: no such link: %s", THIS_MODULE->name, link);
      err = -ENODEV; goto err;
   }
   if(priv->parent->type != ARPHRD_ETHER && priv->parent->type != ARPHRD_LOOPBACK) {
      ERR("%s: illegal net type", THIS_MODULE->name);
      err = -EINVAL; goto err;
   }
   /* also, and clone its IP, MAC and other information */
   memcpy(child->dev_addr, priv->parent->dev_addr, ETH_ALEN);
   memcpy(child->broadcast, priv->parent->broadcast, ETH_ALEN);
   if((err = dev_alloc_name(child, child->name))) {
      ERR("%s: allocate name, error %i", THIS_MODULE->name, err);
      err = -EIO; goto err;
   }
   register_netdev(child);
   rtnl_lock();
   netdev_rx_handler_register(priv->parent, &handle_frame, NULL);
   rtnl_unlock();
   LOG("module %s loaded", THIS_MODULE->name);
   LOG("%s: create link %s", THIS_MODULE->name, child->name);
   LOG("%s: registered rx handler for %s", THIS_MODULE->name, priv->parent->name);
   return 0;
err:
   free_netdev(child);
   return err;
}

void __exit virt_exit(void) {
   struct priv *priv = netdev_priv(child);
   if(priv->parent) {
      rtnl_lock();
      netdev_rx_handler_unregister(priv->parent);
      rtnl_unlock();
      LOG("unregister rx handler for %sn", priv->parent->name);
   }
   unregister_netdev(child);
   free_netdev(child);
   LOG("module %s unloaded", THIS_MODULE->name);
}

module_init(init);
module_exit(virt_exit);

MODULE_AUTHOR("Oleg Tsiliuric");
MODULE_AUTHOR("Nikita Dorokhin");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("3.2");

EOF



ЧДД?
make
insmod virt.ko link=p7p1
ifconfig virt0 192.168.50.2
ping 192.168.50.1
rmmod virt
virt.1.ko link=p7p1
lsmod | head -n3
rmmod 'virt.1'


Протокол сетевого уровня:

На этом уровне (L2) обеспечивается обработка таких протоколов как IP/IPv4/IPv6/IPX/ICMP/RIP/OSPF/ARP 
или оригинальных пользовательских протоколов.
Для установки обработчиков сетевого уровня предоставляются API сетевого уровня ()

Фактически в протокольных модулях мы должны добавить фильтр, через который проходят буферы сокетов из 
входящего потока интерфейса. (Исходящий поток реализуется проще)
На обработку функции отбираются те буферы сокетов, которые удовлетворяют критериям,
заложенным в структуре "struct packet_type"


Примеры кода:
cat > Makefile << "EOF"
CURRENT = $(shell uname -r)
KDIR = /lib/modules/$(CURRENT)/build
PWD = $(shell pwd)
DEST = /lib/modules/$(CURRENT)/misc
EXTRA_CFLAGS += -std=gnu99

TARGET1 = net_proto
TARGET2 = net_proto2
TARGET3 = net_protox
TARGET4 = trn_proto

obj-m   := $(TARGET1).o 
#$(TARGET2).o $(TARGET3).o

all: modules clean

modules:
        $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
        @rm -f *.o .*.cmd .*.flags *.mod.c *.order *.d
        @rm -f .*.*.cmd *.symvers *~ *.*~
        @rm -fR .tmp* .*.cmd .*.d
        @rm -rf .tmp_versions

disclean:  clean
        @rm -f *.ko
EOF


cat > net_proto.c << "EOF" 
#include "net_common.c"

// int (*func) (struct sk_buff *, struct net_device *,
//              struct packet_type *,
//              struct net_device *);

int test_pack_rcv(struct sk_buff *skb, struct net_device *dev,
                  struct packet_type *pt, struct net_device *odev) {
//   printk(KERN_INFO "packet received with length: %un", skb->len);
   kfree_skb(skb);
   return skb->len;
};

#define TEST_PROTO_ID 0x1234
static struct packet_type test_proto = {
   __constant_htons(ETH_P_ALL),  // may be: __constant_htons(TEST_PROTO_ID),
   false,
   NULL,
   test_pack_rcv,
   (void*)1,
   NULL
};

static int __init my_init(void) {
   dev_add_pack(&test_proto);
   LOG("module %s loadedn", THIS_MODULE->name);
   return 0; 
}

static void __exit my_exit(void) {
   dev_remove_pack(&test_proto);
   LOG("module %s unloadedn", THIS_MODULE->name);
}

/*
struct packet_type {            // 5.4
        __be16                  type;           // This is really htons(ether_type). 
        bool                    ignore_outgoing;
        struct net_device       *dev;      // NULL is wildcarded here        
        int                     (*func) (struct sk_buff *,
                                         struct net_device *,
                                         struct packet_type *,
                                         struct net_device *);
        void                    (*list_func) (struct list_head *,
                                              struct packet_type *,
                                              struct net_device *);
        bool                    (*id_match)(struct packet_type *ptype,
                                            struct sock *sk);
        void                    *af_packet_priv;
        struct list_head        list;
};
*/


/* struct packet_type {
   __be16 type;            // This is really htons(ether_type). 
   struct net_device *dev; // NULL is wildcarded here
   (*func) ( struct sk_buff *, struct net_device *, 
             struct packet_type *, struct net_device * );
   struct sk_buff *(*gso_segment)( struct sk_buff *skb, int features );
   int (*gso_send_check)( struct sk_buff *skb );
   struct sk_buff **(*gro_receive)( struct sk_buff **head, struct sk_buff *skb );
   int (*gro_complete)( struct sk_buff *skb );
   void *af_packet_priv;
   struct list_head list;
}; */

EOF


cat > net_proto2.c << "EOF"
#include "net_common.c"

static int debug = 0;
module_param( debug, int, 0 );

static char* link = NULL;
module_param( link, charp, 0 );

int test_pack_rcv_1( struct sk_buff *skb, struct net_device *dev,
                     struct packet_type *pt, struct net_device *odev ) {
   int s = atomic_read( &skb->users );  
   kfree_skb( skb );
   if( debug > 0 ) LOG( "function #1 - %p => users: %d->%dn", skb, s, atomic_read( &skb->users ) );
   return skb->len;
};

int test_pack_rcv_2( struct sk_buff *skb, struct net_device *dev,
                     struct packet_type *pt, struct net_device *odev ) {
   int s = atomic_read( &skb->users );  
   kfree_skb( skb );
   if( debug > 0 ) LOG( "function #2 - %p => users: %d->%dn", skb, s, atomic_read( &skb->users ) );
   return skb->len;
};

static struct packet_type 
test_proto1 = {
   __constant_htons( ETH_P_IP ),
   NULL,
   test_pack_rcv_1,
   (void*)1,
   NULL
},
test_proto2 = {
   __constant_htons( ETH_P_IP ),
   NULL,
   test_pack_rcv_2,
   (void*)1,
   NULL
};

static int __init my_init( void ) {
   if( link != NULL ) {
      struct net_device *dev = __dev_get_by_name( &init_net, link );
      if( NULL == dev ) { 
         ERR( "%s: illegal link", link );
         return -EINVAL; 
      }
      test_proto1.dev = test_proto2.dev = dev;
   }
   dev_add_pack( &test_proto1 );
   dev_add_pack( &test_proto2 );
   if( NULL == test_proto1.dev ) LOG( "module %s loaded for all linksn", THIS_MODULE->name );
   else LOG( "module %s loaded for link %sn", THIS_MODULE->name, link );
   return 0; 
}

static void __exit my_exit( void ) {
   if( test_proto2.dev != NULL ) dev_put( test_proto2.dev );
   if( test_proto1.dev != NULL ) dev_put( test_proto1.dev );
   dev_remove_pack( &test_proto2 );
   dev_remove_pack( &test_proto1 );
   LOG( "module %s unloadedn", THIS_MODULE->name );
}


/* struct packet_type {
   __be16 type;            // This is really htons(ether_type). 
   struct net_device *dev; // NULL is wildcarded here
   (*func) ( struct sk_buff *, struct net_device *, 
             struct packet_type *, struct net_device * );
   struct sk_buff *(*gso_segment)( struct sk_buff *skb, int features );
   int (*gso_send_check)( struct sk_buff *skb );
   struct sk_buff **(*gro_receive)( struct sk_buff **head, struct sk_buff *skb );
   int (*gro_complete)( struct sk_buff *skb );
   void *af_packet_priv;
   struct list_head list;
}; */

EOF



cat > net_protox.c << "EOF"
#include "net_common.c"

int test_pack_rcv( struct sk_buff *skb, struct net_device *dev,
                   struct packet_type *pt, struct net_device *odev ) {
   LOG( "packet received with length: %un", skb->len );
   kfree_skb( skb );
   return skb->len;
};

#define TEST_PROTO_ID 0x1234
static struct packet_type test_proto = {
//   __constant_htons( TEST_PROTO_ID ),
   __constant_htons( ETH_P_IP ),
   NULL,
   test_pack_rcv,
   (void*)1,
   NULL
};

static int my_open( struct net_device *dev ) {
   LOG( "my_open(%s)n", dev->name );
   /* start up the transmission queue */
   netif_start_queue( dev );
   return 0;
}

static int my_close( struct net_device *dev ) {
   LOG( "my_close(%s)n", dev->name );
   /* shutdown the transmission queue */
   netif_stop_queue( dev );
   return 0;
}

static int stub_start_xmit( struct sk_buff *skb, struct net_device *dev ) {
   dev_kfree_skb( skb );
   return 0;
}

static struct net_device_ops ndo = {
   .ndo_open = my_open,
   .ndo_stop = my_close,
   .ndo_start_xmit = stub_start_xmit,
};

static void __init my_setup( struct net_device *dev ) {
   int j;
   for( j = 0; j < ETH_ALEN; ++j )
      dev->dev_addr[ j ] = (char)j;    // Fill the MAC address
   ether_setup( dev );
   dev->netdev_ops = &ndo;
}

static struct net_device *dev;

static int __init my_init( void ) {
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0))
   dev = alloc_netdev( 0, "xxx%d", my_setup );
#else
   dev = alloc_netdev( 0, "xxx%d", NET_NAME_UNKNOWN, my_setup );
#endif
   if( register_netdev( dev ) ) {
      ERR( "failed to registern" );
      free_netdev( dev );
      return -1;
   }
   test_proto.dev = dev;
   dev_add_pack( &test_proto );
   LOG( "module loadedn" );
   return 0; 
}

static void __exit my_exit( void ) {
   dev_remove_pack( &test_proto );
   unregister_netdev( dev );
   free_netdev( dev );
   LOG( "module unloadedn" );
}

/* struct packet_type {
   __be16 type;            // This is really htons(ether_type)
   struct net_device *dev; // NULL is wildcarded here
   (*func) ( struct sk_buff *, struct net_device *, 
             struct packet_type *, struct net_device * );
   struct sk_buff *(*gso_segment)( struct sk_buff *skb, int features );
   int (*gso_send_check)( struct sk_buff *skb );
   struct sk_buff **(*gro_receive)( struct sk_buff **head, struct sk_buff *skb );
   int (*gro_complete)( struct sk_buff *skb );
   void *af_packet_priv;
   struct list_head list;
}; */

EOF


cat > trn_proto.c < "EOF" 
#include 
#include "net_common.c"

int test_proto_rcv( struct sk_buff *skb ) {
   printk( KERN_INFO "Packet received with length: %un", skb->len );
   return skb->len;
};

/* This is used to register protocols. 
struct net_protocol {
   int  (*handler)( struct sk_buff *skb );
   void (*err_handler)( struct sk_buff *skb, u32 info );
   int  (*gso_send_check)( struct sk_buff *skb );
   struct sk_buff *(*gso_segment)( struct sk_buff *skb,
                                   int features);
   struct sk_buff **(*gro_receive)( struct sk_buff **head,
                                    struct sk_buff *skb );
   int (*gro_complete)( struct sk_buff *skb );
   unsigned int no_policy:1,
                netns_ok:1;
}; */
static struct net_protocol test_proto = {
   .handler = test_proto_rcv,
   .err_handler = 0,
   .no_policy = 0,
};

#define PROTO IPPROTO_ICMP
//#define PROTO IPPROTO_TCP
//#define PROTO IPPROTO_RAW
// #define PROTO IPPROTO_UDP

static int __init my_init( void ) {
   int ret;
   if( ( ret = inet_add_protocol( &test_proto, PROTO ) ) < 0 ) {
      printk( KERN_INFO "proto init: can't add protocoln");
      return ret;
   };
   printk( KERN_INFO "proto module loadedn" );
   return 0; 
}

static void __exit my_exit( void ) {
   inet_del_protocol( &test_proto, PROTO );
   printk( KERN_INFO "proto module unloadedn" );
}


EOF



ЧТДСХ?
insmod net_proto.ko
insmod net_protox.ko
insmod trn_proto.ko 
insmod net_proto2.ko link=p7p1
dmesg | tail -n30 | grep -v ^audit
ls -R /sys/module/trn_proto 
rmmod trn_proto
dmesg | tail -n60 | grep -v ^audit



Очень большое число идентификаторов протоколов содержится в файле 

Методика проверки драйвера на утечку памяти:

Утечка памяти может приводить к краху системы.

0. Запустить команду free и зафиксировать показания
free 

1. На тестируемом узле запустить:

cat > client << "EOF" 
LIMIT=1000000
for ((a=1; a <= LIMIT ; a++))
do
   rm z.txt
   nc localhost 12345 > z.txt
   sleep 1
done

EOF

./client



2. На стороннем доступном узле запустить:

cat > server < "EOF"
dd if=/dev/zero of=z.txt bs=1M count=1

LIMIT=1000000
for ((a=1; a <= LIMIT ; a++))
do
   cat z.txt | nc -l 12345
done

EOF


./server

3. Выждать 10-20 минут
За это время система не должна зависнуть.
А результат команды free  должен быть близок к результату из пункта 0.



Драйвер virt-full


cat > Makefile << "EOF" 
CURRENT = $(shell uname -r)
KDIR = /lib/modules/$(CURRENT)/build
PWD = $(shell pwd)

TARGET = virt
obj-m := $(TARGET).o 

all: default clean

default:
        $(MAKE) -C $(KDIR) M=$(PWD) modules 

clean:
        @rm -f *.o .*.cmd .*.flags *.mod.c *.order
        @rm -f .*.*.cmd *.symvers *~ *.*~ TODO.*
        @rm -fR .tmp* *.mod
        @rm -rf .tmp_versions

disclean: clean
        @rm -f $(TARGET).ko

EOF



cat >  virt.c << "EOF"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  

#define ERR(...) printk( KERN_ERR "! "__VA_ARGS__ )
#define LOG(...) printk( KERN_INFO "! "__VA_ARGS__ )
#define DBG(...) if( debug != 0 ) printk( KERN_INFO "! "__VA_ARGS__ )

static char* link = "eth0";
module_param( link, charp, 0 );

static char* ifname = "virt"; 
module_param( ifname, charp, 0 );

static int debug = 0;
module_param( debug, int, 0 );

static struct net_device *child = NULL;
static struct net_device_stats stats;
static u32 child_ip;

struct priv {
   struct net_device *parent;
};

static char* strIP( u32 addr ) {     // диагностика IP в точечной нотации
   static char saddr[ MAX_ADDR_LEN ];
   sprintf( saddr, "%d.%d.%d.%d",
            ( addr ) & 0xFF, ( addr >> 8 ) & 0xFF,
            ( addr >> 16 ) & 0xFF, ( addr >> 24 ) & 0xFF
          );
   return saddr;
}

static char* strAR_IP( unsigned char addr[ 4 ] ) {
   static char saddr[ MAX_ADDR_LEN ];
   sprintf( saddr, "%d.%0d.%d.%d",
            addr[ 0 ], addr[ 1 ], addr[ 2 ], addr[ 3 ] );
   return saddr;
}

// :
struct arp_eth_body {
   unsigned char  ar_sha[ ETH_ALEN ];     // sender hardware address      
   unsigned char  ar_sip[ 4 ];            // sender IP address            
   unsigned char  ar_tha[ ETH_ALEN ];     // target hardware address      
   unsigned char  ar_tip[ 4 ];            // target IP address            
};

static rx_handler_result_t handle_frame( struct sk_buff **pskb ) {
   struct sk_buff *skb = *pskb;
   if( skb->protocol == htons( ETH_P_IP ) ) {
      struct iphdr *ip = ip_hdr( skb );
      char *addr = strIP( ip->daddr );
      DBG( "rx: IP4 to IP=%s", addr );
      if( ip->daddr != child_ip )
         return RX_HANDLER_PASS; 
   }
   else if( skb->protocol == htons( ETH_P_ARP ) ) {
      struct arphdr *arp = arp_hdr( skb );
      struct arp_eth_body *body = (void*)arp + sizeof( struct arphdr ); 
      int i, ip = child_ip;
      DBG( "rx: ARP for %s", strAR_IP( body->ar_tip ) );
      for( i = 0; i < sizeof( body->ar_tip ); i++ ) {
         if( ( ip & 0xFF ) != body->ar_tip[ i ] ) break;
         ip = ip >> 8;
      }
      if( i < sizeof( body->ar_tip ) )
         return RX_HANDLER_PASS; 
   }
   else           // не ARP и не IP4
      return RX_HANDLER_PASS; 
   stats.rx_packets++;
   stats.rx_bytes += skb->len;
   DBG( "rx: injecting frame from %s to %s", skb->dev->name, child->name );
   skb->dev = child;
   return RX_HANDLER_ANOTHER;
}

static netdev_tx_t start_xmit( struct sk_buff *skb, struct net_device *dev ) {
   struct priv *priv = netdev_priv( dev );
   stats.tx_packets++;
   stats.tx_bytes += skb->len;
   if( priv->parent ) {
      skb->dev = priv->parent;
      skb->priority = 1;
      dev_queue_xmit( skb );
      LOG( "tx: injecting frame from %s to %s", dev->name, skb->dev->name );
      return 0;
   }
   return NETDEV_TX_OK;
}

static int open( struct net_device *dev ) {
   struct in_device *in_dev = dev->ip_ptr;
   struct in_ifaddr *ifa = in_dev->ifa_list;      /* IP ifaddr chain */
   char sdebg[ 40 ] = "";
   LOG( "%s: device opened", dev->name );
   child_ip = ifa->ifa_address;
   sprintf( sdebg, "%s:", strIP( ifa->ifa_address ) );
   strcat( sdebg, strIP( ifa->ifa_mask ) );
   DBG( "%s: %s", dev->name, sdebg );
   netif_start_queue( dev );
   return 0;
}

static int stop( struct net_device *dev ) {
   LOG( "%s: device closed", dev->name );
   netif_stop_queue( dev );
   return 0;
}

static struct net_device_stats *get_stats( struct net_device *dev ) {
   return &stats;
}

static struct net_device_ops crypto_net_device_ops = {
   .ndo_open = open,
   .ndo_stop = stop,
   .ndo_get_stats = get_stats,
   .ndo_start_xmit = start_xmit,
};

static void setup( struct net_device *dev ) {
   int j;
   ether_setup( dev );
   memset( netdev_priv(dev), 0, sizeof( struct priv ) );
   dev->netdev_ops = &crypto_net_device_ops;
   for( j = 0; j < ETH_ALEN; ++j ) // fill in the MAC address with a phoney 
      dev->dev_addr[ j ] = (char)j;
}

int __init init( void ) {
   int err = 0;
   struct priv *priv;
   char ifstr[ 40 ];
   sprintf( ifstr, "%s%s", ifname, "%d" );
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0))
   child = alloc_netdev( sizeof( struct priv ), ifstr, setup );
#else
   child = alloc_netdev( sizeof( struct priv ), ifstr, NET_NAME_UNKNOWN, setup );
#endif
   if( child == NULL ) {
      ERR( "%s: allocate error", THIS_MODULE->name ); return -ENOMEM;
   }
   priv = netdev_priv( child );
   priv->parent = __dev_get_by_name( &init_net, link ); // parent interface  
   if( !priv->parent ) {
      ERR( "%s: no such net: %s", THIS_MODULE->name, link );
      err = -ENODEV; goto err;
   }
   if( priv->parent->type != ARPHRD_ETHER && priv->parent->type != ARPHRD_LOOPBACK ) {
      ERR( "%s: illegal net type", THIS_MODULE->name );
      err = -EINVAL; goto err;
   }
   /* also, and clone its IP, MAC and other information */
   memcpy( child->dev_addr, priv->parent->dev_addr, ETH_ALEN );
   memcpy( child->broadcast, priv->parent->broadcast, ETH_ALEN );
   if( ( err = dev_alloc_name( child, child->name ) ) ) {
      ERR( "%s: allocate name, error %i", THIS_MODULE->name, err );
      err = -EIO; goto err;
   }
   register_netdev( child );
   rtnl_lock();
   netdev_rx_handler_register( priv->parent, &handle_frame, NULL );
   rtnl_unlock();
   LOG( "module %s loaded", THIS_MODULE->name );
   LOG( "%s: create link %s", THIS_MODULE->name, child->name );
   LOG( "%s: registered rx handler for %s", THIS_MODULE->name, priv->parent->name );
   return 0;
err:
   free_netdev( child );
   return err;
}

void __exit virt_exit( void ) {
   struct priv *priv = netdev_priv( child );
   rtnl_lock();
   netdev_rx_handler_unregister( priv->parent );
   rtnl_unlock();
   LOG( "%s: unregister rx handler for %sn", THIS_MODULE->name, priv->parent->name );
   unregister_netdev( child );
   free_netdev( child );
   LOG( "module %s unloaded", THIS_MODULE->name );
}

module_init( init );
module_exit( virt_exit );

MODULE_AUTHOR( "Oleg Tsiliuric" );
MODULE_AUTHOR( "Nikita Dorokhin" );
MODULE_LICENSE( "GPL v2" );
MODULE_VERSION( "4.5" );

EOF


ЧДТ?
make

insmod ./virt.ko link=p7p1 debug=1
dmesg | tail -n3
ifconfig virt0 192.168.50.2
dmesg | tail -n26
tcpdump -i virt0 
dmesg | tail -n19
ping 192.168.50.1
dmesg | tail -n30
rmmod virt
dmesg | tail -n4


ls -l *.ko
make
ls -l
sudo insmod virt.ko
sudo insmod virt.ko link=eno2
lsmod | head -n4
ping -I eno2 -c3 192.168.1.142
dmesg | tail -n8
dmesg | tail -n90
lsmod | head -n4
ip a s
rmmod virt


TOR

Прокси/VPN

TOR (The Omion Router) 
Свободное и открытое программное обеспечение для реализации второго (V2) 
и третьего (V3) поколения так называемой луковой маршрутизации:
ваши данные - это сердцевина луковицы
а их защита это слоя вокруг

ПК <=> входная точка <=> опорная точка <=> выходная точка <=> веб-сайт

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


Установка tor для любых служб
apt show tor
apt install tor
systemctl status tor
pd -A | grep tor
netstat -l -t | grep 9050
curl check-host.net/ip
curl -x socks4://127.0.0.1:9050 check-host.net/ip
whois 8.8.8.8 | grep -i country:




Свой ресурс onion
Для этого нужно отредактировать файл "/etc/tor/torrc"
vim /etc/tor/torrc
------------------
HiddenServiceDir /var/lib/tor/onion.service
HiddenServiceProt 80 127.0.0.1:80
HiddenServicePort 443 127.0.0.1:443
#HiddenServicePort 22 127.0.0.1:22
------------------

После этого в каталоге "/var/lib/tor" у вас появится новый каталог с тем произвольным именем onion.service
Этот каталог будет содержать файлы
hostname - ваше имя onion


ПО:
torsocks wget -q http://asdjasidhiashdojaspokppskjfpajppkspfvvbnbnahj13kldk.onion/file.html
torsocks ssh asdjasidhiashdojaspokppskjfpajppkspfvvbnbnahj13kldk.onion



Mesh-сети

Mesh-сеть - это распределенная одноранговая сеть, основанная на ячеистой топологии.
Каждый узел в такой сети обладает такими же полномочиями, что и все остальные,
груб говоря, все узлы в mesh-сети равны.


Яркий представитель меш сети это Yggdrasil (есть альтернатива Riv-mesh работает по такому же принципу)
Пиринговая - это название ячеистых одноранговых сетей, в которой каждый хост связывается с несколькими известными ему пирами, 
соседями по сети, через которые хост организует свой адаптивный роутинг в сеть.
Yggdrasil - это пиринговая сеть, использующая IPv6-адресацию, весь трафик в которой шифруется несимметричным кодированием( с приватным и публичным ключами)

Проект Yggdrasil использует адреса IPv6 из подсети 0200::/7, который никак не пересекается с IANA(2000::/3)


Где брать:
https://github.com/yggdrasil-network/yggdrasil-go


Установка:
Читать:
https://yggdrasil-network.github.io/installation-linux-deb.html

Ключи для деб:
mkdir -p /usr/local/apt-keys
gpg --fetch-keys https://neilalexander.s3.dualstack.eu-west-2.amazonaws.com/deb/key.txt
gpg --export BC1BF63BD10B8F1A | sudo tee /usr/local/apt-keys/yggdrasil-keyring.gpg > /dev/null
echo 'deb [signed-by=/usr/local/apt-keys/yggdrasil-keyring.gpg] http://neilalexander.s3.dualstack.eu-west-2.amazonaws.com/deb/ debian yggdrasil' | sudo tee /etc/apt/sources.list.d/yggdrasil.list

Ставим:
apt-get install dirmngr
apt-get install yggdrasil
systemctl enable yggdrasil
systemctl start yggdrasil


Конфиг:
/etc/yggdrasil.conf
Тут надо добавить в секцию "Peers {}" пиров с которыми будем взаимодействовать 
пиры смотри тут:
https://github.com/yggdrasil-network/public-peers
проверить этим:
https://github.com/zhoreeq/peer_checker.py


или вот апдейтер:
https://github.com/ygguser/peers_updater
cargo build --release
cd traget/release
./peers_updater -p > peers.txt


После перезапустить:
systemctl restart yggdrasil


Проверка какие пиры использует хост:
yggdrasilctl getPeers

Показать адрес и другую информацию:
yggdrasilctl getSelf



Обнаружена пиров:
MPL(Multicast Peer Discovery) - технология автоматического обнаружения пиров
порт UDP-9001
Для работы в локальной сети нужен настроенный IPv6

Для yggdrasil могут использоваться адреса вида 02XX и 03XX


Трюк:
Если в сети уже установлен клиент Yggdrasil можно сделать маршрутизацию через него для других пк в сети
У вас на ПК с клиентом должен быть включен "IPv6 forward"
Узнаем IPv6 на клиенте ygg например так:
yggdrasilctl getSelf
Например на адресс там:
31d:4cbf:9eaf:2399::/64
На устройстве где у нас Yddgrasil добавим адрес на интерфейс который смотрит в лакалку так:
ip a a  31d:4cbf:9eaf:2399::1/64 dev eth1
включим ip forwarding:
systemctl -w net.ipv6.conf.all.all.forwarding=1

На ПК который мы хотим научить ходить в ygg:
ip a a  31d:4cbf:9eaf:2399::5/64 dev eth1
ip -6 route add 0200::/7 via 31d:4cbf:9eaf:2399::1
ping ygg.linux-ru.lib

l2p

l2p - представляет собой изолированную автономную сеть Даркнет.
https://github.com/PurpleI2P/i2pd


apt isntall libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-thread-dev libssk-dev

Сборка:
cd /opt
git clone https://github.com/PurpleI2P/i2pd.git
cd i3pf/build
cmake -DCMAKE_BUILD_TYPE=release
make -j2
make install


Для того что бы все заработало, нужно из каталога сборки скопировать конфиги в домашний каталог пользователя
cp -R contrib/certificates $HOME/.i2pd
cp contrib/i2pd.conf $HOME/.i2pd/
cp contrib/tunnels.conf $HOME/.i2pd/

теперь можно запустить
i2pd

проверяем доступность локальной веб консоли
telnet 127.0.0.1 7070

Локальный хеша адресов можно обнаружить в каталоге "$HOME/.i2pd/addressbook"

Для рганизации тунеля нужно будет настроить файл из .i2pd/tennels.conf