LINUX.ORG.RU

Отправка и получение датаграм SO_BROADCAST из другой сети

 , , ,


0

2

Всем доброго времечка, не болеть и не дурнеть от СМИ!

Взялся я писать клиента на Пингвине, который получает бродкаст udp-датаграмму и посылает ответ таким же способом. Нужно это для того, чтобы удаленный девайс, который может находится в другой подсети, доступ к которому ограничен, смог словить пакет, и прислать свой в ответ со своим ip-адресом для дальнейшей с ним работы. Ну вот незадача - если говорить об SOCK_DGRAM, Linux стоит на страже любых поползновений, акромя тех, что в его сетке.

Общение проходит между Windows и Linux (я пишу для этой стороны).

Удалось протиснуться, установив опцию SO_BINDTODEVICE, хоть этот сокет начал отправлять, но этот сокет не принимает, а может лишь отправлять (насколько я понял из мануалов). То, что сработало, ниже:


int main(int argc, char *argv[])
{
    int sockfd;
    struct sockaddr_in their_addr; // connector's address information
    struct hostent *he;
    int numbytes;
    int broadcast = 1;
    
    if (argc != 3) {
        fprintf(stderr,"usage: broadcaster hostname message\n");
        exit(1);
    }

    if ((he=gethostbyname(argv[1])) == NULL) {  // get the host info
        perror("gethostbyname");
        exit(1);
    }

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }
//    if ((sockfd = socket(AF_INET, SOCK_RAW, htons(ETH_P_IP))) == -1) {
//        perror("socket");
//        exit(1);
//    }

    // this call is what allows broadcast packets to be sent:
    if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof broadcast) == -1) {
        perror("setsockopt (SO_BROADCAST)");
        exit(1);
    }
    if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, "enp4s0", strlen("enp4s0")) == -1) {
        perror("setsockopt (SO_BINDTODEVICE)");
        exit(1);
    }

    their_addr.sin_family = AF_INET;	 // host byte order
    their_addr.sin_port = htons(SERVERPORT); // short, network byte order
    their_addr.sin_addr = *((struct in_addr *)he->h_addr);
    memset(their_addr.sin_zero, '\0', sizeof their_addr.sin_zero);


    if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0,
                             (struct sockaddr *)&their_addr, sizeof their_addr)) == -1) {
        perror("sendto");
        exit(1);
     }


    printf("sent %d bytes to %s\n", numbytes,
           inet_ntoa(their_addr.sin_addr));


    close(sockfd);

Далее мои размышления пошли в сторону RAW-сокетов, что-то вроде этого

socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))

Как прочитал из мануалов, сырые сокеты могут получать датаграму еще до ее попадания в ядро, таким образом есть возможность отловить этот броадкаст, и вот моя следующая попытка это сделать:


    int fd;
    int bcast = 1;
    char recvString[MAXRECVSTRING+1]; /* Buffer for received string */
    int recvStringLen;                /* Length of received string */


    /* Create a best-effort datagram socket using UDP */
    if ((fd = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP))) < 0)
        printf("socket() failed");

    struct sockaddr_ll sock;

    sock.sll_family     = AF_PACKET;
    sock.sll_protocol   = htons(ETH_P_IP);
    sock.sll_ifindex    = 0;
    sock.sll_hatype;
    sock.sll_pkttype    = PACKET_BROADCAST;;
    sock.sll_halen;
    sock.sll_addr[8];
    memset(sock.sll_addr, '\0', sizeof(sock.sll_addr));

    if (-1 == bind(fd, (struct sockaddr *) &sock, sizeof(sock)))
    {
        perror("bind");
        close(fd);
        exit(1);
    }
    if (-1 == setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &bcast, sizeof(bcast))) {
        perror("setsockopt (SO_BROADCAST)");
        exit(1);
    }

    while (true)
    {
    /* Receive a single datagram from the server */
    if ((recvStringLen = recvfrom(fd, recvString, MAXRECVSTRING, 0, NULL, 0)) < 0)
        printf("recvfrom() failed");

    recvString[recvStringLen] = '\0';
    printf("Received: %s\n", recvString);    /* Print the received string */

    }

    close(fd);

