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


Автор: konshyn 19.9.2013, 14:18
Всем привет. 
Есть такая цель. Нужно принимать ВЕСЬ трафик, который приходит на интерфейс и не просматривая его, отправить на другой.

Есть код приема всех пакетов(обычный сниффер).

sd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
  
struct sockaddr_ll  s_ll = {0};
s_ll.sll_family = PF_PACKET;    // Тип сокета
s_ll.sll_protocol = htons(ETH_P_ALL);   //Тип применяемого протокола
s_ll.sll_ifindex = index; //индекс сетевого интерфейса (2)

в бесконечном цикле принимаются пакеты 
while (true)
    {
        memset(buff, 0, ETH_FRAME_LEN);
        
        rec = recvfrom(eth0_if, (char *)buff, ifp.mtu + 18, 0, NULL, NULL);
    } 

как мне полученный пакет buff отправить на другой адрес?

UPD: Другими словами, мне нужно перенаправлять все пакеты, которые проходят через мою сетевую карту, на другой комп, а оттуда уже опять отправить адресату.

Автор: StopPanic 20.9.2013, 10:17
А бридж подойдет, или нужен свой велосипед ?
man brctl 
http://www.opennet.ru/base/net/linux_bridge.txt.html

Автор: konshyn 20.9.2013, 10:59
Цитата(StopPanic @ 20.9.2013,  10:17)
А бридж подойдет, или нужен свой велосипед ?
man brctl 
http://www.opennet.ru/base/net/linux_bridge.txt.html

Нужен свой велосипед smile

Автор: StopPanic 20.9.2013, 22:01
Цитата(konshyn @ 20.9.2013,  10:59)
Нужен свой велосипед smile

Ну а в чем собственно проблема то ? Создаешь еще второй сокет, используя индекс второго интерфейса, пихаешь это дело в пол и при чтении из одного записываешь то что пришло в другой. Если мне память не изменяет, то это все работает даже в неблокирующем режиме с еполом. Только учти, что бродкаст тоже будет проходить через все это.

Автор: konshyn 21.9.2013, 12:26
Цитата(StopPanic @ 20.9.2013,  22:01)
Цитата(konshyn @ 20.9.2013,  10:59)
Нужен свой велосипед smile

Ну а в чем собственно проблема то ? Создаешь еще второй сокет, используя индекс второго интерфейса, пихаешь это дело в пол и при чтении из одного записываешь то что пришло в другой. Если мне память не изменяет, то это все работает даже в неблокирующем режиме с еполом. Только учти, что бродкаст тоже будет проходить через все это.

У меня одна сетевая карта, т.е. нет другого интерфейса.
Ну вот создал я другой сокет:

send_socket = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

принял пакет через первый.
отлично. в нем есть все данные. ethernet-заголовок, ip-заголовок, mac-заголовок 
т.е. я получил пакет без изменений, ядро linux ничего не отбрасывало от самого пакета.

прототип функции sendto:
int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen); 

const struct sockaddr *to - это общая структура. к примеру для TCP используется struct sockaddr_in
struct sockaddr_in {
    short int          sin_family;  // Семейство адресов
    unsigned short int sin_port;    // Номер порта
    struct in_addr     sin_addr;    // IP-адрес
    unsigned char      sin_zero[8]; // "Дополнение" до размера структуры sockaddr
};

в ней есть IP адрес.

для пакетных сокетов используется struct sockaddr_ll
struct sockaddr_ll {
unsigned short sll_family; /* AF_PACKET */
unsigned short sll_protocol; /* протокол канального уровня */
int sll_ifindex; /* номер интерфейса */
unsigned short sll_hatype; /* тип заголовка */
unsigned char sll_pkttype; /* тип пакета */
unsigned char sll_halen; /* размер адреса */
unsigned char sll_addr[8]; /* аппаратный адрес */
};

я не могу понять, как отправить, потому что если в sendto я использую sockaddr_in, то пакет отправляется, но до адресата не доходит. без понятия, куда он там отправляется, а как использовать sockaddr_ll?


Автор: StopPanic 21.9.2013, 19:10
Чейта я не понял, а как у тебя вообще сетевая коммутация происходит ? Учитывая что у тебя всего 1 сетевой интерфейс на девайсе который должен только перекидывать пакеты ?

Читаешь - recv, пишешь write. То что пишешь и читаешь - это ethernet фрейм.

Автор: konshyn 21.9.2013, 20:04
Цитата(StopPanic @ 21.9.2013,  19:10)
Чейта я не понял, а как у тебя вообще сетевая коммутация происходит ? Учитывая что у тебя всего 1 сетевой интерфейс на девайсе который должен только перекидывать пакеты ?

