From 1a9e1d6e64bf03e0b11de335b7b06a73c4c0e8c3 Mon Sep 17 00:00:00 2001 From: David Cook Date: Fri, 14 Jun 2024 04:34:47 +0000 Subject: [PATCH] Bug 37087: Add TCP keepalive support to SIP server This change adds the ability to enable and configure TCP keepalive support for the SIP server using SIPconfig.xml. For the sake of backwards compatibility, it defaults to disabled and additional parameters default match typical kernel defaults. Technical detail can be found in the perldoc for C4/SIP/SIPserver.pm Test plan: 0. Apply the patch 1. koha-sip --restart kohadev 2. apt-get update && apt-get install tcpdump 3. In one window, run "tcpdump -A -n -v -i any 'port 6001'" 4. In another window, run the following: echo -e "9300CNterm1|COterm1|CPCPL|\r" | nc 127.0.0.1 6001 -v 5. Note in tcpdump output that after the initial flood of packets, nothing more is received 6. vi /etc/koha/sites/kohadev/SIPconfig.xml 7. In the "server-params" element, add attributes like the following: custom_tcp_keepalive='1' custom_tcp_keepalive_time='10' custom_tcp_keepalive_intvl='5' 8. koha-sip --restart kohadev 9. In one window, run "tcpdump -A -n -v -i any 'port 6001'" 10. In another window, run the following: echo -e "9300CNterm1|COterm1|CPCPL|\r" | nc 127.0.0.1 6001 -v 11. Note in tcpdump output that after the initial flood of packets, ACK packets are sent out every 10+ seconds for the idle connection --- C4/SIP/SIPServer.pm | 122 ++++++++++++++++++++++++++++++++- debian/templates/SIPconfig.xml | 3 + etc/SIPconfig.xml | 3 + 3 files changed, 127 insertions(+), 1 deletion(-) diff --git a/C4/SIP/SIPServer.pm b/C4/SIP/SIPServer.pm index d26f5e25e6b..ac4cef69cb5 100644 --- a/C4/SIP/SIPServer.pm +++ b/C4/SIP/SIPServer.pm @@ -8,7 +8,7 @@ use FindBin qw($Bin); use lib "$Bin"; use Net::Server::PreFork; use IO::Socket::INET; -use Socket qw(:DEFAULT :crlf); +use Socket qw(:DEFAULT :crlf TCP_KEEPIDLE TCP_KEEPINTVL IPPROTO_TCP); use Scalar::Util qw(blessed); require UNIVERSAL::require; @@ -90,6 +90,126 @@ push @parms, 'group=' . $>; # This is the main event. __PACKAGE__ ->run(@parms); +# +# Server +# + +=head3 options + +As per Net::Server documentation, override "options" to provide your own +custom options to the Net::Server* object. This allows us to use the Net::Server +infrastructure for configuration rather than hacking our own configuration into the +object. + +=cut + +sub options { + my $self = shift; + my $prop = $self->{'server'}; + my $template = shift; + + # setup options in the parent classes + $self->SUPER::options($template); + + $prop->{'custom_tcp_keepalive'} ||= undef; + $template->{'custom_tcp_keepalive'} = \$prop->{'custom_tcp_keepalive'}; + + $prop->{'custom_tcp_keepalive_time'} ||= undef; + $template->{'custom_tcp_keepalive_time'} = \$prop->{'custom_tcp_keepalive_time'}; + + $prop->{'custom_tcp_keepalive_intvl'} ||= undef; + $template->{'custom_tcp_keepalive_intvl'} = \$prop->{'custom_tcp_keepalive_intvl'}; +} + +=head3 post_configure_hook + +As per Net::Server documentation, this method validates our custom configuration. + +=cut + +sub post_configure_hook { + my $self = shift; + my $prop = $self->{'server'}; + if ( defined $prop->{'custom_tcp_keepalive'} && $prop->{'custom_tcp_keepalive'} ) { + + #NOTE: Any true value defined is forced to 1 just for the sake of predictability + $prop->{'custom_tcp_keepalive'} = 1; + } + + foreach my $key ( 'custom_tcp_keepalive_time', 'custom_tcp_keepalive_intvl' ) { + my $value = $prop->{$key}; + + #NOTE: A regex is used here to detect a positive integer, as int() returns integers but does not validate them + #NOTE: We do not allow zero as it can lead to problematic behaviour + if ( $value && $value =~ /^\d+$/ ) { + + #NOTE: Strictly, you must convert into an integer as a string will cause setsockopt to fail + $prop->{$key} = int($value); + } + } +} + +=head3 post_accept_hook + +This hook occurs after the client connection socket is created, which gives +us an opportunity to enable support for TCP keepalives using the SO_KEEPALIVE +socket option. + +By default, the kernel-level defaults (in seconds) are used. You can view these in the output of "sysctl -a": +net.ipv4.tcp_keepalive_intvl = 75 +net.ipv4.tcp_keepalive_time = 7200 + +Alternatively, you can use "custom_tcp_keepalive_time" and "custom_tcp_keepalive_intvl" to define +your own custom values for the socket. Note that these parameters are defined at the top server-level +and not on the listener-level. + +If you lower "custom_tcp_keepalive_time" below 75, you will also need to set "custom_tcp_keepalive_intvl". +The "tcp_keepalive_time" is the initial time used for the keepalive timer, and the "tcp_keepalive_intvl" +is the time used for subsequent keepalive timers. However, timers only send keepalive ACKs if the idle time +elapsed is greater than "tcp_keepalive_time". + +Thus, if "tcp_keepalive_time = 10" and "tcp_keepalive_intvl = 5", a keepalive ACK will be sent every 10 seconds +of idle time. If "tcp_keepalive_intvl = 10" and "tcp_keepalive_time = 5", a keepalive ACK will be sent after 5 +seconds of idle time, and the next keepalive ACK will be sent after 10 seconds of idle time. Generally speaking, +it's best to set "tcp_keepalive_time" to be higher than "tcp_keepalive_intvl". + +Reminder: once these settings are set on the socket, they are handled by the operating system kernel, and not +by the SIP server application. If you are having trouble with your settings, monitor your TCP traffic using +a tool such as "tcpdump" to review and refine how they work. + +=cut + +sub post_accept_hook { + my $self = shift; + my $prop = $self->{'server'}; + my $client = shift || $prop->{'client'}; + + my $tcp_keepalive = $prop->{custom_tcp_keepalive}; + my $tcp_keepalive_time = $prop->{custom_tcp_keepalive_time}; + my $tcp_keepalive_intvl = $prop->{custom_tcp_keepalive_intvl}; + + if ($tcp_keepalive) { + + #NOTE: set the SO_KEEPALIVE option to enable TCP keepalives + setsockopt( $client, SOL_SOCKET, SO_KEEPALIVE, 1 ) + or die "Unable to set SO_KEEPALIVE: $!"; + + if ($tcp_keepalive_time) { + + #NOTE: override "net.ipv4.tcp_keepalive_time" kernel parameter for this socket + setsockopt( $client, IPPROTO_TCP, TCP_KEEPIDLE, $tcp_keepalive_time ) + or die "Unable to set TCP_KEEPIDLE: $!"; + } + + if ($tcp_keepalive_intvl) { + + #NOTE: override "net.ipv4.tcp_keepalive_intvl" kernel parameter for this socket + setsockopt( $client, IPPROTO_TCP, TCP_KEEPINTVL, $tcp_keepalive_intvl ) + or die "Unable to set TCP_KEEPINTVL: $!"; + } + } +} + # # Child # diff --git a/debian/templates/SIPconfig.xml b/debian/templates/SIPconfig.xml index e79a2950b6c..f0a408c67b7 100644 --- a/debian/templates/SIPconfig.xml +++ b/debian/templates/SIPconfig.xml @@ -10,6 +10,9 @@ diff --git a/etc/SIPconfig.xml b/etc/SIPconfig.xml index 1816b91a234..a1432b13f34 100644 --- a/etc/SIPconfig.xml +++ b/etc/SIPconfig.xml @@ -19,6 +19,9 @@ user='koha' group='koha' pid_file='/var/run/sipserver.pid' + custom_tcp_keepalive='0' + custom_tcp_keepalive_time='7200' + custom_tcp_keepalive_intvl='75' /> -- 2.34.1