I was wondering whether it is possible, on Ubuntu 12.04, to send an UDP packet from a socket with a local link IPv6 address to a device on the same wireless network using its IPv4 address. I have already succeeded in sending a UDP packet to that destination interface using its IPv6 address.

I have a socket with IPv6 address, IPv6ONLY not set:

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));

I just send to a peer with 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));

This function creates the 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;
}

Basically if the address is a IPv4 address, I prepend it with ::ffff:, and I set the family to AF_INET.

Is this possible to do. If so, what am I doing wrong?

EDIT:

To summarize, the IPv4 message cannot be send if the IPv6 socket is bound to a specific ip (or interface, not sure which). Therefore the IPv6 socket should use the wildcard ::0. The family of the sockaddr_in6 struct should still be AF_INET6, with the prepended ::ffff:. The code will not return failure if AF_INET4 is used, but in my experience no actual message is sent.

Indeed if instead of creating the sockaddr struct yourself, you get it from getaddrinfo, you will be able to pass it directly to a IPv6 wildcard socket to send.

EDIT YEARS LATER:

Per request, @Erfan, I dug up the code. I'm afraid to say that I no longer understand it all, and I cannot tell you whether it actually works.

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;
}

And to bind:

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;
}
有帮助吗?

解决方案

When you use the ::ffff: mapped addresses then you are actually sending IPv4 packets across the network while using IPv6 sockets in software. It makes it easier to support both protocols, but it doesn't allow you to mix the different address families.

The packet on the network is either an IPv4 packet (with IPv4 source and destination addresses) or an IPv6 packet (with IPv6 source and destination addresses). The ::ffff: address will never be used in an actual packet. It only exists as a software representation.

If you use IPv6 sockets to communicate with ::ffff: addresses then on the wire they will be plain IPv4 packets, and your local IPv4 address will be used on your side of the connection.

其他提示

::ffff: addresses are only for usage in IPV6_V6ONLY=0 server sockets.

If you want to connect to an IPv4 address, create a matching socket.

You can simplify your life if you just use getaddrinfo(): essentially, you put a host/port combination in and it will give you a list of "address entries" to connect to. These entries contain everything needed for creating and connecting a socket.

Here is a short code snippet as an example:

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);
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top