Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Сети > Порядок байт и битовые операции.


Автор: konshyn 6.8.2014, 16:29
Ребят, нет возможности проверить (ни одной машины нет с нужным порядком байт)
У меня на машине little-endian порядок байт, сетевой порядок байт - big-endian.
Так вот, хочу работать с заголовками с помощью битовых операций. Пример:
Код

// принял пакет на raw сокет ( в buffer), т.е. заголовок ip и, к примеру, udp и полезную нагрузку.
// мне нужно получить, к примеру, порт отправителя и получателя в udp заголовке
// что я делаю:
port_src = (buffer[(buffer[0] & 0x0F) * 4] << 8) | buffer[(buffer[0] & 0x0F) * 4 + 1];

и я получаю именно тот порт, с которого была отправлена датаграмма. я так понимаю, с порядком big-endian на машине я получу такой же результат.
но вот загвоздка, если у меня little-endian, то мне нужно опять использовать htons(), чтобы записать этот порт, к примеру, в sockaddr_in, а если big-endian - не нужно.
Как сделать это элегентно, и с возможностью работать через битовые операции (намного удобнее, чем через всякие структуры и просто копирование. по крайне мере мне так нравится smile ) ?

Автор: volatile 6.8.2014, 17:57
Цитата(konshyn @  6.8.2014,  16:29 Найти цитируемый пост)
нужно опять использовать htons(), чтобы записать этот порт, к примеру, в sockaddr_in, а если big-endian - не нужно.

Неверно. htons() следует использвать в любом случае.
На машине, где это нужно она перевернет порядок байт, а на машине,где не нужно - ничего делать не будет.
htons(), ntohs() и подобые, специально придуманы чтоби писать платформонезависимый код.
А возится с порядком байт вручную, не стоит.


Автор: konshyn 6.8.2014, 18:19
Цитата(volatile @  6.8.2014,  17:57 Найти цитируемый пост)
Неверно

Почему неверено? Верно, дальше по тексту вы сами объясняете, что будет smile
Цитата(volatile @  6.8.2014,  17:57 Найти цитируемый пост)
htons(), ntohs() и подобые, специально придуманы чтоби писать платформонезависимый код.

Верно, но, к примеру, когда я принимаю через recvfrom() датаграмму, т.е. я туда еще отправляю структуру sockaddr_in, чтобы мне ядро скинуло ip-адрес отправителя, то этот ip-адрес записан в big-endian порядке. Жаль, что порт туда не записывается. хотя, структура sockaddr_in специально для AF_INET сделана, другими словами для TCP и UDP. они имеют поля портов на одном и том же месте в своем заголовке - в начале. логично было бы делать это.
так вот, я бы хотел делать как писал выше, но для этого придется писать htons(), но, если я буду делать как делают все, то не нужно, т.к. они уже записаны в big-endian порядке.
Думал-думал, как элегантно сделать, но не придумал...придется как всегда:(
Код

struct udp_header
{
    u_int16 src_port;
    u_int16 dst_port;
    u_int16 length;
    u_int16 checksum;
};
...
struct sockaddr_in src_addr, dst_addr;
...
recvfrom(..., buffer, ..., (struct sockaddr *)&src_addr, (void*)&length);
struct udp_header *udp;
udp = (struct udp_header *)(buffer + (buffer[0]&0x0F) * 4);
...
dst_addr.sin_port = udp->src_port;
...
 


Автор: feodorv 6.8.2014, 19:55
Цитата(konshyn @  6.8.2014,  19:19 Найти цитируемый пост)
Жаль, что порт туда не записывается.

Что Вы имеете в виду?
Цитата

struct sockaddr_in {
        short   sin_family;
        u_short sin_port;
        struct  in_addr sin_addr;
        char    sin_zero[8];
};

Всё записывается.

Добавлено через 2 минуты и 58 секунд
Код
... func( struct sockaddr_in *sa, ...)
{
  unsigned int ip = ntohl( sa->sin_addr.s_addr );
  unsigned int port = ntohs( sa->sin_port );
...
}

Автор: konshyn 6.8.2014, 22:19
Цитата(feodorv @  6.8.2014,  19:55 Найти цитируемый пост)
Все записывается

Я имел в виду из функции recvfrom()

Автор: feodorv 6.8.2014, 23:22
Цитата(konshyn @  6.8.2014,  23:19 Найти цитируемый пост)
Я имел в виду из функции recvfrom() 

И я имел в виду из функции recvfrom(). Приведённый код как раз выполняется после recvfrom [UDP].

Автор: konshyn 7.8.2014, 10:35
Цитата(feodorv @  6.8.2014,  23:22 Найти цитируемый пост)
И я имел в виду из функции recvfrom(). Приведённый код как раз выполняется после recvfrom [UDP]. 

У меня почему-то на сырой сокет порт не записывается

Код

//recvfrom() на сыром сокете
printf("IP = %s\n", inet_ntop(AF_INET, (void*)&servaddr.sin_addr, ipaddr, sizeof(ipaddr)));
printf("Port = %d\n", ntohs(servaddr.sin_port));
...


Цитата

IP = 127.0.0.1
Port = 0


Добавлено через 1 минуту и 16 секунд
отправляю пакет вот так:
Цитата

echo "hello" > /dev/udp/localhost/58549

Автор: tzirechnoy 7.8.2014, 13:15
http://commandcenter.blogspot.ru/2012_04_01_archive.html

Добавлено через 3 минуты и 15 секунд
Ровно обратным порядком: buffer[0] = post&0x0F; buffer[1] = (port >> 8)&0x0F;

Впрочем, htons можно просто вставлять всегда.

Автор: konshyn 7.8.2014, 14:06
Цитата(tzirechnoy @  7.8.2014,  13:15 Найти цитируемый пост)
Впрочем, htons можно просто вставлять всегда. 

можно, но:
Цитата(tzirechnoy @  7.8.2014,  13:15 Найти цитируемый пост)
buffer[0] = post&0x0F; buffer[1] = (port >> 8)&0x0F;

этот вариант намного красивей)

