Отправить UDP на локальный адрес ipv4 с помощью локального сокета ipv6

Мне было интересно, можно ли в Ubuntu 12.04 отправить UDP-пакет из сокета с IPv6-адресом локальной ссылки на устройство в той же беспроводной сети, используя его IPv4-адрес. Мне уже удалось отправить пакет UDP на этот интерфейс назначения, используя его адрес IPv6.

У меня есть сокет с IPv6-адресом, IPv6ONLY не установлен:

int fd = socket(AF_INET6, SOCK_DGRAM, 0);
int no = 0;
setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&no, sizeof(no));
bind(fd, (sockaddr*)&sa, sizeof(sa));

Я просто отправляю партнеру с помощью sendto:

struct sockaddr_in6 peer = create_ipv6_sockaddr(55555, "193.156.108.67", 0, 0, true);
sendto(sock,content,strlen(content),0,(struct sockaddr*)&peer,sizeof(peer));

Эта функция создает sockaddr_in6:

sockaddr_in6 create_ipv6_sockaddr(int port, string addr, uint32_t flowinfo, uint32_t scope_id, bool ipv4=false) {
    struct sockaddr_in6 si;
    si.sin6_family = AF_INET6;
    if (ipv4) {
        si.sin6_family = AF_INET;
    }
    si.sin6_port = htons(port);
    si.sin6_flowinfo = flowinfo; // Should be 0 or 'random' number to distinguish this flow
    if (ipv4) {
        addr = "::ffff:" + addr;
    }
    inet_pton(AF_INET6, addr.c_str(), &si.sin6_addr);
    if (!si.sin6_addr.s6_addr) {
        perror("Address is wrong..");
    }
    si.sin6_scope_id = scope_id;
    return si;
}

Обычно, если адрес является IPv4-адресом, я добавляю к нему ::ffff: и устанавливаю для семейства AF_INET.

Возможно ли это сделать. Если да, то что я делаю не так?

РЕДАКТИРОВАТЬ:

Подводя итог, сообщение IPv4 не может быть отправлено, если сокет IPv6 привязан к определенному IP (или интерфейсу, не уверен, какой). Поэтому сокет IPv6 должен использовать подстановочный знак ::0. Семейство структуры sockaddr_in6 должно по-прежнему быть AF_INET6 с добавленным ::ffff:. Код не вернет ошибку, если используется AF_INET4, но, по моему опыту, фактическое сообщение не отправляется.

Действительно, если вместо того, чтобы создавать структуру sockaddr самостоятельно, вы получите ее из getaddrinfo, вы сможете передать ее напрямую в сокет с подстановочными знаками IPv6 для отправки.

ИЗМЕНИТЬ ГОДЫ СПУСТЯ:

По запросу @Erfan я откопал код. Боюсь сказать, что я больше не понимаю всего этого, и я не могу сказать вам, действительно ли это работает.

sockaddr_in6 create_ipv6_sockaddr(int port, string addr, uint32_t flowinfo, uint32_t scope_id, bool ipv4=false) {
    struct sockaddr_in6 si;
    si.sin6_family = AF_INET6;
    //  if (ipv4) {
    //      si.sin6_family = AF_INET;
    //  }
    si.sin6_port = htons(port);
    si.sin6_flowinfo = flowinfo; // Should be 0 or 'random' number to distinguish this flow
    if (ipv4) {
        addr = "::ffff:" + addr;
    }
    inet_pton(AF_INET6, addr.c_str(), &si.sin6_addr);
    if (!si.sin6_addr.s6_addr) {
        perror("Address is wrong..");
    }
    //  char s[40];
    //  inet_ntop(AF_INET6, &(si.sin6_addr), s, sizeof(s));
    //  fprintf(stderr, "Sockaddr %d %s\n", si.sin6_family, s);
    si.sin6_scope_id = scope_id;
    if (scope_id == 0 && !ipv4) {
        si.sin6_scope_id = ipv6_to_scope_id(&si); // Interface number
    }
    return si;
}

