3

Ubuntu 12.04 で、ローカル リンク IPv6 アドレスを持つソケットから、IPv4 アドレスを使用して同じワイヤレス ネットワーク上のデバイスに UDP パケットを送信できるかどうか疑問に思っていました。IPv6アドレスを使用して、その宛先インターフェースにUDPパケットを送信することにすでに成功しています。

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 に設定します。

これは可能ですか?もしそうなら、私は何を間違っていますか?

編集:

要約すると、IPv6 ソケットが特定の IP (またはインターフェイス、どちらか不明) にバインドされている場合、IPv4 メッセージを送信できません。したがって、IPv6 ソケットではワイルドカードを使用する必要があります::0。sockaddr_in6 構造体のファミリは、先頭に::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;
}
4

2 に答える 2

4

マッピングされたアドレスを使用すると::ffff:、ソフトウェアで IPv6 ソケットを使用しながら、実際にはネットワーク経由で IPv4 パケットを送信しています。両方のプロトコルのサポートが容易になりますが、異なるアドレス ファミリを混在させることはできません。

ネットワーク上のパケットは、IPv4 パケット (IPv4 送信元アドレスと宛先アドレスを持つ) または IPv6 パケット (IPv6 送信元アドレスと宛先アドレスを持つ) のいずれかです。::ffff:アドレスが実際のパケットで使用されることはありません。ソフトウェア表現としてのみ存在します。

IPv6 ソケットを使用して::ffff:アドレスと通信する場合、ワイヤ上ではプレーンな IPv4 パケットになり、ローカル IPv4 アドレスが接続側で使用されます。

于 2013-10-24T12:36:48.800 に答える
0

::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);
于 2013-10-25T05:15:50.163 に答える