Socket Reset in Ruby

Wenn man sich mit der Socket Programmierung herumschlagen muss, steht man häufig vor dem Problem, dass man ganz gezielt ein bestimmtes Verhalten provozieren möchte. In diesem Fall geht es um eine Server, der die Verbindung zum Client beenden soll. Warten bis die letzten Daten verschickt worden sind ist in diesem Fall unerwünscht (Hard Close und kein Graceful Close).

Alle Beispiele hier sind mit der Scriptsprache Ruby umgesetzt.

Das Ganze ist gar nicht so einfach wie ich zunächst erwartet hatte, da ein einfaches socket.close nicht ausreicht.

Ein Beispiel, hier der Einfachheit mit einem Client und nicht mit einem Server:

require 'socket'
socket = TCPSocket.new('www.brainbombs.de', 80)
socket.close

Wir öffnen eine Verbindung zum Server und schließen diese dann gleich wieder. Im Netzwerk-Trace sieht man sehr schön, dass der Client zunächst ein FIN sendet und der Server dann entsprechend darauf antwortet. Dieses Vorgehen nennt man ein „Graceful Close“.

Netzwerk Trace für Beispiel 1: FIN

Netzwerk Trace - FIN

 

Um nun einen Reset zu erreichen müssen wir anders vorgehen. Hierfür müssen wir ein wenig am Socket rumschrauben. Ein Guter Start dafür scheint das Level SOL_SOCKET zu sein, da es den Wert SO_LINGER beinhaltet, mit dem wir Einfluss auf die Terminierung des Sockets nehmen können. Auch wenn ich hier mit Linux arbeite hat Microsoft zu diesem Thema dennoch eine sehr gute Dokumentation.

Zunächst gucken wir uns an wie die Standardwerte aussehen:

require 'socket'
socket = TCPSocket.new('www.brainbombs.de', 80)
val = socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER)
val = val.unpack "ii"
puts 'SO_Linger: ' + val.inspect
socket.close()

Mit getsockopt lesen wir die Socket Werte aus. Der Rückgabewert ist eine Linger Struktur (wieder gut in der MSDN erklärt).

Die Standardwerte sind: 0, 0

Der erste Wert definiert ob Linger ein oder ausgeschaltet ist (l_onoff). 0 steht für eingeschaltet und signalisiert ein Graceful Close. Wir warten also bis die Kommunikation abgeschlossen ist. Der zweite Wert ist in diesem Fall egal, da solange gewartet wird wie es notwendig ist.

Für ein Reset müssen wir den ersten Wert verändern um das Warten auszuschalten. Jetzt erhält der zweite Wert eine Bedeutung. 0 steht für keine Wartezeit. Eine andere Zahl kann als Timeout angegeben werden. In diesem Fall wartet die Applikation ob in dieser Zeit die Kommunikation abgeschlossen werden kann. Ist dies nicht der Fall wird ein Reset durchgeführt.

Da wir einen sofortigen Reset wünschen werden wir den Linger Wert auf folgendes setzen: 1,0

Das Script sieht nun so aus:

require 'socket'
socket = TCPSocket.new('www.brainbombs.de', 80)
linger = [1,0].pack('ii')
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, linger)
socket.close()

Nun werfen wir noch einen Blick auf den Netzwerk Trace dazu:

Netzwerk Trace zu Beispiel 2 - RST

Netzwerk Trace - RST

Wie wir sehen wird nun die Verbindung mit einem Reset (RST) beendet.