Только ОНО всё равно не работает, как надо: летит куча пакетов с первый байтом ‘E’. На просторах репозиториев находил, как люди устанавливают фильтры, но там жуткая морзянка из макросов, которую не каждый смелый человек отважится искать по крупицам. С posix сокетами раньше я не работал, использовал curl или qt, но нужда приперла. Прошу помочь навести порядок (что с чем мешать) с этими сокетами.


Посылка броадкастов в удаленную сеть - это тупиковый вариант в ipv4. В ipx если не ошибаюсь такая хрень работала :)

Любой нормальный роутер такую хрень дропает (форвардинг пакетов на широковещательный адрес).

Ты сам то подумай! Есть сетка с несколькими тысячами хостов. Ты туда один пакетик РАЗ! а тебе в ответ несколько тысяч ответов прилетает через узенький канал или еще более веселый вариант: юный хакер посылает пакет у которого оба адреса широковещательные.

PS для приема пакетов приходящих на широковещательный адрес ничего специального программировать не нужно.

vel ★★★★★ ()
Последнее исправление: vel (всего исправлений: 1)
Ответ на: комментарий от vel

Да это понятно, что всё ради безопасности, кто бы спорил, и Линух молодец. Однако, Wireshark чудесным образом ловит эти пакеты на той же машине с линухом. Да и в варианте, где один пакет и ух - будет девайсов не много и отвечать будут только они, а не всё, что движется и слушается, внутри свой протокол. DHCP же не умирает. Нагрузка на сеть - да, но девайсов не так много.

Tumyq ()

В первом примере (который с AF_INET сокетом) не вызывается bind. Сокет не имеет локального адреса, вот и не получает бродкаст. Надо его сбиндить на sin_family=AF_INET, sin_port=htons(SERVERPORT), sin_addr.s_addr=htonl(INADDR_ANY).

Но посылать бродкасты через роутер это действительно хреново. Обычный бродкаст на ip=255.255.255.255, mac=ffff.ffff.ffff роутер должен дропать по rfc. Directed бродкаст на ip=x.y.z.255, mac=gateway-mac роутер может отфорвардить, а может дропнуть, в зависимости от настроек роутера.

iliyap ★★★★★ ()
Ответ на: комментарий от Tumyq

DHCP требует форвардера при отсутствии dhcp-сервера в сети клиента.

Форвардинг пакетов на широковещательный адрес - это нестандартное поведение маршрутизатора.

vel ★★★★★ ()
Ответ на: комментарий от iliyap

