Лабораторная работа №3 и №4 Использование интерфейса низкоуровневого доступа к сетевому программированию ("сырые" сокеты) через механизм выполнения нативного кода из Java (JNI). Цель работы: Целью работы является получение опыта работы с низкоуровневым интерфейсом сетевой карты, создания сетевых пакетов в ручную и использования наработок на Java платформе. План: 1. Создать класс решения прикладной задачи на Java с использованием нативных (native) функций 2. Генерация заголовочного файла на C, на основе реализованного класса в Java 3. Реализация функций из заголовочного файла на языке С 4. Компиляция динамической библиотеки для последующего использования в JVM 5. Проверка работы Java приложения Ход: 1. Создать класс решения прикладной задачи на Java с использованием нативных (native) функций Прикладная задача на данную лабораторную работу определяется персональным вариантом. Вариант вычисляется следующим образом: Номер протокола = |Ф| % 3 0 - ping(ICMP); 2 - udp; 1 - tcp; Протокол сетевого уровня = |И| % 2 0 - IPv4 1 - IPv6 Предлагается следующие функции в Java классе: public static boolean native init(String interfaceName); - инициализирует работу с "сырыми" сокетами. interfaceName - имя интерфейса, который необходимо задействовать для работы (например "eth0") Возвращает true, если инициализация прошла успешно, иначе false. public static native void deinit(); - завершает работу с сырыми сокетами, возвращает используюмую память системе. public static native int sendTo(byte[] buf); - отсылает через сырые сокеты буфер buf. Возвращает количество посланных байт, или -1 при ошибке. public static native byte[] recvFrom(byte[] buf, int offset); - прием первого пакета, содержимое которого совпадает с buf со смещения offset. Возвращает массив присланных байт. Пример приема с MAC адреса 11:12:13:1a:1b:1c : recvFrom(new byte[]{0x11,0x12,0x13,0x1a,0x1b,0x1c},0); Пример приема с IPv4 адреса 192.168.0.20 : recvFrom(new byte[]{(byte)0xc0,(byte)0xa8,0x00,0x14},26); 192=0xc0 168=0xa8 0 =0x00 20 =0x14 26 - смещение IP источника в Ethernet кадре с использованием IP протокола. 6+6+2 = 14 - длина заголовка Ethernet 12 - смещение IP источника в IP-пакете. 14+12 = 26. При помощи использования данных функций формируется логика работы программы, определяемая персональным вариантом. Перед использованием нативных функций необходимо загрузить динамическую библиотеку методом System.loadLibrary. IPv4-адрес сервера: 127.0.0.1 IPv6-адрес сервера: ::1 (в полном виде 0000:0000:0000:0000:0000:0000:0000:0001) MAC-адрес сервера: 00:e0:4c:68:00:07 Работу с сырыми сокетами осуществлять через сетевой интерфейс с именем "lo". Для "ping" необходимо отослать ping запрос на MAC-адрес и IP-адрес сервера, принять ответ, и вывести ответный буфер на экран. Для "udp" необходимо отправить UDP пакет на MAC-адрес и IP-адрес сервера на порт 43521 с содержимом ФИО студента. Получить ответ от сервера и вывести на экран информационную нагрузку ответного UDP пакета. Данные в Ethernet-IPv4-UDP пакете со смещения 42. Данные в Ethernet-IPv6-UDP пакете со смещения 62. Для "tcp" отправить запрос на установку соединения TCP на MAC-адрес и IP-адрес сервера на порт 43522, получить подтверждение успешной установки соединения и вывести ответный пакет. Справочная информация по строению пакетов: Общий вид Ethrnet-кадра с использованием протокола транспортного уровня: [Заголовок Ethernet][IPv4,IPv6,ICMP,ICMPv6 заголовок][TCP, UDP заголовок][Данные (опционально)] Заголовок Ethernet: см. лекции Заголовок IPv4: см. лекции Заголовок IPv6: см. лекции Заголовок ICMP (ping): ╔════════╦════════╦════════╦════════╦════════╦════════╦════════╦════════╗ ║ 0x00 ║ 0x01 ║ 0x02 ║ 0x03 ║ 0x04 ║ 0x05 ║ 0x06 ║ 0x07 ║ ╠════════╬════════╬════════╬════════╬════════╬════════╬════════╬════════╣ ║76543210║76543210║76543210║76543210║76543210║76543210║76543210║76543210║ ╠════════╬════════╬════════╬════════╬════════╬════════╬════════╬════════╣ ║ Type ║ Code ║ Checksum ║ Identifier ║ Sequence number ║ ╚════════╩════════╩════════╩════════╩════════╩════════╩════════╩════════╝ ╔════════╦════════╦════════╦════════╦════════╦════════╦════════╦════════╗ ║ 0x08 ║ 0x09 ║ 0x0a ║ 0x0b ║ 0x0c ║ 0x0d ║ 0x0e ║ 0x0f ║ ╠════════╬════════╬════════╬════════╬════════╬════════╬════════╬════════╣ ║76543210║76543210║76543210║76543210║76543210║76543210║76543210║76543210║ ╠════════╬════════╬════════╬════════╬════════╬════════╬════════╬════════╣ ║ UNIX Time ║ Milliseconds ║ ╚════════╩════════╩════════╩════════╩════════╩════════╩════════╩════════╝ ╔════════╦════════╦════════╦════════╦════════╦════════╦════════╦════════╗ ║ 0x10 ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ 0x__ ║ ╠════════╬════════╬════════╬════════╬════════╬════════╬════════╬════════╣ ║76543210║76543210║76543210║76543210║76543210║76543210║76543210║76543210║ ╠════════╬════════╬════════╬════════╬════════╬════════╬════════╬════════╣ ║ DATA ║ ╚════════╩════════╩════════╩════════╩════════╩════════╩════════╩════════╝ Type (тип сообщения): 0x08 - PINGv4 запрос; 0x80 - PINGv6 запрос; 0x00 - PINGv4 ответ. 0x81 - PINGv6 ответ. Code = 0x00 Identifier - номер сессии Sequence number - номер запроса Checksum для IPv4 определяется по заголовку ICMP-пакета и данным. Checksum для IPv6 определяется по псевдо-заголовку IPv6, заголовку ICMP-пакета и данным. Псевдо-заголовок IPv6: ╔═══════════════════════════════════════════════════════════════════════╗ ║ 0x00 ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ 0x0f ║ ╠═══════════════════════════════════════════════════════════════════════╣ ║76543210╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍76543210║ ╠═══════════════════════════════════════════════════════════════════════╣ ║ Source address ║ ╚═══════════════════════════════════════════════════════════════════════╝ ╔═══════════════════════════════════════════════════════════════════════╗ ║ 0x10 ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ 0x1f ║ ╠═══════════════════════════════════════════════════════════════════════╣ ║76543210╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍76543210║ ╠═══════════════════════════════════════════════════════════════════════╣ ║ Destination address ║ ╚═══════════════════════════════════════════════════════════════════════╝ ╔════════╦════════╦════════╦════════╦════════╦════════╦════════╦════════╗ ║ 0x20 ║ 0x21 ║ 0x22 ║ 0x23 ║ 0x24 ║ 0x25 ║ 0x26 ║ 0x27 ║ ╠════════╬════════╬════════╬════════╬════════╬════════╬════════╬════════╣ ║76543210║76543210║76543210║76543210║76543210║76543210║76543210║76543210║ ╠════════╬════════╬════════╬════════╬════════╬════════╬════════╬════════╣ ║ ICMPv6 length ║ Zeros (0x000000) ║NextHdr ║ ╚════════╩════════╩════════╩════════╩════════╩════════╩════════╩════════╝ NextHdr - Next header - поле пакета IPv6. Заголовок UDP: см. лекции Заголовок TCP: ╔════════╦════════╦════════╦════════╦════════╦════════╦════════╦════════╗ ║ 0x00 ║ 0x01 ║ 0x02 ║ 0x03 ║ 0x04 ║ 0x05 ║ 0x06 ║ 0x07 ║ ╠════════╬════════╬════════╬════════╬════════╬════════╬════════╬════════╣ ║76543210║76543210║76543210║76543210║76543210║76543210║76543210║76543210║ ╠════════╬════════╬════════╬════════╬════════╬════════╬════════╬════════╣ ║ Source port ║Destination port ║ Sequence number ║ ╚════════╩════════╩════════╩════════╩════════╩════════╩════════╩════════╝ ╔════════╦════════╦════════╦════════╦════════╦════════╦════════╦════════╗ ║ 0x08 ║ 0x09 ║ 0x0a ║ 0x0b ║ 0x0c ║ 0x0d ║ 0x0e ║ 0x0f ║ ╠════════╬════════╬════════╬════════╬════════╬════════╬════════╬════════╣ ║76543210║76543210║76543210║76543210║76543210║76543210║76543210║76543210║ ╠════════╬════════╬════════╬════════╬═══╦╦═══╬════════╬════════╬════════╣ ║ Acknowledgment number ║HL ║║ FLAGS ║ Window size ║ ╚════════╩════════╩════════╩════════╩═══╩╩═══╩════════╩════════╩════════╝ ╔════════╦════════╦════════╦════════╦════════╦════════╦════════╦════════╗ ║ 0x10 ║ 0x11 ║ 0x12 ║ 0x13 ║ 0x14 ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ ╍ 0x27 ║ ╠════════╬════════╬════════╬════════╬════════╬════════╬════════╬════════╣ ║76543210║76543210║76543210║76543210║76543210║76543210║76543210║76543210║ ╠════════╬════════╬════════╬════════╬════════╬════════╬════════╬════════╣ ║ Checksum ║Urgent pointer=0 ║ TCP Options ║ ╚════════╩════════╩════════╩════════╩════════╩════════╩════════╩════════╝ HL - длинна заголовка кол-во байт/4 FLAGS: Установка соединения - 0x002 Подтверждение установки - 0x012 TCP Options: Общий формат: [Номер опции][Длина опции в байтах][Значения опции(опционально)] Пример: Maximum segment size (0x02) = 65495 |0x02|0x04|0xff|0xd7| Опции для установки соединения: Maximum segment size (0x02) = 65495; Длина=4 SACK permitted (0x04) - без значений; Длина=2 Timestamps (0x08) = Время UNIX (4 байта), Время ответа: 0x00000000; Длина=10 No-Operation (0x01) - без длины, без значения Window Scale (0x03) - Величина смещения окна=7; Длина=3; Подсчет контрольной суммы TCP производится по псевдо-заголовку, заголовку TCP, и данным. Для IPv4 псевдо-заголовок: ╔════════╦════════╦════════╦════════╗ ║76543210║76543210║76543210║76543210║ ╠════════╩════════╩════════╩════════╣ ║ Source Address ║ ╠═══════════════════════════════════╣ ║ Destination Address ║ ╠════════╦════════╦═════════════════╣ ║ zero ║ PTCL ║ TCP Length ║ ╚════════╩════════╩═════════════════╝ PTCL - Protocol - номер протокола из IPv4 заголовка Для IPv6 псевдо-заголовок: Подсчет контрольной суммы: uint16_t checksum(uint8_t *buf, int len){ if( len % 2 == 1 ){ buf[len] = 0; len++; } uint16_t *addr = (uint16_t *) buf; int count = len; register uint32_t sum = 0; uint16_t answer = 0; // Sum up 2-byte values until none or only one byte left. while (count > 1) { sum += *(addr++); count -= 2; } // Add left-over byte, if any. if (count > 0) { sum += *(uint8_t *) addr; } // Fold 32-bit sum into 16 bits; we lose information by doing this, // increasing the chances of a collision. // sum = (lower 16 bits) + (upper 16 bits shifted right 16 bits) while (sum >> 16) { sum = (sum & 0xffff) + (sum >> 16); } // Checksum is one's compliment of sum. answer = ~sum; return (answer); } 2. Генерация заголовочного файла C, на основе реализованного класса в Java ╔════════════════════════════╗ ║ Класс с нативными методами ║ ╚════════════════════════════╝ ▼ ╭┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╮ ┊ Java компилятор (javah) ┊ ╰┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╯ ▼ ╔════════════════════════════╗ ║ Заголовочный файл ║ ║ динамической библиотеки ║ ╚════════════════════════════╝ Генерация заголовочного файла C выполняется следующей командой: javah <пакет_класса>.<имя_класса_Java> Пример: Raw.java в пакете raw (package raw) Raw.java ▶ javah raw.Raw ▶ Raw.h 3. Реализация функций из заголовочного файла на языке С Реализация выполняется в файле с рассширением *.c (пример: Raw.c). 3.1 Подключить сгенерированный заголовочный файл 3.2 Подключить заголовочные файлы используемых библиотек 3.3 Реализовать каждый метод из заголовочного файла Справочная информация по реализации JNI и "сырых" сокетов: Используемые заголовки: #include #include #include #include // close() #include // strcpy, memset(), and memcpy() #include // struct addrinfo #include // needed for socket(), uint8_t, uint16_t, uint32_t #include // needed for socket() #include // IPPROTO_ICMP, INET_ADDRSTRLEN #include // struct ip and IP_MAXPACKET (which is 65535) #include // struct icmp, ICMP_ECHO #include // inet_pton() and inet_ntop() #include // macro ioctl is defined #include // defines values for argument "request" of ioctl. #include // struct ifreq #include // ETH_P_IP = 0x0800, ETH_P_IPV6 = 0x86DD #include // struct sockaddr_ll (see man 7 packet) #include struct sockaddr_ll { unsigned short sll_family; - используемый домен (AF_PACKET) __be16 sll_protocol; int sll_ifindex; - номер интерфейса (if_nametoindex) unsigned short sll_hatype; unsigned char sll_pkttype; unsigned char sll_halen; - длина физического адреса (6) unsigned char sll_addr[8]; - физический адрес (MAC dst) }; unsigned int if_nametoindex(const char *ifname); - получение номера интерфейса по его наименованию. socket (PF_PACKET, SOCK_RAW, htons (ETH_P_ALL)); - создание "сырого" сокета sendto( sd, frame, flen, 0, (struct sockaddr *) &device, sizeof (device) ); - отправка через сокет recvfrom( sd, frame, flen, 0, (struct sockaddr *) &device, socklen_t *fromlen); - прием с сокета close(sd) - закрытие сокета Типы JNI: typedef unsigned char jboolean; typedef unsigned short jchar; typedef short jshort; typedef float jfloat; typedef double jdouble; typedef jint jsize; Функции JNI: const char* GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy); isCopy - если не NULL, возвратит, была ли скопирована область памяти. void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf); jsize GetStringUTFLength(JNIEnv *env, jstring string); jsize GetArrayLength(JNIEnv *env, jarray array); void GetArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, NativeType *buf); Пример: void GetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, char* buf); ArrayType NewArray(JNIEnv *env, jsize length); Пример: jbyteArray NewByteArray(JNIEnv *env, jsize length); void SetArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, const NativeType *buf); Пример: void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, const jbyte *buf) typedef const struct JNINativeInterface_* JNIEnv; (*e)->NewByteArray(e,256); 4. Компиляция динамической библиотеки для последующего использования в JVM Для компиляции динамической библиотеки используется компилятор GCC со следующими флагами: -fPIC - Position Independent Code -shared - динамическая библиотека -o libИмяБиблиотеки.so - выходной файл -I. - поиск заголовочных файлов в текущей дериктории. Для подключания заголовочного файла библиотеки. -I/path/to/jdk/include - поиск заголовочного файла jni.h -I/path/to/jdk/include/linux - поиск заголовочного файла jni_md.h Пример для Linux: -I/usr/lib/jvm/default-java/include -I/usr/lib/jvm/default-java/include/linux Общая команда компиляции динамической библиотеки: gcc -fPIC -shared -I. -I/path/to/jdk/include -o libИмяБиблиотеки.so *.c 5. Проверка работы Java приложения Запуск приложения осуществлять с установленным параметром java.library.path. В терминале: java -Djava.library.path=. RunJavaClass В NetBeans: Свойства проекта -> Запуск -> Параметры запуска -Djava.library.path=/path/to/your/library.so Проверку осуществлять контролем корректности заголовка и содержимого пакета в снифере wireshark. Для этого: 1. Запустить wireshark; 2. Выбрать интерфейс lo в качестве прослушиваемого; 3. Запустить Java приложение; 4. Найти пакет, отправленный Java приложением среди перехваченных в wireshark (удобно использовать фильтры: mac.addr==, ip.addr==, ipv6.addr==, ip, ipv6, tcp, udp, icmp, icmpv6) 5. Убедиться в корректности заголовка пакета, а также корректности контрольных сумм. В wireshark в нижней панели подробного парсинга пакета не должно быть элементов заголовка помеченных красным цветом, контрольные суммы сетевого и транспортного заголовков должны быть помечены как [correct]. Если wireshark не осуществляет проверку контрольной суммы, ее следует включить через контекстное меню на строчке контрольной суммы -> Protocol Preferences -> Validate the *** checksum if possible