Читаешь - recv, пишешь write. То что пишешь и читаешь - это ethernet фрейм.

Ладно. Поправь, если я не прав.
Сетевой интерфейс есть сетевая карта. Я создаю пакетный сокет(socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) , который принимает все пакеты без исключения. Связывать с сетевым интерфейсом его необязательно, т.к. у меня он только один. Имею ввиду функцию int bind()
Создаем еще один такой же сокет. Который будет отправлять пакет по нужному мне адресу. Вопрос, как задать адрес пакету, который получил? для пакетных сокетов используется функция sendto и структура, что писал выше struct sockaddr_ll.

Я не могу понять, где мне задавать адрес.

ethernet-фрейм, это:
(ip-заголовок(ethernet-заголовок(данные)))
правильно?

Автор: StopPanic 21.9.2013, 22:38
Тэкс, понятно что чебе непонятно =) Ладно, сейчас попробую разжевать псевдокодом

Вот формат ethernet кадра, т.е. то что ты будешь получать и должен будешь отправлять.
http://ru.wikipedia.org/wiki/Ethernet#.D0.A4.D0.BE.D1.80.D0.BC.D0.B0.D1.82_.D0.BA.D0.B0.D0.B4.D1.80.D0.B0


socketfd = socket(AF_PACKET, SOCK_RAW, ethertype)
Это создает в системе сокет, и возвращает его дескриптор. Тут следует обратить внимание на ethertype (будет принимать пакеты только этого типа), но если тебе нужен будет весь трафик, то ставим htons(ETH_P_ALL).
Т.е. если в системе будет 2 физических интерфейса, то весь трафик с них с заданным ethertype будет валится в этот сокет, чтобы валилось с конкретного интерфейса, используется bind который опишу ниже.

struct ifreq ifMac;
const char *ethName = "eth0";
memset(&ifMac, 0, sizeof (ifMac));
strncpy(ifMac.ifr_name, ethName, strlen(ethName));

Если необходимо, переводим в PROMISC (http://ru.wikipedia.org/wiki/Promiscuous_mode) . Это нужно например для ethernet мультикаста, т.к. сетевая карта будет отсеивать все что не является ее dest адресом. В твоем случае по идее нужно, т.к. то что ты слушаешь не принадлежит тебе как конечному звену.
ioctl (socketfd, SIOCGIFFLAGS, &ifMac) 
ifMac.ifr_flags |= IFF_PROMISC;
ioctl (socketfd, SIOCSIFFLAGS, &ifMac)


Получаем индекс интефейса:
ioctl (socketfd, SIOCGIFINDEX, &ifMac)

Биндим конкретный интерфейс:
struct sockaddr_ll sAddr;
memset (&sAddr, 0, sizeof (sAddr));
sAddr.sll_family = AF_PACKET;
sAddr.sll_ifindex = ifMac.ifr_ifindex;
sAddr.sll_protocol = ethertype;
bind (socketfd, (struct sockaddr *) &sAddr, sizeof (struct sockaddr_ll))

Все, после того как это сделал, можно добавлять дескриптор socketfd в пол или епол.

Соответственно читаешь и пишешь данные с помощью recv и write, как я уже писал.

То что будешь получать - это будут структуры типа :
struct {
char destMac[6];
char srcMac[6];
char etherType[2];
char data[]
}

Там думаю разберешься после первого recv =) Только учти, у тебя возможен будет дубляж фреймов, если ты не будешь на своей прослушке менять dstmac. Надеюсь wireshark у тебя уже стоит.

Автор: konshyn 21.9.2013, 23:54
все, что ты написал. уже написано smile

while (true)
    {
        memset(buff, 0, ETH_FRAME_LEN);
        
        rec = recvfrom(eth0_if, (char *)buff, ifp.mtu + 18, 0, NULL, NULL);
    } 

код, где считывает пакет.
какой командой мне отправить этот пакет на адрес, к примеру, 192.168.1.1???
и как заполнить структуру?
Вот чего я не понимаю в пакетных сокетах. Я вроде отправляю пакет, и возвращает значение, сколько байт отправил, но! до адресата не доходит.