int ipv6_to_scope_id(sockaddr_in6 *find) {
    struct ifaddrs *addrs, *iap;
    struct sockaddr_in6 *sa;
    char host[NI_MAXHOST];

    getifaddrs(&addrs);
    for (iap = addrs; iap != NULL; iap = iap->ifa_next) {
        if (iap->ifa_addr && (iap->ifa_flags & IFF_UP) && iap->ifa_addr->sa_family == AF_INET6) {
            sa = (struct sockaddr_in6 *)(iap->ifa_addr);
            if (memcmp(&find->sin6_addr.s6_addr, &sa->sin6_addr.s6_addr, sizeof(sa->sin6_addr.s6_addr)) == 0) {
                getnameinfo(iap->ifa_addr, sizeof(struct sockaddr_in6), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
                fprintf(stderr, "Found interface %s with scope %d\n", host, sa->sin6_scope_id);
                return sa->sin6_scope_id;
            }
        }
    }
    freeifaddrs(addrs);
    return 0;
}

И привязать:

int bind_ipv6 (sockaddr_in6 sa) {
    int fd = socket(AF_INET6, SOCK_DGRAM, 0);
    if (fd < 0) {
        perror("Creating socket failed");
    }
    char str[40];
    inet_ntop(AF_INET6, &(sa.sin6_addr), str, sizeof(str));
    //  fprintf(stderr, "Bind to %s:%d\n", str, ntohs(sa.sin6_port));
    int no = 0;
    if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&no, sizeof(no)) < 0 ) { // Only works with wildcard
        perror("V6ONLY failed");
    }
    if (bind(fd, (sockaddr*)&sa, sizeof(sa)) < 0) {
        perror("Binding failed");
    }

    return fd;
}

person Vincent Ketelaars    schedule 24.10.2013    source источник
comment
здесь приведены примеры необработанных сокетов: pdbuchan.com/rawsock/rawsock.html. inet_pton вызывается с AF_INET в случае IPV4 в примере udp6_to4.c   -  person lucasg    schedule 24.10.2013


Ответы (2)


Когда вы используете ::ffff: сопоставленные адреса, вы фактически отправляете пакеты IPv4 по сети при использовании сокетов IPv6 в программном обеспечении. Это упрощает поддержку обоих протоколов, но не позволяет смешивать разные семейства адресов.

Пакет в сети представляет собой либо пакет IPv4 (с адресами источника и назначения IPv4), либо пакет IPv6 (с адресами источника и назначения IPv6). Адрес ::ffff: никогда не будет использоваться в реальном пакете. Он существует только как программное обеспечение.

Если вы используете сокеты IPv6 для связи с ::ffff: адресами, тогда на проводе они будут обычными пакетами IPv4, а ваш локальный IPv4-адрес будет использоваться на вашей стороне соединения.

person Sander Steffann    schedule 24.10.2013
comment
Так что мне все равно нужно создать структуру sockaddr_in6, как я это делаю сейчас. Затем сокет IPv6 превратит его в пакет IPv4 с локальным адресом IPv4 в качестве источника и адресом назначения IPv4. Так почему же я все еще получаю сообщение об ошибке Сеть недоступна? - person Vincent Ketelaars; 24.10.2013
comment
Попробуйте без si.sin6_family = AF_INET; и относитесь к нему как к IPv6 из программного обеспечения - person Sander Steffann; 24.10.2013
comment
это ничего не меняет, боюсь. - person Vincent Ketelaars; 24.10.2013
comment
Ой, что ты вставляешь sa? Если вы привязываетесь к IPv6-адресу, он больше не может отправлять пакет с исходного IPv4-адреса. IPV6_V6ONLY=0 работает только тогда, когда ваш локальный сокет привязан к подстановочному адресу. - person Sander Steffann; 24.10.2013
comment
привет @Oxidator, не могли бы вы выложить рабочий код после модификации? У меня такая же проблема в моем приложении, и я должен сказать, что я очень ботаник в случае этих сетевых вещей. Ваша помощь облегчит мне жизнь. Заранее спасибо... :) - person Erfan; 25.07.2016
comment
@Erfan Я добавил код, который смог найти. Надеюсь, это поможет. Не знаю, действительно ли это работает. Прошло много времени с тех пор, как я им пользовался;) - person Vincent Ketelaars; 26.07.2016
comment
Большое спасибо @Oxidator за код. Я попробую это сегодня и дам вам знать. :) Стоит ли вносить какие-то изменения и на стороне сервера? У меня есть сервер, поддерживающий ipv4, который был написан много лет назад! :( - person Erfan; 26.07.2016
comment
Также я не нашел реализации функции ipv6_to_scope_id (& si); - person Erfan; 26.07.2016
comment
@Erfan Я тоже это добавил. Виноват. Удачи :) - person Vincent Ketelaars; 27.07.2016
comment
@Oxidator еще раз спасибо. Но мне не повезло, это не работает. Не удается выполнить привязку и отображается следующая ошибка: Ошибка привязки: не удается назначить запрошенный адрес :( - person Erfan; 27.07.2016
comment
@Erfan Мне жаль это слышать, но я мало что могу сделать. Возможно, вам будет лучше, если вы откроете новый вопрос. Удачи! - person Vincent Ketelaars; 27.07.2016
comment
Да, в любом случае, огромное спасибо за помощь @Oxidator :) - person Erfan; 27.07.2016

