ubuntuusers.de

Benötige Hilfe mit openbsd-inetd

Status: Gelöst | Ubuntu-Version: Server 14.04 (Trusty Tahr)
Antworten |

Rabenschwinge

Anmeldungsdatum:
22. Dezember 2007

Beiträge: 129

Wohnort: Düsseldorf

Hallo,

ich habe einen kleinen Dienst in PHP geschrieben, der Nachrichten über das lokale Netzwert mithilfe eines UDP Broadcasts verteilt. Das funktioniert auch, die Pakete kommen bei allen Zielservern an, und zwar jeweils genau einmal.

Das Problem ist, dass das Skript, dass die Nachricht empfangen soll nicht einmal, sondern dutzende von malen gestartet wird, Ewigkeiten braucht und ich weiß nicht wieso. Ich habe Testweise ein Dummy-Skript geschrieben und mit tcp-Wrapper in den inetd gehängt. (Der tcp Wrapper ist nicht das Problem, ich habe auch schon versucht ihn wegzulassen.)

$ grep testservice /etc/inetd.conf /etc/services
/etc/inetd.conf:testservice	dgram	udp	nowait	user	/usr/sbin/tcpd /home/user/test.php
/etc/services:testservice	2802/udp

test.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/usr/bin/php
<?php
$message = trim(fgets(STDIN));
// ich habe es auch schon mit fread(STDIN, 1024) versucht, dasselbe Problem

fclose(STDIN);
// daran liegts nicht, es macht keinen Unterschied, wenn man es weglässt

$fp = fopen("/var/tmp/test.log", "a");
fwrite($fp, $message . "\n");
fclose($fp);
?>

Am Ende habe ich dann ein ganzes Rudel gleichartiger Meldungen in /var/log/syslog, obwohl laut tcpdump nur ein einziges Paket eingegangen ist:

May  3 14:24:51 server test.php[28879]: connect from 10.123.9.1 (10.123.9.1)
May  3 14:24:51 server test.php[28861]: connect from 10.123.9.1 (10.123.9.1)
(...)

Die genaue Anzahl der Meldungen ist unterschiedlich. Von einem Falle weiß ich das es 63 waren. Das sind sehr viele, aber dann doch weniger als es mögliche Adressen in dem Netzwerk (10.112.0.0/12, broadcast 10.127.255.255) gibt. Die Skripte hängen hinterher, aber ob sie bei dem fgets oder dem fopen hängen weiß ich nicht. Wieso startet inetd so viele Instanzen des Skriptes, warum nicht nur eine? In dem Logile tauchen hinterher jede Menge Leerzeilen auf und vereinzelt eine mit einer Nachricht. In der Prozessliste stehen die jeweiligen Skripte jedoch weiterhin. (Also es steht weiterhin "/home/user/test.php" da, nicht aber der dadurch aufgerufene PHP Prozess.)

misterunknown Team-Icon

Ehemalige
Avatar von misterunknown

Anmeldungsdatum:
28. Oktober 2009

Beiträge: 4403

Wohnort: Sachsen

Rabenschwinge schrieb:

Die Skripte hängen hinterher, aber ob sie bei dem fgets oder dem fopen hängen weiß ich nicht.

Finde es raus. strace ist dein Freund, außerdem auch lsof. Prinzipiell gibt es 2 Ansätze: Wenn die Skripte auch ohne inetd lange laufen, kannst du es direkt mit strace starten:

1
strace skript.php -irgendwelche -parameter

Falls es nicht so ist, kannst du dich auch im nachhinein zuschalten:

1
strace -p <PID>

Dann siehst du, was genau das Skript für SysCalls macht. Wenn es beispielsweise bei "read(3, ...)" hängt, weißt du, dass es auf den File-Deskriptor 3 wartet. Was sich dahinter verbirgt, siehst du mit lsof:

1
lsof -p <PID>

