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

Signed-off-by: Tadeusz „tadzik” Sośnierz <tadeusz@sosnierz.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Katrin Fischer <katrin.fischer@bsz-bw.de>
This commit is contained in:
David Cook 2024-06-14 04:34:47 +00:00 committed by Katrin Fischer
parent db03a3a9d9
commit 5ca20665a3
Signed by: kfischer
GPG key ID: 0EF6E2C03357A834
3 changed files with 127 additions and 1 deletions

View file

@ -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
#

View file

@ -10,6 +10,9 @@
<server-params
min_servers='1'
min_spare_servers='0'
custom_tcp_keepalive='0'
custom_tcp_keepalive_time='7200'
custom_tcp_keepalive_intvl='75'
/>
<listeners>

View file

@ -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'
/>
<listeners>