Отличия сетевых вызовов Windows и Linux
Во многом совместимые на уровне исходных кодов модели сокетов от Berkeley и Microsoft, на практике оказываются не такими уж кросплатформенными.
Рассмотрим некоторые хитрые различия в их реализации, которые обнаружились при написании кросплатформенного RPC для перенаправления сетевых вызовов некоторого процесса в одной ОС на другую ОС.
Дескриптор сокета на BSD ничем не отличается от дескриптора файла, а значит, некоторые системные вызовы принимают одновременно описатели и сокетов и файлов (например, такие-вот «редко» используемые вызовы, как close(), fcntl() и ioctl()).
Еще есть побочный эффект, проявляющий себя в довольно подлых ситуациях, заключающийся в том, что в системах с поддержкой модели Berkeley числовое значение описателя сокета — обычно маленькое число (меньше 100), и подряд создающиеся дескрипторы различаются на 1. В модели Microsoft такой описатель сразу больше 200 (примерно), а подряд создающиеся описатели различаются на sizeof(SOCKET).
- BSD: Вызовы возвращают -1, устанавливается глобальная перменная errno.
- Win: Вызовы возвращают -1 (макрос SOCKET_ERROR), статус получаем с помощью WSAGetLastError().
Создание сокетов:
socket(int af, int type, int protocol);
Константы для первого аргумента имеют абсолютно разные значения на BSD и Windows. Для второго пока совпадают.
getsockopt(int sockfd, int level, int option_name, void *option_value, socklen_t *option_len);
setsockopt(int sockfd, int level, int option_name, void const *option_value, socklen_t option_len)
Работа с DNS
struct addrinfo
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
socklen_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
>;
recv(int sockfd, void *buffer, size_t length, int flags);
recvfrom(int sockfd, void *buffer, size_t length, int flags, struct sockaddr *from, socklen_t *fromlen);
send(int sockfd, void const *buffer, size_t length, int flags);
sendto(int sockfd, void const *buffer, size_t length, int flags, struct sockaddr const *to, socklen_t tolen);
- BSD: poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd
int fd;
short events;
short revents;
>;
- BSD: select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
typedef struct
long fds_bits[FD_SETSIZE / 8 * sizeof(long)];
> fd_set;
Для BSD помещение некоторого сокета в некоторое множество заключается во взведении в последнем такого бита, порядковый номер которого численно равен дескриптору сокета. FD_SETSIZE обычно равно 1024. Первый аргумент select() представляет собой максимальное числовое значение дескриптора сокета, входящего в любое из трех множеств плюс один. Учитывая, что установка бита в массиве fds_bits производится исключительно без проверки диапазона, становится ясно, что при значении дескриптора сокета >= FD_SETSIZE поведение программы неопределено. Такая, несколько ненадежная реализация select — пережиток скупых на память компьютеров. Кстати, вот в каком случае важно непрямое преобразование int -> SOCKET и обратно.
Для Windows помещение некоторого сокета в некоторое множество заключается во вставке его в массив fd_array по индексу fd_count и дальнейшем увеличении последнего. FD_SETSIZE обычно равно 64. При этом, первый аргумент select() игнорируется вовсе.
Глава 7. Сетевая среда
В этой главе мы намерены изучить как подсистема Windows для Linux настраивает сетевую среду внутри самой подсистемы и как изменения и обновления в построении сетевой среды распространяются со стороны Windows в WSL. Кроме того, мы рассмотрим что представляют собой сокеты Linux и как WSL реализует их для предоставления хорошей практики Linux поверх операционной системы Windows в то же время сопровождая возможность взаимодействия.
В наши дни все компьютеры подключены к сетевым средам и устройствам и нам необходим постоянный доступ к Интернету и прочим системам через сетевой стек для обмена данными, перемещения файлов и тому подобного, что добавляет дополнительно важность сетевых сред как одного из критически важных компонентов стоящих позади успеха любого программного приложения.
Обзор сетевой среды WSL
Давайте начнём с небольшого обзора того как устанавливается сетевая среда в Linux и в его реализации в WSL, а также позвольте нам немного порассуждать о выборах архитектуры выполняемых для создания бесшовного уровня наведения моста в промежутке между построением сетей в Windows и Linux.
Сетевые интерфейсы и DNS
Linux применяет общие системные вызовы, которые можно использовать для управления (считыванием и записью) любого устройства, например, сетевых интерфейсов, и такие вызовы также носят название IOCTL (Input/Output Control, управления вводом/ выводом). IOCTL делают возможным просмотр перечня всех подключённых к Linux сетевых устройств через осуществление таких системных вызовов для считывания имеющихся сетевых интерфейсов и сохранения этих сведений в ядре. Однако WSL1 не обладает такой возможностью, ибо нет доступного ядра и мы фактически эмулируем Linux поверх Windows, применяя трансляцию системных вызовов на уровне совместимости.
Чтобы заполнить этот пробел, как только экземпляр WSL запускается в Windows, служба диспетчера LXSS запрашивает перечень имеющихся в операционной системе Windows сетевых устройств и передаёт этот список в драйвер WSL (lxcore.sys), и когда из дистрибутива Linux выполняется некий системный вызов (IOCTL), тогда вышеупомянутая кэшировнная информация предоставляет имеющийся список сетевых устройств в WSL.
Тот же самый перечень получает автоматическое заполнение в файле /etc/resolv.conf , который выступает файлом разрешения конфигурации в Linux и который содержит свой перечень системных серверов доменных имён, настроенных в Windows. Существует множество параметров настройки, которые можно применять в этом файле, однако по умолчанию он создаётся в общей конфигурации:
Значением IP адреса может быть либо некий IPv4 адрес в нотации с точками, либо адрес IPv6 в нотации точек/ двоеточий, как это показано на Рисунке 7-1.
Автоматически заполняемый файл разрешений в WSL
Некоторые из запрошенных таких сетевых сведений также заполняют файл /etc/hosts , который также носит название файла хостов и содержит статическую таблицу нахождения имён хостов и соответствующих им IP адресов, как это показано на Рисунке 7-2. Сочетание /etc/resolv.conf и /etc/hosts делает возможным поддержку необходимых DNS в подсистеме Windows для Linux.
Автоматически заполняемый файл хостов в WSL
Однако построение сетевой среды очень динамичная вещь и ситуации могут меняться стремительно, например, пользователь может очень быстро переключаться с кабельного Ethernet на беспроводную сеть. В WSL должен существовать некий механизм поддержки обновлений таких изменений из Windows в WSL с тем, чтобы служба диспетчера LXSS снова вписывалась в общую картину регистрируя саму себя для всех изменений, связанных с обновлениями в сетевых интерфейсах на стороне Windows. Это означает, что служба диспетчера LXSS ожидает любых уведомлений об изменениях и, когда происходят изменения в сетевой среде, он снова автоматически заполняются в WSL с применением вышеупомянутых подходов, как это демонстрируется на Рисунке 7-3. Это удерживает файлы /etc/resolv.conf и /etc/hosts в актуальном состоянии и синхронизирует их с настройками Windows.
WSL перестраивается изменениями в среде своего Windows
Рисунок 7-3 демонстрирует, что на шаге 3, когда сетевой интерфейс отключён на стороне Windows, соответствующий Eternet немедленно исчезает из подсистемы Windows для Linux (что проверяется отсутствием MAC адреса на шаге 4), а запросы ICMP начинают отказывать на шаге 6.
Сокеты
Сокет является неким абстрактным представлением конечной точки пути сетевого взаимодействия. Сокеты также способны служить некоторыми конечными точками для локальных, не подключаемых к сетевой среде межпроцессных взаимодействий. В наших последующих подразделах мы выполним краткий обзор сокетов Беркли в Linux и аналогичную реализацию в операционной системе Windows с названием Winsock Kernel (WSK), которая делает возможной для WSL трансляцию API вызовов сокета Беркли в API вызовы Winsock Kernel и наоборот для того, чтобы сделать возможным сетевой обмен между Windows и Linux.
Сокеты Беркли
В Linux сокеты Беркли (также именуемые сокетами BSD) это некий интерфей API, который делает возможным межпроцессное взаимодействие (IPC, inter-process communication). Для установления соединения любые две терминальные точки открывают некий сокет на каждом из своих концов, которые далее привязываются к заданному адресу с тем, чтобы между ними можно было бы отправлять и получать данные.
Вот некоторые из распространённых функций API сокета Беркли.
Применяется для создания некого нового сокета определённого типа и для открытия сокета существуют три требования, причём они далее могут применяться для категоризации получаемых сокетов:
- Семейство адреса (AF, Address family) или домен — Сокет может быть одним из следующих доменов или семейств адреса:
- AF_INET это реализация Linux IPv4.
- AF_UNIX , также именуемый AF_LOCAL , применяется для межпроцессного взаимодействия внутри общей системы.
- Сокеты AF_NETLINK используются для взаимодействия между режимом пользователя и ядром, поскольку они составляются из стандартного интерфейса на основе сокета для процессов пространства пользователя и некого внутреннего API ядра для модулей ядра.
socket(AddressFamily, Type, Protocol);
socket(AF_INET, SOCK_STREAM, 0);
Эта функция связывает некий сокет с адресом сокета, то есть, комбинацией IP адреса и номера порта.
Данная функция помечает определяемый сокет в качестве пассивного, который лишь принимает входящие подключения.
Эта функция применяется для установления соединения между указанным сокетом и передаваемым IP адресом.
send( ), recv( ), sendto( ) и recvfrom( )
Как и предполагают их наименования, эти функции используются для отправки и получения данных через сокеты.
Данная функция применяется для высвобождения системных ресурсов прекращением некого установленного через сокеты соединения.
Winsock и WSK (Winsock Kernel)
Операционная система Windows обладает реализацией режима пользователя вышеупомянутых сокетов BSD, носящих название “Winsock”, причём их реализация очень похожа, но не идентична, и она не может применяться в подсистеме Windows для Linux, потому что реализация сокета WSL выполняется в режиме ядра, то есть внутри библиотеки драйвера подсистемы Linux ( WslSocket.lib ).
Для решения этой проблемы в WSL был применён другой API Windows нижнего уровня, носящий название Winsock Kernel (WSK). WSK это интерфейс сетевого программирования режима ядра и, применяя его, любое программное обеспечение ядра способно осуществлять сетевые операции ввода/ вывода в точности как в режиме пользователя “Winsock”. В основном, драйверы подсистемы Windows для Linux транслируют вызовы из API сокета BSD в API WSK и реализуют нечто ещё, что пропущено для поддержки почти такой же сетевой практики, которая имеется в естественном дистрибутиве Linux.
Как демонстрируется на Рисунке 7-4, когда некое приложение создаёт сокет BSD, запущенный в дистрибутиве Linux поверх WSL, тогда системные вызовы сокета BSD выполняются в соответствующий драйвер режима ядра lxcore.sys WSL, который обрабатывает необходимую трансляцию. lxcore.sys транслирует такие системные вызовы сокета BSD в вызовы, которые понятны для WSK (Winsock Kernel), что является API нижнего уровня в соответствующем ядре NT для обработки всех относящихся к сокету запросов в операционной системе Windows и соединяет соответствующий сокет с лежащим в основе стеком TCP/IP.
Схема сетевой среды WSL