Dort werden alle offenen FDs aufgelistet. Ein FD kann vieles sein: eine offene Datei, ein RAM-Segment, eine Netzwerkverbindung...

Wieso startet inetd so viele Instanzen des Skriptes, warum nicht nur eine?

Das kannst du evtl. mit einem strace von dem inetd herausfinden. Eventuell hilft auch ein tcpdump, um zu sehen, was alles ankommt.

In dem Logile tauchen hinterher jede Menge Leerzeilen auf und vereinzelt eine mit einer Nachricht. In der Prozessliste stehen die jeweiligen Skripte jedoch weiterhin. (Also es steht weiterhin "/home/user/test.php" da, nicht aber der dadurch aufgerufene PHP Prozess.)

Ich würde im Skript weitere Ausgaben einbauen. Beispielsweise eine Abfrage ob $message leer ist, um darauf zu schließen, warum das so sein könnte.

Rabenschwinge

(Themenstarter)

Anmeldungsdatum:
22. Dezember 2007

Beiträge: 129

Wohnort: Düsseldorf

Vielen Dank für die Ratschläge, das war in der Tat sehr hilfreich, auch wenn ich das Problem noch nicht gelöst habe. Sowie ich das Problem gelöst habe poste ich noch mal.

Folgendes habe ich herausgefunden: In der Prozessliste stehen einige Prozesse mit "/usr/bin/php /home/user/test.php", das sind die eigentlichen PHP Prozesse und die Terminieren auch irgendwann - logisch sonst würde in der Ausgabedatei ja auch nichts stehen. Es dauert nur eine ganze Weile und anscheinend hängen die bei dem fgets... strace zeigt mit eine ganze Weile "restart_syscall" an, danach geht es zügig weiter mit close(0) - also das fclose(STDIN) und so weiter. Möglich, das die Prozesse nur deswegen terminieren, weil die irgendwo ein Timeout eingebaut ist, weil bei dem fgets einfach nichts kommt, auch kein EOF.

Diejenigen Prozesse, die als "/home/user/test.php" in der Prozessliste stehen sind in /usr/sbin/tcpd Prozesse. Die hängen bei einem "recvfrom" Systemcall. Wenn man tcpd weglässt ändert das nichts am Verhalten der eigentlichen PHP Prozesse, die tcpd Prozesse stehen einfach nicht mehr da. Nach wie vor geöffnet in lsof sind UDP-Ports, das heißt es versucht anscheinend aus dem Netz zu lesen weil es das EOF nicht mitbekommen hat oder so etwas.

Ich hatte schon gemutmaßt, dass es irgendetwas damit zu tun hat, dass es sich um einen Broadcast handelt, zumal das Interface zwei logische IP Adressen im selben Netzwerk hat. Leider ist dem nicht so. Wenn ich nur ein Paket ganz gezielt an eine Adresse versende tritt das Problem ebenso auf. Mit tcpdump habe ich ja auch zuvor schon mitgeschnitten und es mir hinterher in Wireshark angeguckt. Es war immer nur genau ein UDP Paket, das reingegangen ist, und damit war alles in Ordnung.

Dazu fällt mir jetzt nichts mehr, außer, dem Vorschlag zu folgen, ein strace auf den inetd-Prozess zu machen, oder eine andere inetd-Implementierung zu verwenden.

misterunknown Team-Icon

Ehemalige
Avatar von misterunknown

Anmeldungsdatum:
28. Oktober 2009

Beiträge: 4403

Wohnort: Sachsen

Rabenschwinge schrieb:

Es dauert nur eine ganze Weile und anscheinend hängen die bei dem fgets... [...] Möglich, das die Prozesse nur deswegen terminieren, weil die irgendwo ein Timeout eingebaut ist, weil bei dem fgets einfach nichts kommt, auch kein EOF.

Das ist durchaus möglich. Das könntest du umgehen, indem du deiner Nachricht immer einen Zeilenumbruch anhängst, denn fgets liest zeilenweise ein.

