Christian Seiler: vhost apache unter osx

Beitrag lesen

Hallo Christoph,

Listen 80, Listen *:80 und Listen 0.0.0.0:80 sind äquivalent

Im strengen Sinn nicht.

Das schrieb ich ja selbst. ;-)

Aber wenn man nur IPv4 einsetzt, dann haben sie die gleiche Wirkung, das meinte ich mit äquivalent - nicht mehr, nicht weniger.

Hintergrund: Bei der Listen-Direktive gibt man nur an, wo das Betriebssystem lauschen soll.

Das halte ich für eine fragwürdige und deshalb auch zu diskutierende Aussage. In der Apache-Dokumentation steht: "Die Direktive Listen weist den Apache an, nur an den angegebenen IP-Adressen oder Ports zu lauschen". Da ist also nichts von "Betriebssystem" formuliert.

Ähm, ich wollte das ganze jetzt nicht mit technischem Slang überladen, aber da Du das jetzt anzweifelst:

Der Apache ruft im Endeffekt nur die Systemaufrufe bind() und listen() auf. Wenn ich mal aus dem Apache-Code zitieren darf. Aus server/listen.c:

// Zeile 132 (Apache 2.2 SVN)
if ((stat = apr_socket_bind(s, server->bind_addr)) != APR_SUCCESS)
[...]
// Zeile 140 (Apache 2.2 SVN)
if ((stat = apr_socket_listen(s, ap_listenbacklog)) != APR_SUCCESS)

Hier werden die Funktionen apr_socket_bind() und apr_socket_listen() aufgerufen. Diese sind im Endeffekt NICHTS ANDERES als Wrapper um die Systemaufrufe bind() und listen().

bind() sagt dem Betriebssystem: "Dieses Socket ist an diese Adresse gebunden" während listen() sagt "Lausche auf eingehende Verbindungen".

Schauen wir uns nun Zeile 603 von server/listen.c an, die die "Listen"-Einträge verarbeitet:

rv = apr_parse_addr_port(&host, &scope_id, &port, argv[0], cmd->pool);

Dort wird die Funktion apr_parse_addr_port() aufgerufen, die im Endeffekt nichts anderes tut, als die Variablen host, scope_id und port zu befüllen auf Grund der Daten, die als Angabe für Listen angegeben wurden.

Beispiel: Listen 10.0.0.1:1234
Nach Aufruf der Funktion:
      host == "10.0.0.1"
      scope_id == NULL
      port == "1234"

Beispiel: Listen 1234
      host == NULL
      scope_id == NULL
      port == "1234"

Beispiel: Listen *:1234
      host == "*"
      scope_id == NULL
      port == "1234"

[scope_id ist eh nur für IPv6 und kann hier ignoriert werden]

Weiter unten ist dann noch folgende Abfrage drin:

if (host && !strcmp(host, "*")) {
        host = NULL;
    }

Sprich: Wenn host angegeben wurde und das gleiche ist wie "*", dann wird host auf NULL gesetzt. Hier haben wir die Äquivalenz von "*" und keiner Angabe (ok, soweit waren wir uns ja eh einig).

Dann wird weiter unten die Funktion alloc_listener() mit host und port aufgerufen, schauen wir uns die an:

In Zeile 244 der gleichen Datei sehen wir, dass die Variable host als Parameter addr übergeben wird, während der Parameter für den Port gleich heißt: port.

In Zeile 283 steht:

