Последние записи
- TChromium (CEF3), сохранение изображений
- Как в Delphi XE обнулить таймер?
- Изменить цвет шрифта TextBox на форме
- Ресайз PNG без потери прозрачности
- Вывод на печать графического файла
- Взаимодействие через командную строку
- Перенести программу из Delphi в Lazarus
- Определить текущую ОС
- Автоматическая смена языка (раскладки клавиатуры)
- Сравнение языков на массивах. Часть 2
Интенсив по Python: Работа с API и фреймворками 24-26 ИЮНЯ 2022. Знаете Python, но хотите расширить свои навыки?
Slurm подготовили для вас особенный продукт! Оставить заявку по ссылке - https://slurm.club/3MeqNEk
Online-курс Java с оплатой после трудоустройства. Каждый выпускник получает предложение о работе
И зарплату на 30% выше ожидаемой, подробнее на сайте академии, ссылка - ttps://clck.ru/fCrQw
20th
Май
Взаимодействие с сетевыми интерфейсами в Linux
Posted by bullvinkle under Журнал, Статьи
Linux программирование. Начинающим
Этой статьей я бы хотел открыть цикл публикаций, связанных с самым интересным и захватывающим в Linux – программировании. Я не собираюсь рассказывать о том, в чем писать программы, как их компилировать и запускать, информацию по данным вопросам найти очень легко и я думаю, что любой с этим справится. Языки программирования, которые будут использоваться в статьях: C, C++, bash script.
Олег Кутков http://www.programmersforum.ru/member.php?u=2583
Oleg Kutkov by elenbert@gmail.com
В последнее время программисты и простые пользователи начинают проявлять все больший интерес к Unix- подобным операционным системам, в частности к Linux. К сожалению, новичкам, не всегда просто разобраться в новой среде, даже не смотря на то, что Linux считается одной из самых хорошо документированных ОС. Информация, как правило, разбросана по форумам, множеству отдельных статей и блогов. Основное содержимое данных материалов касается администрирования и настройки дистрибутивов, программистам же, как правило, приходится довольствоваться man-документацией ил
и автоматической doxygen-документацией (документация, сгенерированная автоматически, на основе комментариев в исходном коде). К тому же, как это часто бывает – наиболее интересный материал на английском языке. Безусловно, данную ситуацию следует исправлять.
Начало. Общие сведения
Программировать в Linux очень просто и легко. Для программистов созданы практически идеальные условия: множество мощных инструментов, открытые исходные коды, сама организация системы, множество фреймворков. Работа с файлами, строками, массивами, классами, контейнерами,
в Unix- среде, практически ничем не отличается от таковой в Windows, это касается и множества других стандартных функций и библиотек. Различия начинаются на более низком уровне. Предлагаю разобраться, как работать с сетевыми интерфейсами.
Как известно, сетевые интерфейсы Linux обозначаются короткими строковыми именами – eth0, wlan0, lo и т.д. Интерфейсу можно присвоить любое удобное имя, но лучше руководствоваться общепринятыми правилами именования. Думаю, что ни для кого не секрет, что все устройства в Linux представлены в виде особых файлов в каталоге /dev, это справедливо для всех устройств, кроме сетевых адаптеров. Но так было не всегда, в прошлых версиях Linux ядра были доступны устройства /dev/eth0, /dev/tap0 и т.д., в более же новых ядрах эти устройства упразднили, и сетевые интерфейсы были перенесены в п
амять в так называемое пространство сокетов
Примечание.
* Следует сказать, что в другой популярной Unix – подобной ОС – FreeBSD по-прежнему, сохранен старый способ. Поэтому, если вы захотите переносить приложения с Linux на FreeBSD – следует учитывать это и другие мелкие различия. Но это не значит, что работа с этими устройствами каким-либо образом осложнилась, все очень и очень просто.
Далее я бы хотел рассмотреть особую и очень важную функцию, обеспечивающую обмен управляющими сообщениями между устройством и пользовательским приложениями.
Интерфейсы управления
Для взаимодействия с устройствами, а на самом деле с драйверами устройств, в Unix имеется особый вызов – ioctl, означающий Input-Output Control.
Справедливости ради, следует сказать, что Windows имеется подобный интерфейс – DeviceIoControl. Для использования данного вызова следует включить заголовочный файл <sys/ioctl.h>. Существует также возможность определять свои собственные ioctl вызовы, этим пользуются разработчики драйверов. Рассмотрим вызов ioctl детально…
int ioctl(int d, int request, …);
d – это открытый файловый дескриптор устройства.
request – это тип запроса, для различных устройств запросы различные, соответствующую информацию обычно легко найти в справочных материалов.
Примечание.
В этой статье я рассмотрю все типы запросов для сетевых устройств.
третий аргумент – это указатель на void, т.е. там могут оказаться какие угодно данные, в зависимости от того, что требует конкретный тип запроса. В случае успеха вызовом возвращается ноль. В случае ошибки возвращается «-1» и значение глобальной переменной errno устанавливается соответствующим образом. Чтобы было понятнее, пример использования:
#include <termios.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
int main()
{
int fd, serial, res; // дескриптор, параметр, результат
fd = open("/dev/ttyS0", O_RDONLY); // открываем устройство
if (fd < 0) { // проверяем дескриптор и в случае ошибки - выводим ее пользователю
printf("Открытие /dev/ttyS0 завершилось с ошибкой: %s\n", strerror(errno));
return 1;
}
res = ioctl(fd, TIOCMGET, &serial); // выполняем вызов ioctl с запросом TIOCMGET
if (res < 0) { // проверяем результат и в случае ошибки выводим
printf("Вызов ioctl завершился с ошибкой: %s\n", strerror(errno));
return 1;
}
if (serial & TIOCM_DTR) // проверяем результат
printf("Последовательный порт не готов\n");
else
printf("Последовательный порт готов\n");
close(fd); // закрываем дескриптор
return 0;
}
Сначала мы подключаем необходимые заголовочные файлы, в которых объявлены используемые нами функции, а так же ioctl запросы. В данном примере идет работа с последовательным портом компьютера, а точнее проверяется готовность приема данных. Вызов ioctl на открытом файловом дескрипторе, передает драйверу открытого устройства команду TIOCMGET, сохраняет и возвращает результат в переменную
Serial.
Аналогично происходит процесс передачи информации для драйвера из переменной параметра. Как уже говорил, тип переменной параметра зависит от типа запроса. Как правило, это специальная структура. Таков общий принцип работы ioctl, как видим – ничего сложного. Теперь перейдем непосредственно к теме обсуждения – сетевым интерфейсам…
Работа с сетевыми интерфейсами
Выше был рассмотрен пример использования вызова ioctl для получения данных из открытого дескриптора последовательного порта. Для сетевых интерфейсов работа выполняется аналогичная. Внимательный читатель мог обратить внимание, как выше я говорил о том, что для сетевых устройств не существует таких специальных файлов, соответственно eth0 нельзя открыть также, как и последовательный порт. Это так сетевые устройства перенесены в пространство сокетов и для доступа к этим устройствам следует использовать именно дескрипторы сокетов. Открытие сокета для упр
авления сетевым устройством выполняется так:
int sock = socket(AF_INET, SOCK_DGRAM, 0);
Результат sock и есть дескриптор сокета, перед использованием, его, как и все прочие дескрипторы, следует проверять на отрицательные значения, на случай возможных ошибок. Также, для ioctl вызовов на дескрипторе сокета применяется особая структура параметра (serail, в примере выше) – struct ifreq. Это очень важная структура, используемая во всех случаях работы с сетевыми устройствами. Разберем ее подробнее:
struct ifreq {
char ifr_name[IFNAMSIZ];
union {
struct sockaddr ifr_addr;
struct sockaddr ifr_dstaddr;
struct sockaddr ifr_broadaddr;
struct sockaddr ifr_netmask;
struct sockaddr ifr_hwaddr;
short ifr_flags;
int ifr_ifindex;
int ifr_metric;
int ifr_mtu;
struct ifmap ifr_map;
char ifr_slave[IFNAMSIZ];
char ifr_newname[IFNAMSIZ];
char * ifr_data;
};
};
Структура состоит из двух полей: имени интерфейса и объединения, каждое возможное поле, которого выражает конкретный параметр сетевого интерфейса. Данная структура позволяет, как получать параметр интерфейса, так и задавать его. Так как используется объединение – можно получать и задавать только
один параметр за раз.
Интересуемые и используемые поля:
ifr_addr – IP адрес интерфейса
ifr_dstaddr – адрес сервера (для Point-to-Point соединения)
ifr_broadaddr – широковещательный адрес интерфейса
ifr_netmask – маска подсети
ifr_hwaddr – mac адрес
ifr_ifindex – индекс интерфейса (внутри ядра сетевые интерфейсы имеют уникальные индексы, для упращения работы сетевой подсистемы)
ifr_flags – различные флаги (интерфейс поднят или опущен, интерфейс активен или неактивен и др.)
ifr_metric – метрика интерфейса
ifr_mtu – mtu интерфейса
ifr_map – структура, содержащая в себе техническую информацию (номер прерывания, память устройства и т.д.)
ifr_slave – подчиненное устройство
ifr_newname – новое имя интерфейса (для переименования)
Перед любым использованием структуры следует ее обязательно обнулять с помощью memset, а затем задавать имя интересуемого интерфейса ifr_name. Теперь перейдем от теории к действию – получим IP адрес интерфейса eth0! Соответствующий пример приведен ниже:
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <string.h>
#include <net/if.h>
#include <errno.h>
#include <stdio.h>
int main()
{
int sock; // дескриптор сокета
struct sockaddr_in *in_addr; // структура интернет-адреса (поля)
struct ifreq ifdata; // структура - параметр
char *ifname = "eth0"; // имя интерфейса
sock = socket(AF_INET, SOCK_DGRAM, 0); // открываем дескриптор сокета
if (sock < 0) {
printf("Не удалось открыть сокет, ошибка: %s\n", strerror(errno));
return 1;
}
memset(&ifdata, 0, sizeof(ifdata)); // очищаем структуру
strncpy(ifdata.ifr_name, ifname, sizeof(ifname)); // задаем имя интерфейса
//получаем айпи адрес с помощью SIOCGIFADDR, одновременно проверяя результат
if (ioctl(sock, SIOCGIFADDR, &ifdata) < 0) {
printf("Не получить IP адрес для %s, ошибка: %s\n", ifname, strerror(errno));
close(sock);
return 1;
}
in_addr = (struct sockaddr_in *) &ifdata.ifr_addr; // преобразовываем из массива байт
// в структуру sockaddr_in
printf("Интерфейс %s IP адрес: %s\n", ifname, inet_ntoa(in_addr->sin_addr));
close(sock);
return 0;
}
В этом коде я ввел одну новую структуру и одну новую функцию. Начну со структуры:
struct sockaddr_in {
short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
Даная структура предназначена для хранения базовых данных об адресе сетевого узла. Назначение ее полей:
sin_family - семейство адресов, может иметь два значения: AF_INET для IPv4 и AF_INET6 для IPv6
sin_port - порт узла
sin_addr - структура адреса (о ней ниже)
sin_zero - этот массив можно использовать по своему усмотрению
struct in_addr {
unsigned long s_addr; // load with inet_pton()
};
Эта структура состоит всего из одного поля – числа, представляющего собой собственно IP адрес. Например, «192.168.8.98», в таком формате, имеет вид 1644734656. Функция inet_ntoa предназначена для преобразования такого числового значения в привычный цифро-точечный формат. В качестве аргумента, функция принимает struct in_addr, а возвращает указатель на строку.
Теперь скажу о преобразовании из массива байт в структуру sockaddr_in. Как было показано выше, поле ifr_addr, в структуре ifreq, имеет тип struct sockaddr. Эта структура является своего рода, «упрощенной» структурой sockaddr_in.
struct sockaddr {
unsigned short sa_family;
char sa_data[14];
};
У нее всего два поля: семейство адресов и массив 14 байт, содержащий собственно адрес. Структуры sockaddr и sockaddr_in хорошо и естественно приводятся к типу друг друга, чем мы и воспользовались. Обратная операция – задание адреса выполняется примерно так же. Отличия только два: перед вызовом ioctl, полю ifr_addr нужно задать новое значение адреса, а тип запроса будет SIOCSIFADDR. Как видно, оба запроса SIOCSIFADDR и SIOCGIFADDR отличаются на одну букву, которая означает Set и Get, соответственно.
Я не буду приводить пример, показывающий, как задавать новое значение адреса, так как предоставленных сведений уже достаточного для того, что бы читатель разобрался сам. Дам лишь небольшую подсказку: для преобразования строкового значения адреса, например «192.168.8.98», в тип struct in_addr следует применять функцию inet_aton:
inet_aton(const char *saddr, struct in_addr *iaddr);
saddr – указатель на строку с адресом
iaddr – указатель на struct in_addr
Для получения (или задания) всех остальных параметров используется аналогичный способ, отличие лишь в типе запроса и использовании соответствующего поля в объединении структуры ifreq. Список всех возможных типов запроса для получения или задания параметров сетевого интерфейса:
SIOCGIFNAME – получить имя сетевого интерфейса
SIOCGIFINDEX – получить индекс сетевого интерфейса
SIOCGIFFLAGS, SIOCSIFFLAGS – получить/задать флаг интерфейса (о флагах ниже)
SIOCGIFMETRIC, SIOCSIFMETRIC – получить/задать метрику интерфейса
SIOCGIFMTU, SIOCSIFMTU – получить/задать mtu интерфейса
SIOCGIFHWADDR, SIOCSIFHWADDR – получить/задать mac адрес
SIOCGIFMAP, SIOCSIFMAP – получить/задать аппаратные параметры (struct ifmap)
Наиболее интересные флаги интерфейса:
IFF_UP – интерфейс запущен
IFF_BROADCAST – интерфейс является широковещательным
IFF_LOOPBACK – интерфейс является петлевым
IFF_POINTOPOINT – point-to-point интерфейс
IFF_RUNNING – интерфейс активен
IFF_MULTICAST – интерфейс поддерживает многоадресность
Небольшой пример использования флагов, получение информации о том, запущен ли интерфейс:
ioctl(sock, SIOCGIFFLAGS, &ifdata);
if (ifdata.ifr_flags && IFF_UP) {
printf("Сетевой интерфейс %s запущен\n", ifdata.ifr_name);
}
else {
printf("Сетевой интерфейс %s не запущен\n", ifdata.ifr_name);
}
На данном этапе предлагаю читателю самостоятельно написать небольшое приложение, получающее, в качестве аргумента командной строки, имя интерфейса и выводящее всю информацию о нем. Но как быть, когда заранее неизвестны имена интерфейсов, как получить просто список доступных сетевых интерфейсов? Очень просто. В помощь приходит замечательный вызов if_nameindex().
Небольшой пример, показывающий как получить список всех сетевых интерфейсов и их индексов.
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <string.h>
#include <net/if.h>
#include <errno.h>
#include <stdio.h>
int main()
{
int sock; // дескриптор сокета
struct sockaddr_in *in_addr; // структура интернет адреса (поля)
struct ifreq ifdata; // структура - параметр
struct if_nameindex* ifNameIndex; // структура интерфейсов и их индексов
sock = socket(AF_INET, SOCK_DGRAM, 0); // открываем дескриптор сокета
if (sock < 0) {
printf("Не удалось открыть сокет, ошибка: %s\n", strerror(errno));
return 1;
}
ifNameIndex = if_nameindex();
if (ifNameIndex) { // если удалось получить данные
while (ifNameIndex->if_index) { // пока имеются данные
memset(&ifdata, 0, sizeof(ifdata)); // очищаем структуру
strncpy(ifdata.ifr_name, ifNameIndex->if_name, IFNAMSIZ); // получаем имя следующего интерфейса
// получаем IP адрес с помощью SIOCGIFADDR, одновременно проверяя результат
if (ioctl(sock, SIOCGIFADDR, &ifdata) < 0) {
printf("Не получить IP адрес для %s, ошибка: %s\n", ifdata.ifr_name, strerror(errno));
close(sock);
return 1;
}
// преобразовываем из массива байт в структуру sockaddr_in
in_addr = (struct sockaddr_in *) &ifdata.ifr_addr;
printf("Интерфейс %s индекес %i IP адрес: %s\n", ifdata.ifr_name, ifNameIndex->if_index, inet_ntoa(in_addr->sin_addr));
++ifNameIndex; // переходим к следующему интерфейсу
}
}
close(sock);
return 0;
}
Данный пример является доработанной версией предыдущего примера, выводя все доступные интерфейсы, их индексы и IP адреса. На рисунке ниже изображен процесс компиляции и запуска приложения с выводом всей информации. Видно, так же, сообщение об ошибке, для виртуального интерфейса, с которого не удалось получить информацию:
Заключение
В данной статье я постарался раскрыть основные аспекты взаимодействия с сетевыми интерфейсами в OC Linux, многое из этого будет справедливо и для других Unix подобных операционных систем. Надеюсь, что статья получилась интересной и полезной.
Напоследок не могу не сказать, что в Linux, работа с сетью через ioctl является устаревшим способом, на смену которого приходит Netlink. Netlink – это модуль, состоящий из двух частей, одна находится в ядре и взаимодействует непосредственно с соответствующими подсистемами, вторая – библиотека на уровне пользователя. Обе части могут обмениваться информацией между собой. Это очень мощный и удобный способ управлениями всеми параметрами сетевой подсистемы Linux, такие издания, как «Linux journal» рекомендуют пользоваться только Netlink. К сожалению, документации по данному API н
е так много, как хотелось, и приходится собирать по крупицам. Но в будущих статьях я постараюсь рассмотреть некоторые аспекты использования Netlink, так как уже имею опыт данной сфере. До встречи на страницах журнала!
Литература
. Исходные коды пакета утилит net-tools
. Doxygen документация
. Christian Benvenuti. Understanding Linux network internals. – O’reilly Media, 2005
http://www.linbai.info/computers-it/understanding-linux-network-internals.html
. Интернет ресурсы: www.linuxjournal.com, www.stackoverflow.com, различные форумы
Это статья с третьего выпуска журнала “ПРОграммист”.
Скачать этот выпуск можно по ссылке.
Ознакомиться со всеми номерами журнала.
Обсудить на форуме — Взаимодействие с сетевыми интерфейсами в Linux
Похожие статьи
Купить рекламу на сайте за 1000 руб
пишите сюда - alarforum@yandex.ru
Да и по любым другим вопросам пишите на почту
пеллетные котлы
Пеллетный котел Emtas
Наши форумы по программированию:
- Форум Web программирование (веб)
- Delphi форумы
- Форумы C (Си)
- Форум .NET Frameworks (точка нет фреймворки)
- Форум Java (джава)
- Форум низкоуровневое программирование
- Форум VBA (вба)
- Форум OpenGL
- Форум DirectX
- Форум CAD проектирование
- Форум по операционным системам
- Форум Software (Софт)
- Форум Hardware (Компьютерное железо)