::ffff: адреса предназначены только для использования в IPV6_V6ONLY=0 серверных сокетах.

Если вы хотите подключиться к IPv4-адресу, создайте соответствующий сокет.

Вы можете упростить себе жизнь, если просто используете getaddrinfo(): по сути, вы вводите комбинацию хост / порт, и она дает вам список «записей адресов» для подключения. Эти записи содержат все необходимое для создания и подключения сокета.

Вот небольшой фрагмент кода в качестве примера:

struct addrinfo hints = { .ai_socktype=SOCK_STREAM, .ai_flags = AI_CANONNAME };
struct addrinfo * ai;
char name[100];
int sockfd = -1;
int st = getaddrinfo(host, sport, &hints, &ai);
if (st < 0) {
    // complain and exit
}
// Now we have the wanted infos in ai.
struct addrinfo * aii;
for (aii=ai; aii; aii=aii->ai_next) {
    /* Create a socket */
    if((sockfd = socket(aii->ai_family, aii->ai_socktype, aii->ai_protocol)) == -1) {
        continue;
    }
    else {
        /* Establish a connection */
        if(connect(sockfd, aii->ai_addr, aii->ai_addrlen ) == -1) {
            close(sockfd);
            sockfd = -1;
            continue;
        }
        else {
            // Success. Get IP address connected to in a readable form.
            int st = getnameinfo(aii->ai_addr, aii->ai_addrlen, name, sizeof name,
                NULL, 0, NI_NUMERICHOST);
            if (st != 0) name[0] = '\0';
            break;
        }
    }
}
freeaddrinfo(ai);
person glglgl    schedule 25.10.2013
comment
Мне интересно. Как должна работать первая строка кода? - person Vincent Ketelaars; 25.10.2013
comment
@Oxidator Первая строка - это инициализатор для данного struct (см. здесь и здесь). - person glglgl; 25.10.2013
comment
Мне очень нравится синтаксис, но моему компилятору почему-то нет. - person Vincent Ketelaars; 25.10.2013
comment
@Oxidator Тогда вы должны изменить его: посмотрите, как struct addrinfo определено в вашей системе (или в целом), и установите значения соответствующим образом. Или сделайте struct addrinfo hints = {}; hints.ai_socktype=SOCK_STREAM; hints.ai_flags = AI_CANONNAME;. Или воздержитесь от использования устаревшего компилятора. ;-) - person glglgl; 25.10.2013
comment
struct addrinfo hints = { AI_CANONNAME, AF_UNSPEC, SOCK_STREAM };, см. здесь. - person glglgl; 25.10.2013
comment
Ой .. Я его поменял. Нет проблем .. Просто не знаком с этим синтаксисом .. В какой-то момент я попытаюсь выяснить, почему у меня он не работает. - person Vincent Ketelaars; 25.10.2013
comment
Насколько я понимаю, этот синтаксис можно использовать только в C, а не в C ++, что и объясняет ... :) - person Vincent Ketelaars; 25.10.2013
comment
@Oxidator О, это было про C ++ - не видел, извините. - person glglgl; 25.10.2013