首页 文章

使用本地ipv6套接字将UDP发送到本地ipv4地址

提问于
浏览
3

我想知道在Ubuntu 12.04上是否有可能使用其IPv4地址将具有本地链路IPv6地址的套接字的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结构的族应该仍然是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;
}

2 回答

  • 0

    当您使用 ::ffff: 映射地址时,实际上您正在通过网络发送IPv4数据包,同时在软件中使用IPv6套接字 . 它使得更容易支持这两种协议,但它不允许您混合使用不同的地址族 .

    网络上的数据包是IPv4数据包(具有IPv4源和目标地址)或IPv6数据包(具有IPv6源和目标地址) . ::ffff: 地址永远不会在实际数据包中使用 . 它仅作为软件表示存在 .

    如果您使用IPv6套接字与 ::ffff: 地址进行通信,那么在线路上它们将是纯IPv4数据包,并且您的本地IPv4地址将用于您的连接 .

  • 4

    ::ffff: 地址仅用于 IPV6_V6ONLY=0 服务器套接字 .

    如果要连接到IPv4地址,请创建匹配的套接字 .

    如果你只是使用 getaddrinfo() ,你可以简化你的生活:基本上,你把一个主机/端口组合,它会给你一个"address entries"连接列表 . 这些条目包含创建和连接套接字所需的所有内容 .

    这是一个简短的代码片段作为示例:

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

相关问题