Автор: feodorv 7.8.2014, 14:54
Цитата(konshyn @  7.8.2014,  11:35 Найти цитируемый пост)
отправляю пакет вот так:

Гм. Сделайте честную отправку пакета через sendto. Что там с /dev/udp, я не знаю.

Автор: konshyn 7.8.2014, 15:11
Вот код:
Код

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    int udp_socket = socket(PF_INET, SOCK_RAW, IPPROTO_UDP);
    if (udp_socket < 0)
    {
        perror("socket() failed");
        return -1;
    }

    struct sockaddr_in servaddr;
    memset((void*)&servaddr, 0, sizeof(struct sockaddr_in));
    servaddr.sin_family = AF_INET;                  
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);   
    servaddr.sin_port = 0;       
   
    if ((bind(udp_socket, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in))) < 0)
    {
        perror("bind() failed");
        return -1; 
    }
    
    unsigned char buffer[2000] = {0};
    int length = 0;
    int i = 0;
    int counter = 0;
    struct timeval tv_ioctl = {0};

    int length_sock;
    char ipaddr[256] = {0};

    while (1)
    {
        memset((void *)&servaddr, 0, sizeof(struct sockaddr_in));
        memset((void *)&ipaddr, 0, sizeof(ipaddr));
        length = recvfrom(udp_socket, buffer, 2000, 0, (struct sockaddr *)&servaddr, (void*)&length_sock);

        printf("ip = %s\n", inet_ntop(AF_INET, (void*)&servaddr.sin_addr, ipaddr, sizeof(ipaddr))); 
        printf("Port = %d\n", servaddr.sin_port);

        unsigned short int port = (buffer[22] << 8) | (buffer[23]);

        printf("Port = %u\n", port);
        printf("Port = %u\n", (buffer[(buffer[0] & 0x0F) * 4 + 2] << 8) | buffer[(buffer[0] & 0x0F) * 4 + 3]);
    }
    return 0;
}

вот результат одно из пакетов:
Цитата

ip = 192.168.1.76
Port = 0;
Port = 55955
Port = 55955


Добавлено через 6 минут и 52 секунды
Но если делать не сырой сокет, а обычный датаграммный (верхнуюю часть заменить на это)
Код

    int udp_socket = socket(PF_INET, SOCK_DGRAM, 0);
    if (udp_socket < 0)
    {
        perror("socket() failed");
        return -1;
    }

    struct sockaddr_in servaddr;
    memset((void*)&servaddr, 0, sizeof(struct sockaddr_in));
    servaddr.sin_family = AF_INET;                  
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);   
    servaddr.sin_port = htons(58549);    

то да, recvfrom пишет в sin_port номер порта, с которого пришел пакет.
Все дело в сыром сокете.

Автор: feodorv 7.8.2014, 21:53
Цитата(konshyn @  7.8.2014,  16:11 Найти цитируемый пост)
Все дело в сыром сокете. 

Ага. Видимо, сетевой уровень в этом случае не разбирает датаграмму...

Автор: konshyn 8.8.2014, 10:43
Цитата(feodorv @  7.8.2014,  21:53 Найти цитируемый пост)
Ага. Видимо, сетевой уровень в этом случае не разбирает датаграмму... 

Но source IP записывает.

Автор: feodorv 8.8.2014, 21:36
Цитата(konshyn @  8.8.2014,  11:43 Найти цитируемый пост)
Но source IP записывает. 

Гм. Значит, разбирает только до уровня заголовка IP))) Сырой сокет, однако)))

Заметил в коде недочет. Переменная length_sock должна быть правильно инициализирована перед вызовом recvfrom.

Автор: konshyn 9.8.2014, 13:04
Цитата(feodorv @  8.8.2014,  21:36 Найти цитируемый пост)
Заметил в коде недочет. Переменная length_sock должна быть правильно инициализирована перед вызовом recvfrom. 

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

Автор: feodorv 9.8.2014, 15:47
Цитата(konshyn @  9.8.2014,  14:04 Найти цитируемый пост)
Но я не знаю, как в стандарте.

Переменная обязана быть инициализирована. Она http://www.opennet.ru/man.shtml?topic=recvfrom&category=2&russian=2 коду recvfrom, сколько байт находится по адресу ipaddr, доступных для записи:
Цитата

If from is not NULL, and the underlying protocol provides the source address, this source address is filled in. The argument fromlen is a value-result argument, initialized to the size of the buffer associated with from, and modified on return to indicate the actual size of the address stored there. 
 
У Вас в переменной length_sock - мусорное значение, которое может оказаться подходящим для вызова recvfrom, а может и нет. Как минимум в виндах будет ошибка вызова, если передать неверное значение. В юниксах возможны варианты.

Автор: konshyn 9.8.2014, 17:29
Цитата(feodorv @  9.8.2014,  15:47 Найти цитируемый пост)
В юниксах возможны варианты. 

В linux не вызывает ошибки и работает.
Цитата(feodorv @  9.8.2014,  15:47 Найти цитируемый пост)
 Как минимум в виндах будет ошибка вызова

Понятно smile

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)