if ((status = apr_sockaddr_info_get(&sa, addr, APR_UNSPEC, port, 0,
                                        process->pool))

Dort wird also eine Struktur des Types apr_sockaddr_t (so wird sa deklariert) gefüllt mit den Daten aus addr und port. Im Endeffekt füllt diese Funktion aus der Apache Portable Runtime die sockaddr_t-Struktur so, dass das Betriebssystem beim bind()-Aufruf etwas damit anfangen kann. Wenn addr "0.0.0.0" ist, dann ändert die Funktion an der IP gar nichts, lässt sie vom Betriebssystem einfach umrechnen in die maschinenlesbare Form. Wenn addr dagegen NULL ist (d.h. wenn "alle Adressen" gefordert werden), dann füllt die Funktion die Struktur mit der Adresse, die für "alle Interfaces" steht - und die ist eben "0.0.0.0" auf allen Betriebssystemen.

Das Betriebssystem bekommt also in jedem Fall eine struct sockaddr_in zu sehen, bei der der Eintrag sin_addr auf INADDR_ANY (= 0) gesetzt ist - was dann das Betriebssystem anweist "dieser Socket ist an alle IP-Adressen gebunden".

Die folgende Tabelle [...]

...transportiert diesen Irrtum, was den Terminus "Betriebssystem" angeht.

Nein, siehe oben.

Beispiel: Der Apache lauscht auf allen Interfaces, dies sei zum einen eine normale Netzwerkkarte (oder WLAN) mit 10.0.0.1 (als Beispiel jetzt mal), zum anderen natürlich localhost mit 127.0.0.1.

Und das ist es erneut nicht ganz. Dem Apache sind "Interfaces" ziemlich egal - hier könnte es allerdings sein, daß du unter "Interface" etwas anderes verstehst als ich.

Unter Interface verstehe ich alles, was unter ifconfig (UNIX) bzw. ipconfig (Windows) zu sehen ist - und da gehört localhost auch dazu!

Natürlich ist 'lo' (für Localhost) nur ein internes Interface, aber es ist ein Interface.

Wie das Interface physikalisch realisiert ist, interessiert den Apache natürlich wirklich nicht, das ist klar. Aber unterschiedliche physikalische Interfaces führen eben auch zu unterschiedlichen "logischen" Interfaces, die zwar alle, was IP angeht, gleich ansteuerbar sind, aber dennoch unterscheidbar sind!

Wenn ein Programm sich ber bind() an eine Adresse binden will, dann MUSS ein Interface diese Adresse als eingestellte IP-Adresse besitzen, sonst gibt's einen Fehler. Sprich: Wenn ich zwei Interfaces 'lo' (mit 127.0.0.1 als IP) und 'eth0' (mit 10.0.0.1 als IP) habe, dann kann ich NICHT sagen "nimm 10.0.0.2 als IP, an die Du den Socket binden sollst" - dann schlägt der Aufruf von bind() nämlich fehl! Warum? Weil's kein Interface mit dieser IP gibt!

Natürlich übergibt man an bind() immer Adressen, an die ein Dienst gebunden werden soll, weil das viel universeller (aus Programmsicht) ist, als sich um Interfaces im Programm direkt kümmern zu müssen - aber der Systemkern setzt sowas immer so um, dass das passende Interface für diese IP herausgesucht wird.

Wenn nun eine Verbindung hereinkommt, dann fragt der Apache das Betriebssystem "Woher kommt die Verbindung?"

Nein, das tut er meines Wissens nicht.

Doch. Zum Beispiel (andere MPMs machen's natürlich genauso) ruft server/mpm/prefork/prefork.c die Funktion ap_run_create_connection() auf. Das führt dann dazu, dass im Endeffekt core_create_conn() in server/core.c, Zeile 3873 aufgerufen wird. In Zeile 3890 steht nun:

if ((rv = apr_socket_addr_get(&c->local_addr, APR_LOCAL, csd))

Sprich: Dort wird in das Feld local_addr der Struktur c die lokale (!) Adresse der bestehenden Verbindung eingetragen!

Und an Hand derer kann der Server dann auch für IP-basierte virtuelle Hosts unterscheiden.

Und etwas weiter unten macht er das gleiche für die Remote-Adresse, um so die Client-IP zu kennen.

Er fragt allerdings das Protokoll, ob er denn mit den hereinkommenden Paketen etwas anfangen soll,

Was genau meinst Du damit? "Fragt das Protokoll?" Und wie kommst Du auf Pakete? Wenn man TCP macht, können einem die Pakete egal sein.

Ich hatte in meiner Antwort stillschweigend vorausgesetzt, daß es um einen namensbasierten VirtHost geht, obwohl die Angaben im OP für diese Einschätzung nicht ausreichen. Wenn ich deine Ausführungen richtig lese, gehst du ebenfalls davon aus, daß der OP einen namensbasierten VirtHost haben möchte

Nein, ich setzte das eben nicht vorraus, sondern IP-basierte virtuelle Hosts. Was ja hier auch sinnvoll ist, denn der OP will ja nur, dass auf einem anderen Port ein anderer Host erscheint, jedoch nicht, dass unter dem anderen Port verschiedene Hosts erreichbar sind.

Viele Grüße,
Christian