2013-10-24 139 views
3

我想知道是否可以在Ubuntu 12.04上將UDP數據包從具有本地鏈接IPv6地址的套接字發送到使用其IPv4的同一無線網絡上的設備地址。我已經成功地使用其IPv6地址向該目的地接口發送UDP數據包。使用本地ipv6套接字發送UDP到本地ipv4地址

我有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通配符套接字來發送。

EDIT年後:

每請求,@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; 
} 
+0

有原始套接字這裏的例子:http://www.pdbuchan.com/rawsock/rawsock.html。在udp6_to4.c示例中使用IPV4時,使用AF_INET調用inet_pton – lucasg

回答

4

當您使用::ffff:映射地址,那麼你實際上是在網絡上發送IPv4包,而在軟件使用IPv6插槽。它使支持這兩種協議變得更容易,但它不允許你混合使用不同的地址族。

網絡上的數據包是IPv4數據包(帶有IPv4源和目標地址)或IPv6數據包(帶有IPv6源和目標地址)。 ::ffff:地址將永遠不會用在實際的數據包中。它只作爲軟件表示形式存在。

如果您使用IPv6套接字與::ffff:地址進行通信,則在連線上它們將是普通IPv4數據包,並且您的本地IPv4地址將用於連接的一側。

+0

因此,我仍然應該像現在一樣創建一個sockaddr_in6結構。然後,IPv6套接字將使其成爲一個IPv4數據包,其中本地IPv4地址爲源和IPv4目標地址。那麼爲什麼我仍然得到**網絡無法訪問**錯誤? –

+0

嘗試不使用'si.sin6_family = AF_INET;'並將其視爲來自軟件 –

+0

的IPv6,它不會改變任何我害怕的東西。 –

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

我很好奇。代碼的第一行應該如何工作? –

+0

@Oxidator第一行是給定'struct '(見[這裏](http://en.wikipedia.org/wiki/Struct_%28C_programming_language%29#Struct_initialization)和[here](http://publib.boulder.ibm.com/infocenter/ln xpcomp/v8v101/index.jsp的?主題=%2Fcom.ibm.xlcpp8l.doc%2Flanguage%2Fref%2Fstrin.htm))。 – glglgl

+0

我真的很喜歡這個語法,但是我的編譯器沒有出於某種原因。 –

相關問題