Die hängen bei einem "recvfrom" Systemcall.

Und von wo will er lesen? (findest du mit lsof raus)

Nach wie vor geöffnet in lsof sind UDP-Ports, das heißt es versucht anscheinend aus dem Netz zu lesen weil es das EOF nicht mitbekommen hat oder so etwas.

Die Verbindungen bleiben unter Umständen so lange bestehen, wie der Prozess, wenn sie nicht explizit geschlossen werden. Das sollte aber nicht das Problem sein.

Dazu fällt mir jetzt nichts mehr, außer, dem Vorschlag zu folgen, ein strace auf den inetd-Prozess zu machen, oder eine andere inetd-Implementierung zu verwenden.

Ich denke nicht, dass es an der inetd-Implementierung liegt.

Rabenschwinge

(Themenstarter)

Anmeldungsdatum:
22. Dezember 2007

Beiträge: 129

Wohnort: Düsseldorf

Am Ende der Nachrichten ist sowieso ein Zeilenumbruch.

Mittlerweile habe ich fast den Verdacht, dass das kein Fehler ist, sondern so beabsichtigt. Es ist normal, das mehrere Prozesse gestartet werden, nur nicht ganz so viele und die sollten sich auch nicht blockieren. PHP kommt nur nicht gut mit dem Verhalten klar. Dasselbe Problem ergibt sich bei inetutils-inetd.

Ich habe Nachrichten mit folgendem Befehl versandt:

sudo sendip -p ipv4 -is 10.123.2.1 -p udp -us 3912 -ud 2802 -d Hallo 10.123.2.1

(in dem Falle ist natürlich kein Zeilenumbruch am Ende)

Selbst wenn ich das Skript auf den einzigen Befehl: fread(STDIN, 1024) reduziere, tritt das Problem weiter auf.

Also habe ich das ganze noch einmal in C geschrieben:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <fcntl.h>

int main() {
        char buffer[1024];
        ssize_t length = 0;
        int fd;

        length = read(0, buffer, 1024);
        if (length < 0) return 1;
        fd = open("/var/tmp/test.log", O_WRONLY);
        if (!fd) return 1;
        lseek(fd, 0, SEEK_END);
        write(fd, buffer, length);
        close(fd);

        return 0;
}

Ergebnis: Auch da werden mehrere Prozesse gestartet. Die meisten blockieren bei dem "read", allerdings bearbeitet einer der Prozesse die Anfrage und steigt sofort wieder aus. Wenn man eine weitere Anfrage startet, wird einer der Prozesse, die schon laufen genutzt und kein neuer Prozess gestartet. Es wird von vorneherein nur ein gutes dutzend Prozesse gestartet.

Rabenschwinge

(Themenstarter)

Anmeldungsdatum:
22. Dezember 2007

Beiträge: 129

Wohnort: Düsseldorf

Ich habe zumindest ein Work-Around für das Problem gefunden:

1. Auf keinen Fall "nowait" benutzen in den Einstellungen des Dienstes in der /etc/inetd.conf verwenden. Dann werden in PHP nämlich mehrere Prozesse gestartet und aus unerfindlichen Gründen ist das bei PHP problematisch. (Eigentlich sollte es so sein, dass der erste Prozess, der versucht zu lesen den eingegangenen String verwendet und alle anderen blockieren bis was kommt, das funktioniert aber nicht richtig.)

2. Folglich muss über eine while Schleife gelesen werden, falls noch ein Paket reinkommt während der Prozess arbeitet.

/etc/inetd.conf:

testservice	dgram	udp	wait	testuser	/usr/sbin/tcpd /home/testuser/test.php

/home/testuser/test.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/usr/bin/php
<?php
while (($message = fread(STDIN, 1024)) !== false) {
	$message = trim($message);
	$fp = fopen("/var/tmp/test.log", "a");
	fwrite($fp, $message . "\n");
	fclose($fp);
	exit(0);
}
?>
Antworten |