Пробовал сделать bind на INADDR_ANY, в таком случае принимаются только те пакеты, которые я собственно сам отправил. Те, что из другой - не приходят, но их все так же видит WireShark. Судя по man, INADDR_ANY это бродкаст подсети, то бишь той, в которой я. Так же, пробовал делать это с INADDR_BROADCAST, результат тот же. Вариант с INADDR_ANY ниже:

        int sockfd;
        char buf[30];
        struct sockaddr_in sendaddr;
        struct sockaddr_in recvaddr;
        int numbytes;
        socklen_t addr_len;
        int broadcast=1;

        if ((sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
            perror("socket");
            exit(1);
        }
        if ((setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST,
                        &broadcast, sizeof broadcast)) == -1) {
            perror("setsockopt - SO_SOCKET ");
            exit(1);
        }
        if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
                       "enp4s0", strlen("enp4s0")) == -1) {
            perror("setsockopt (SO_BINDTODEVICE)");
            exit(1);
        }
        printf("Socket created\n");

        memset(&sendaddr, 0, sizeof sendaddr);
        sendaddr.sin_family = AF_INET;
        sendaddr.sin_port = htons(PORT);
        sendaddr.sin_addr.s_addr = INADDR_BROADCAST;

        memset(&recvaddr, 0, sizeof recvaddr);
        recvaddr.sin_family = AF_INET;
        recvaddr.sin_port = htons(PORT);
        recvaddr.sin_addr.s_addr = INADDR_ANY;
        if (bind(sockfd, (struct sockaddr*)&recvaddr, sizeof recvaddr) == -1) {
            perror("bind");
            exit(1);
        }
        numbytes = sendto(sockfd, "Hello", 5 , 0,
                          (struct sockaddr *)&sendaddr, sizeof sendaddr);
        if (numbytes < 0)
        {
            perror("sendto");
            //exit(1);
        }
        else
        {
            printf("sent %d bytes to %s\n", numbytes,
                   inet_ntoa(sendaddr.sin_addr));
        }

        for (;;) {
            int n;
            fd_set set;
            struct timeval time_500ms = { 0, 500*1000 };
            FD_ZERO(&set);
            FD_SET(sockfd, &set);

            n = select(sockfd+1, &set, NULL, NULL, &time_500ms);
            if (n < 0) {
                perror("select");
                break;
            }
            else if (n == 0) {
                printf("sleep(5)\n");
                sleep(5);
            }
            else if (!FD_ISSET(sockfd, &set)) {
                perror("FD_ISSET");
                break;
            }
            else {
                addr_len = sizeof recvaddr;
                if ((numbytes = recvfrom(sockfd, buf, sizeof buf, 0,
                                         (struct sockaddr *)&recvaddr, &addr_len)) > 0)
                {
                    time_t now = time(NULL);
                    printf("recvfrom: '%.*s' at %s\n", numbytes, buf, ctime(&now));
                }
                else
                    perror("recvfrom");
            }
        }
        close(sockfd);
    }

«дропать по RFC» Имеется в виду DHCP(RFC 2131)?

Tumyq ()
Последнее исправление: Tumyq (всего исправлений: 2)
Ответ на: комментарий от Tumyq

«дропать по RFC» Имеется в виду DHCP(RFC 2131)?

rfc по которому роутер не форвардит пакет на широковещательный адрес сети попробуй найти сам :) Это должно быть ооочень древним rfc.

У dhcp другая проблема. Пакет с 0.0.0.0->255.255.255.255 бесполезно передавать дальше, т.к. отвечать на адрес 0.0.0.0 бессмысленно.

Про твой код.

SO_BINDTODEVICE - это отдельная магия, которая позволяет посылать пакеты мимо таблицы машрутизации. Тебе она нафиг не нужна.

SO_BROADCAST нужен только для посылки пакетов на широковещательный адрес своих (непосредственоо подключенных) сетей.

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

vel ★★★★★ ()
Последнее исправление: vel (всего исправлений: 1)
Ответ на: комментарий от vel

Мультикастинг мне вряд ли подойдёт. Несмотря на то, что девайсы физически будут в одной сети, они могут иметь через пень колоду абсолютно неизвестные настроенные статические ip-шники. Именно поэтому я и влез в это «нелегальное» бродкастовое дело. Спасибо за советы. У меня, вроде как, получилось скрестить ужа с ежом на сокетах - итого созданы два сокета: один с SO_BINDTODEVICE для отправки и другой SOCK_RAW с ETH_P_IP, который, по сути, работает как снифер всей кучималы ещё до обрезки заголовков, начиная с канального уровня. И вот ОНО мне даже 10 Мб за 28 сек в 100Мбит-ной сетке прислало. Единственное, что меня ещё волнует, то, что размер даты упёрся в стандартные 1500 байт. Я полез мастерить что-то с mtu, увеличил - не помогло, увеличил буфер - не помогло, попытался включить/выключить фрагментацию - опять же «лесной друг-индеец» Линух отреагировал крайне отрицательно на все потуги. Понятно, что 1500 это стандартный размер пакета, но было бы здорово, если бы нашлось что-то вроде фрагментацию.

Tumyq ()