Уровни OSI
1 2 3 4 5 6 7 |
1. Физический 2. Канальный 3. Сетевой 4. Транспортный 5. Сеансовый 6. Представительный 7. Прикладной |
Уровни OSI TCP/IP
1 2 3 4 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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 адреса:
1 2 3 |
L3 Каждый сетевой интерфейс имеет свой IP-адрес. Конечные точки коммуникации знают о друг друге только по их IP адресу |
Маски и подсети:
1 2 3 4 5 6 7 8 |
Разделение на классы 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) |
Широковещательный и групповой обмен:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Всего существует три типа 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 |
Частные адреса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
Никогда не маршрутизируются шлюзами 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
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{}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
Функция inet_pton() преобразовывает символьные изображения IP адреса в структуру адреса int inet_pton(int af, const char *src, void *dst); af - семейство адресов, которые может быть записано только одной из символьных констант: AF_INET или AF_INET6 означает IP-адрес IPv4 или IPv6 (</usr/include/bits/socket.h>) 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 это (определяется в <linux/in.h>): struct in_addr{ __br32 s_addr; }; для AF_INET6 это (определяется в <linux/in6.h>: 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()
1 2 3 4 5 6 7 8 9 |
Функция 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 В заголовочном файле <arpa/inet.h> представлено еще весьма много полезных функций |
Код программы adr для преобразования ip адреса
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
cat > adr.c << "EOF" #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> 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|<num>} string\n", 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("%s\n", 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 / разрешение имен
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Человеку сложно запомнить цифры, запомнить слова проще, собственно это и привело к появлению 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 |
Разрешение имен в программном коде:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
cat > gclie.c << "EOF" #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #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: %s\n", 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 connect\n"); 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 %d\n", j); continue; } if (write(sfd, argv[j], len) != len) { fprintf(stderr, "partial/failed write\n"); exit(EXIT_FAILURE); } nread = read(sfd, buf, BUF_SIZE); if (nread == -1) { perror("read"); exit(EXIT_FAILURE); } printf("Received %ld bytes: %s\n", (long)nread, buf); } exit(EXIT_SUCCESS); } EOF cat > gserv.c << "EOF" #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/socket.h> #include <netdb.h> #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 port\n", 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: %s\n", 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 bind\n"); 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:%s\n", (long)nread, host, service); else fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s)); if (sendto(sfd, buf, nread, 0, (struct sockaddr*)&peer_addr, peer_addr_len) != nread) fprintf(stderr, "Error sending response\n"); } } EOF Для компиляции используй: gcc -o gclie gclie.c gcc -o gserv gserv.c Запуск: ./gserv 6000 ./gclie localhost 6000 privet ./gclie 127.0.0.1 6000 privet Что тут происходи: Функция <netdb.h> #include <netdb.h> 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
cat > Makefile << "EOF" CC = gcc -Wall SRC = adr gclie gserv all: $(SRC) clean disclean: rm -f $(SRC) EOF # Исправить восемь пробелов на TAB sed -i 's/ /\t\t/g' Makefile Для компиляции используй: make |
Сетевые интерфейсы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
Посмотреть текущие сетевые интерфейсы: 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 |
Таблица маршрутизации (роутинг)
1 2 3 4 5 6 7 8 9 |
route -n route -n -6 ip route --help ip route show ip -6 route show Если все рассматриваемые ранее параметры: IP-адрес, маска, префикс и д.р. - это атрибуты сетевого интерфейса, то таблица роутинга - это атрибут сетевого хоста в целом, это таблица ядра операционной системы. 0.0.0.0 - default - шлюз последней надежды |
Алиасные IP-адреса / дополнительный IP адрес из другой сети
1 2 3 4 5 |
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 |
Петлевой интерфейс:
1 2 3 4 5 |
На любом компьютере хосте, даже если он никак не использует сеть (не подключен к сети), присутствует петлевой интерфейс (loopback, интерфейс lo) под петлевой интерфейс выделена группа 127/8 и ::1 Петлевой интерфейс позволяет программам на компьютере использовать стек TCP/IP локально. |
Переименование сетевого интерфейса
1 |
ip link set dev eno1 name eth0 |
Альтернативные имена (ядро выше 5.4.0)
1 |
ip link property add dev eth0 altname eno99 |
Порты транспортного уровня:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
Каждому протоколу более высоких уровней (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, который занял не одно десятилетие. |
Инструменты диагностики:
1 2 3 4 |
посмотреть линки: ip link Проверить таблицу маршрутизации: route -n |
Инструменты наблюдения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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 |
Инструменты тестирования:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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 4 |
Все сетевые продукты состоят из трех основных компонентов 1) Протокол 2) Клиент 3) Сервис |
mc (Midnight Commnader)
1 2 3 4 5 6 7 8 9 10 |
Есть очень удобный способ подключится по ssh к удаленной машине Называется он "Shell-соединение" перед использованием этого способа убедитесь что у вас подключается по ssh. TAB - перемещение между правой и левой лакацией Enter - Свободное перемещение по каталогам F5 - копировать F6 - перемещать F3 - просматривать F4 - редактировать |
SSH
1 2 3 4 5 6 7 |
Графическая сессия ssh -X user@server xclock ssh -Y user@server VirtualBox Не все так может быть запущенно, но многие программы работают. Не которые программы сами умеют подключатся через shell. Например mc, virt-manager и etc |
Протокол DHCP
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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 |
Немного про конфигурацию dnsmasq
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
!!! Врятли у вас получится сделать это на современных дистрибутивах, так как файлы для запуска сервиса будут установлены автоматически. 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 |
Прокси серверы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
Преодоление текториальных ограничений Защита компьютера клиента от атак с наружи Позволяет сохранить анонимность клиента путем подмены 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
1 2 3 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
#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 |
Мониторинг за прокси
1 2 3 |
apt install sockstat sockstat -h sockstat -4 |
Фазы соединения TCP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
В формате каждого пакета транспортного уровня 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 в коде :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 |
cat > common.h << "EOF" #ifndef __common_h #define __common_h #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <netdb.h> #include <sys/socket.h> /* basic socket definitions */ #include <netinet/in.h> #include <arpa/inet.h> #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 <stdarg.h> /* ANSI C header file */ #include <syslog.h> /* 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/ /\t\t/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 <sys/types.h> #include <sys/socket.h> /* for SOL_SOCKET and SO_xx values */ #include <netinet/in.h> /* for IPPROTO_TCP value */ #include <netinet/tcp.h> /* 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 = %d\n", 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 = %d\n", 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 <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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 - внешнее имя драйвера (модуля) |
ОС с микро ядерной архитектурой:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Операционные системы с микроядерной архитектурой включают: Minix — учебная операционная система, использующая микроядро. Стала основой для создания концепции микроядерных систем. QNX — коммерческая реальная ОС с микроядром, широко используемая в автомобильной и встраиваемой электронике. L4 — семейство микроядер, которое используется в исследовательских и промышленных системах, таких как системы безопасности и встраиваемые системы. Есть несколько версий L4, например, L4Ka::Pistachio, Fiasco. GNU Hurd — проект свободной операционной системы, использующий микроядро Mach. Hurd разрабатывался как часть проекта GNU для замены Unix-подобных систем. Mach — изначально разработанное микроядро, которое повлияло на разработку других микроядер. Использовалось как основа для NeXTSTEP и позднее стало частью macOS в виде гибридного ядра XNU. Integrity — реальная ОС с микроядром, используемая в авиационной и медицинской технике. Genode — платформа, основанная на микроядре, ориентированная на безопасность и высокую модульность встраиваемых систем. |
Сборка своего модуля ядра hello_printk.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
Обратите внимание что данный 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/ /\t\t/g' Makefile cat > hello_printk.c << "EOF" #include <linux/module.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("LALA <mail@gmail.com>>"); 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 |
Модули ядра, точки входа и завершения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
Любой модуль должен иметь объявление функции входа(инициализации) модуля и его завершения (не обязательно, может и отсутствовать). Функция инициализации будет вызываться после проверки и соблюдения всех условий достаточных для инициализации. Выполнение команды 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 с такими записями нельзя! такая запись ухудшает читаемость кода |
Модули ядра, вывод диагностики модуля:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
Для диагностики модуля используется функция 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 - показывает зависимости |
Параметры загрузки модуля
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
Из командной строки передаются через массив argv[] Для каждого параметра определяется переменная-параметр, далее эти переменные используются в макросе module_param(). Подобный макрос следует записать для каждого предусмотренного параметра, и он должен последовательно определить: 1) имя параметра и переменной 2) тип значения этой переменной 3) права доступа к параметру (отображаемое как путевое имя в системе псевдо каталог /sys) Значения параметрам могут быть установлены во время загрузки модуля через insmod и modprobe modprobe также может прочитать файл /etc/modprobe.conf Обработка входных параметров модуля обеспечивается макросами описанными в <linux/moduleparam.h> И там множество! Но чаще всего вы встретите module_param() и module_param_array() Пример кода: cat > mod_params.c << "EOF" #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/string.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("OLA laA <mail@gmail.com>>"); 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 = %d\n", iparam); printk("nparam = %d\n", k); printk("bparam = %d\n", bparam); printk("sparam = %s\n", 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("%s\n", 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/ /\t\t/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 |
Модуль ядра, подсчет ссылок использования:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
Одно из важных понятий модуля. При загрузки модуля начальное значение счетчика ссылок нулевое. При загрузке любого следующего модуля, который использует(импортирует) имена, экспортируемые этим модулем, счетчик ссылок такого модуля инкрементируется. !!! Модуль, счетчик ссылок использования которого ненулевой, не может быть выгружен командой 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 будет невозможно удалить. функции вызова определены в файле <linux/module.h> 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); !!! Возможность управлять значениями счетчика ссылок из собственного модуля есть, но делать это не рекомендуется. |
Структура данных сетевого стека:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
Сетевая реализация построена так, чтобы не зависеть от конкретики протоколов. Основной структурой данных является struct net_device, описывает сетевое устройство. Основной структурой обмениваемых данных между сетевыми устройствами являются "буферы сокетов" Определена в <linux/skbuff.h> 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) обычно делается несколько повторных попыток, прежде чем принять решение об ошибке канала. |
Путь пакета сквозь стек протоколов / Прием: традиционный подход
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Традиционный подход состоит в том, что каждый приходящий сетевой пакет порождает аппаратное прерывание по линии 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 для исполнения). |
Путь пакета сквозь стек протоколов / Прием: высокоскоростной интерфейс