Dual-Stack und C

Vom einfachen Programm zum fertigen Debian-Paket, Fragen rund um Programmiersprachen, Scripting und Lizenzierung.
Antworten
wanne
Moderator
Beiträge: 7462
Registriert: 24.05.2010 12:39:42

Dual-Stack und C

Beitrag von wanne » 05.04.2014 23:11:41

Hi,
ich versuche gerade Ein TCP-Programm in C zu schreiben und komme nicht wo wirklich weiter. Das sieht in etwa so aus:

Code: Alles auswählen

struct addrinfo h, *r, *r0;;
memset(&h, 0, sizeof(h));
h.ai_family = AF_UNSPEC;
h.ai_socktype = SOCK_STREAM;
h.ai_flags = AI_PASSIVE;
h.ai_protocol = IPPROTO_TCP;

int e, servSock = -1;
e = getaddrinfo(NULL, portnum, &h, &r0);
if (e)
  exit(1);
for (struct addrinfo *addr = r0; addr != NULL; addr = addr->ai_next) {
  servSock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
  if (servSock < 0)
      continue;
  if((!bind(servSock, addr->ai_addr, addr->ai_addrlen)) && (!listen(servSock, 23)))
[…]
Tja und jetzt kommt das ursprüngliche Problem: getaddrinfo liefert mir eine Liste mit 2 Adressen zurück: Der für IPv4 und die mit IPv6. Nachdem ich bind auf die IPv4 Adresse gemacht habe, kann ich kein Bind mehr auf die IPv6 addresse machen.
Also habe ich mir mal Hilfe geholt: http://mirror7.meh.or.id/Programming/tc ... amming.pdf
Da (und überall anders) steht, dass ich einfach die erste Adresse nehmen soll und dass dass für einen Dualstack-Rechner dann die IPv6 adresse ist und die dann auch über IPv4 über die kompatibilitätsadressen erreichbar ist.

Problem1 (Harmos): Ich hätte dann nur die Komptibilitätsadresse. Das ist eher unschön. gerade, wenn man sich dann die Client Adresse holt, muss man die jedes mal zurückübersetzen. Außerdem ist meine IPv4 connectivität weg /proc/sys/net/ipv6/bindv6only auf 1 steht. Die anderen Serverprogramme können das richtig und binden auf beides.
Problem2: Entgengen aller Beteuerungen bekomme ich aber zuerst die IPv4 Adresse zurück. Ich könnte das dadurch behebn, dass ich AF_INET6 setze, dann funktioniert das Programm aber nicht mehr wenn man keine IPv6 unterstützung hat. Immer die letzte zu nehmen scheint auch keine gute Idee zu sein, weil eigentlich sollte es ja anders herum sein.
rot: Moderator wanne spricht, default: User wanne spricht.

PatrickOliver
Beiträge: 70
Registriert: 03.04.2014 10:54:32

Re: Dual-Stack und C

Beitrag von PatrickOliver » 08.04.2014 14:55:15

Code: Alles auswählen

uname -r
2.6.26-2-686

Code: Alles auswählen

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>

int main () {
        const char *portnum={"8080"};
        struct addrinfo h, *r, *r0, *addr;
        memset(&h, 0, sizeof(h));

        h.ai_family = AF_UNSPEC;
        h.ai_socktype = SOCK_STREAM;
        h.ai_flags = AI_PASSIVE;
        h.ai_protocol = IPPROTO_TCP;

        int e, servSock = -1;
        if ( 0!= (e = getaddrinfo(NULL, portnum, &h, &r0)) ) {
                return EXIT_FAILURE;
        }

        for (addr=r0; addr != NULL; addr = addr->ai_next) {
                /*
                        struct addrinfo {
                                int              ai_flags;
                                int              ai_family;
                                int              ai_socktype;
                                int              ai_protocol;
                                socklen_t        ai_addrlen;
                                struct sockaddr *ai_addr;
                                char            *ai_canonname;
                                struct addrinfo *ai_next;
                        };
                */
                // 2=PF_INET | 10=PF_INET6, 1=SOCK_STREAM | 2=SOCK_DGRAM, 6=IPPROTO_TCP, 17=IPPROTO_UDP
                printf("DEBUG:%d %d %d\n", addr->ai_family, addr->ai_socktype, addr->ai_protocol);
        }

        return EXIT_SUCCESS;
}

Code: Alles auswählen

my@test:~/tests$ gcc -Wall -D_GNU_SOURCE -o test test.c
test.c: In function ‘main’:
test.c:19: warning: unused variable ‘servSock’
test.c:11: warning: unused variable ‘r’

Code: Alles auswählen

my@test:~/tests$ ./test
DEBUG:10 1 6
DEBUG:2 1 6
So ganz kann ich dir noch nicht folgen, aber bei mir ist die erste Adresse der Protokoll-Familie PF_INET6 (10) zugewiesen.

wanne
Moderator
Beiträge: 7462
Registriert: 24.05.2010 12:39:42

Re: Dual-Stack und C

Beitrag von wanne » 11.04.2014 00:16:27

Hier läuft wheezy:

Code: Alles auswählen

uname -r
3.2.0-4-amd64
Aber bei mir siehts eben genau andersherum aus:

Code: Alles auswählen

$ ./a.out 
DEBUG:2 1 6
DEBUG:10 1 6
Außerdem hätte ich eigentlich gerne was, das eben ausdrücklich auf beides hört. Beispiel:

Code: Alles auswählen

$ ncat -l 23532 & sleep 1 && lsof -i && killall ncat
[1] 16399
COMMAND   PID  USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
ncat    16399 wanne    3u  IPv6 287338      0t0  TCP *:23532 (LISTEN)
ncat    16399 wanne    4u  IPv4 287339      0t0  TCP *:23532 (LISTEN)
[1]+  Terminated              ncat -l 23532
rot: Moderator wanne spricht, default: User wanne spricht.

PatrickOliver
Beiträge: 70
Registriert: 03.04.2014 10:54:32

Re: Dual-Stack und C

Beitrag von PatrickOliver » 11.04.2014 09:37:49

Tatsache, ich kann das auf einem Wheezy (3.2.0-4-amd64, wie bei dir) reproduzieren.

In struct addrinfo *addr stehen also pro Schleifendurchlauf Informationen zu einer oder mehreren IPv4 und/oder IPv6 Adresse(n), welche das laufende Programm per bind oder connect nutzen kann (manpage getaddrinfo).
Da wir scheinbar die Reihenfolge nicht zuverlässig vorhersagen können, schlage ich folgendes vor.

Code: Alles auswählen

for (addr=r0; addr != NULL; addr = addr->ai_next) {
	if ( PF_INET == addr->ai_family ) {
		// IPv4
		// - Socket erstellen -> int sock_v4=socket(addr->ai_family, ...
		// - Socket-Option SO_REUSEADDR per setsockopt setzen (manpage socket, setsockopt)
		// - ggf. Socket auf Non-Blocking setzen (manpage fcntl -> O_NONBLOCK)
		// - bind
	} else if ( PF_INET6 == addr->ai_family ) {
		// IPv6
		// - Socket erstellen -> int sock_v6=socket(addr->ai_family, ...
		// - Socket-Option SO_REUSEADDR per setsockopt setzen (manpage socket, setsockopt)
		// - Socket-Option IPV6_V6ONLY per setsockopt setzen!
		int optval=1;
		if ( -1 == setsockopt(sock_v6, IPPROTO_IPV6, IPV6_V6ONLY, &optval, sizeof(optval)) ) ...
		// - ggf. Socket auf Non-Blocking setzen (manpage fcntl -> O_NONBLOCK)
		// - bind
	}
}
Am Ende hättest du int sock_v4 und int sock_v4, die man z.B. per select oder epoll überwachen kann.
Das Ganze ist ziemlich aus der Hüfte geschossen, aber vieleicht kommen wir der Lösung näher.

Nachtrag:
getaddrinfo kann auch mehr als eine IPv4 und mehr als eine IPv6 Adresse liefern!
Und nochwas

Code: Alles auswählen

man freeaddrinfo

Benutzeravatar
habakug
Moderator
Beiträge: 4313
Registriert: 23.10.2004 13:08:41
Lizenz eigener Beiträge: MIT Lizenz

Re: Dual-Stack und C

Beitrag von habakug » 11.04.2014 10:48:29

Hallo!

Vielleicht mal einen Blick in die "/etc/gai.conf" werfen:
man gai.conf hat geschrieben:A call to getaddrinfo(3) might return multiple answers. According to
RFC 3484 these answers must be sorted so that the answer with the high‐
est success rate is first in the list. The RFC provides an algorithm
for the sorting. The static rules are not always adequate, though.
For this reason, the RFC also requires that system administrators
should have the possibility to dynamically change the sorting. For the
glibc implementation, this can be achieved with the /etc/gai.conf file.
Gruss, habakug
( # = root | $ = user | !! = mod ) (Vor der PN) (Debianforum-Wiki) (NoPaste)

PatrickOliver
Beiträge: 70
Registriert: 03.04.2014 10:54:32

Re: Dual-Stack und C

Beitrag von PatrickOliver » 11.04.2014 11:13:18

Guter Input, so in der Art steht es auch in der Manpage zu getaddrinfo.
There are several reasons why the linked list may have more than one addrinfo structure, including: the network host is multihomed, accessible over multiple protocols (e.g., both AF_INET and AF_INET6); or the same service is available from multiple socket types (one SOCK_STREAM address and another SOCK_DGRAM address, for example).
Normally, the application should try using the addresses in the order in which they are returned.
The sorting function used within getaddrinfo() is defined in RFC 3484; the order can be tweaked for a particular system by editing /etc/gai.conf (available since glibc 2.5).
Ändert aber nichts daran, dass die Option IPV6_V6ONLY bei einem IPv6-Socket gesetzt werden muss, wenn auf dem selben Port auch auf einem IPv4-Socket gelauscht werden soll.
So jedenfalls habe ich das verstanden.

Benutzeravatar
habakug
Moderator
Beiträge: 4313
Registriert: 23.10.2004 13:08:41
Lizenz eigener Beiträge: MIT Lizenz

Re: Dual-Stack und C

Beitrag von habakug » 11.04.2014 20:45:48

Hallo!

Du brauchst auch ein "in6addr_any" (und "inaddr_any"). Hier [1] ist es kommentiert:

Code: Alles auswählen

[...]
      /********************************************************************/
      /* After the socket descriptor is created, a bind() function gets a */
      /* unique name for the socket.  In this example, the user sets the  */
      /* address to in6addr_any, which (by default) allows connections to */
      /* be established from any IPv4 or IPv6 client that specifies port  */
      /* 3005. (i.e. the bind is done to both the IPv4 and IPv6 TCP/IP    */
      /* stacks).  This behavior can be modified using the IPPROTO_IPV6   */
      /* level socket option IPV6_V6ONLY if desired.                      */ 
      /********************************************************************/   
      memset(&serveraddr, 0, sizeof(serveraddr));
      serveraddr.sin6_family = AF_INET6;
      serveraddr.sin6_port   = htons(SERVER_PORT);
      /********************************************************************/ 
      /* Note: applications use in6addr_any similarly to the way they use */
      /* INADDR_ANY in IPv4.  A symbolic constant IN6ADDR_ANY_INIT also   */
      /* exists but can only be used to initialize an in6_addr structure  */
      /* at declaration time (not during an assignment).                  */
      /********************************************************************/ 
      serveraddr.sin6_addr   = in6addr_any;
[...]
Gruss, habakug

[1] http://publib.boulder.ibm.com/infocente ... ptboth.htm
[2] http://publib.boulder.ibm.com/infocente ... xip6client
( # = root | $ = user | !! = mod ) (Vor der PN) (Debianforum-Wiki) (NoPaste)

PatrickOliver
Beiträge: 70
Registriert: 03.04.2014 10:54:32

Re: Dual-Stack und C

Beitrag von PatrickOliver » 15.04.2014 09:02:54

Ich hoffe doch sehr, dass du am Ende lauffähigen Beispielcode posten wirst, den wir studieren können! ;-)

Antworten