Автор: StopPanic 22.9.2013, 00:16
Ну ёперный театр, так бы сразу и сказал, что ip адрес. Только вот проблемка - никаких таких команд нет. То что проинициализировано - это работа на mac уровне - тут либо писать свой парсер пакетов (http://ru.wikipedia.org/wiki/IPv4#.D0.A1.D1.82.D1.80.D1.83.D0.BA.D1.82.D1.83.D1.80.D0.B0_.D0.BF.D0.B0.D0.BA.D0.B5.D1.82.D0.B0), либо переходить на уровень выше. 

Автор: konshyn 22.9.2013, 00:25
Т.е. ты хочешь сказать, что мне нужно будет разобрать пакет. Поместить в него свой IP, и потом только отправить?
А если хочу, перенаправлять пакеты, то мне придется разобрать пакет. Всю информацию о получателе поместить вместе с данными, отправить  на другой комп, а там уже снова разобрать пакет. Вытащить из данных старую инфу и снова отправить уже первоначальному получателю?

есть ведь функция sendto, или даже тот же write. я ведь могу сделать обычный сокет TCP или UDP и отправить полученный пакет на нужный мне адрес?

while (true)
    {
        memset(buff, 0, ETH_FRAME_LEN);
        
        rec = recvfrom(eth0_if, (char *)buff, ifp.mtu + 18, 0, NULL, NULL);
    }

он ведь будет мой buff воспринимать просто как данные, неважно, что там хранятся и mac-адреса отправителя и получателя, ip и другая инфа вместе с данными?

UPD:

есть структура о которой писал ранее

struct sockaddr_ll {
unsigned short sll_family; /* AF_PACKET */
unsigned short sll_protocol; /* протокол канального уровня */
int sll_ifindex; /* номер интерфейса */
unsigned short sll_hatype; /* тип заголовка */
unsigned char sll_pkttype; /* тип пакета */
unsigned char sll_halen; /* размер адреса */
unsigned char sll_addr[8]; /* аппаратный адрес */
};

что значит поле: unsigned char sll_addr[8]; /* аппаратный адрес */???

это mac-адрес?

а я могу отправить не по ip, а по mac???

Автор: StopPanic 22.9.2013, 00:33
Да, на этом уровне ты конструируешь пакет сам, вот картинка :
http://www.tldp.org/LDP/tlk/net/protocols.gif

Т.е. добавляешь в буфер dest mac, потом src, потом ethertype ip протокола, потом ip заголовок и т.д. и все это отправляешь write'ом, а при приеме наоборот, парсишь.

Автор: konshyn 22.9.2013, 00:35
StopPanic, понял. Спасибо!  smile 

Автор: StopPanic 22.9.2013, 00:36
Да, кстати не стоит забывать еще про vlan, q-in-q и прочие ethertype-протоколы, которые делают свои преамбулы перед ip заголовком. Это тот еще гемор.

Автор: StopPanic 22.9.2013, 12:17
Вот для уровня выше http://www.opennet.ru/cgi-bin/opennet/man.cgi?topic=raw&category=7 , тут помочь особо не смогу, т.к. не писал протоколы ipv4 уровня. Удачи с экспериментами =)

Автор: konshyn 23.9.2013, 14:18
В принципе, всего, чего я боялся, это то, что придется писать свой "парсер".
Но оказалось намного проще. 
Хорошо, что ты разъяснил, что такое ethernet-фрейм и где моя ошибка. И Спасибо за картинку:) 
Теперь, когда получаю пакет, -  получаю его полностью без изменений - то просто в начало добавляю два поля: адрес получателя и адрес отправителя, т.е. 12 байт(2 mac-адреса). а дальше этот же пакет отправляю функцией sendto();


Для тех, кто будет мучиться с этой функцией при отправке своего пакета: 
прототип функции: 
ssize_t sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen); 

Когда вы полностью вручную формируете пакет, то в пакете уже должны содержаться адреса получателя, отправителя и другие данные. и сокет для отправки - пакетый, т.е. send_socket = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
то отправлять этот пакет нужно без адресной структуры:
sendto(send_socket, buff, sizeof(buff), 0, NULL, 0); 

В принципе, ничего сложного.

Спасибо StopPanic'у.    smile 
Поставил бы "+" в карму, да нельзя:(


UPD:
Перечитал сообщение: http://forum.vingrad.ru/index.php?showtopic=372076&view=findpost&p=2583690
и теперь понял, о чем ты говорил:)

Автор: xvr 24.9.2013, 14:55
Цитата(konshyn @  23.9.2013,  14:18 Найти цитируемый пост)
Поставил бы "+" в карму, да нельзя:(

Можно - поставил  smile 

Автор: konshyn 24.9.2013, 15:57
Цитата(xvr @ 24.9.2013,  14:55)
Цитата(konshyn @  23.9.2013,  14:18 Найти цитируемый пост)
Поставил бы "+" в карму, да нельзя:(

Можно - поставил  smile

Мне нельзя. У меня нет еще 100 постов :(
Но тебе спасибо:)

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