Skip to content
Browse files

Merge pull request #15 from gittex/multi-socket

Add multi socket support
  • Loading branch information...
2 parents 18d1a36 + a61cbf8 commit 580a9f040e4a7ef8309d79f08b62f547f9603ec1 @apparentlymart apparentlymart committed Oct 3, 2013
Showing with 264 additions and 18 deletions.
  1. +129 −18 lib/DJabberd.pm
  2. +135 −0 t/new-connects-multiport.t
View
147 lib/DJabberd.pm
@@ -53,23 +53,45 @@ $SIG{USR2} = sub { Carp::cluck("USR2") };
sub new {
my ($class, %opts) = @_;
+
+ my $s2s_port = delete $opts{s2s_port};
+ my $c2s_port = delete $opts{c2s_port};
+ my $ssl_port = delete $opts{ssl_port};
my $self = {
'daemonize' => delete $opts{daemonize},
- 's2s_port' => delete $opts{s2s_port},
- 'c2s_port' => delete($opts{c2s_port}) || 5222, # {=clientportnumber}
'old_ssl' => delete $opts{old_ssl},
'vhosts' => {},
'fake_peers' => {}, # for s2s testing. $hostname => "ip:port"
'share_parsers' => 1,
'monitor_host' => {},
};
-
+
# if they set s2s_port to explicitly 0, it's disabled for all vhosts
# but not setting it means 5269 still listens, if vhosts are configured
# for s2s.
# {=serverportnumber}
- $self->{s2s_port} = 5269 unless defined $self->{s2s_port};
+ if(defined($s2s_port) && ref($s2s_port) eq 'HASH') { # hashref
+ $self->{s2s_port} = $s2s_port;
+ } elsif($s2s_port && !ref($s2s_port)) { # scalar
+ $self->{s2s_port}->{$s2s_port}++;
+ } elsif(!defined($s2s_port)) { # default
+ $self->{s2s_port}->{5269}++;
+ }
+
+ if(defined($c2s_port) && ref($c2s_port) eq 'HASH') { # hashref
+ $self->{c2s_port} = $c2s_port;
+ } elsif($c2s_port && !ref($c2s_port)) { # scalar
+ $self->{c2s_port}->{$c2s_port}++;
+ } else { # default
+ $self->{c2s_port}->{5222}++;
+ }
+
+ if(defined($ssl_port) && ref($ssl_port) eq 'HASH') {
+ $self->{ssl_port} = $ssl_port;
+ } elsif($ssl_port && !ref($ssl_port)) { # scalar
+ $self->{ssl_port}->{$ssl_port}++;
+ }
croak("Unknown server parameters: " . join(", ", keys %opts)) if %opts;
@@ -133,29 +155,88 @@ sub set_config_oldssl {
$self->{old_ssl} = as_bool($val);
}
+sub add_config_sslport {
+ my ($self, $val) = @_;
+ $self->{ssl_port}->{as_bind_addr($val)}++;
+}
+
+sub set_config_sslport {
+ my ($self, $val) = @_;
+ $self->{ssl_port} = {
+ as_bind_addr($val) => 1,
+ };
+}
+
+sub add_config_unixdomainsocket {
+ my ($self, $val) = @_;
+ $self->{unixdomainsocket}->{$val}++;
+}
+
sub set_config_unixdomainsocket {
my ($self, $val) = @_;
- $self->{unixdomainsocket} = $val;
+ $self->{unixdomainsocket} = {
+ $val => 1,
+ };
}
sub set_config_clientport {
my ($self, $val) = @_;
- $self->{c2s_port} = as_bind_addr($val);
+
+ $self->{c2s_port} = {
+ as_bind_addr($val) => 1,
+ };
+}
+
+sub add_config_clientport {
+ my ($self, $val) = @_;
+ # necessary because the default in the constructor
+ # will be 5222 and subsequent binds would fail
+ if($self->{c2s_port} && ref($self->{c2s_port}) && exists $self->{c2s_port}->{5222}) {
+ delete $self->{c2s_port}->{5222};
+ }
+ $self->{c2s_port}->{as_bind_addr($val)}++;
}
sub set_config_serverport {
my ($self, $val) = @_;
- $self->{s2s_port} = as_bind_addr($val);
+
+ $self->{s2s_port} = {
+ as_bind_addr($val) => 1,
+ };
+}
+
+sub add_config_serverport {
+ my ($self, $val) = @_;
+ # necessary because the default in the constructor
+ # will be 5269 and subsequent binds would fail
+ if($self->{s2s_port} && ref($self->{s2s_port}) && exists $self->{s2s_port}->{5269}) {
+ delete $self->{s2s_port}->{5269};
+ }
+ $self->{s2s_port}->{as_bind_addr($val)}++;
}
sub set_config_adminport {
my ($self, $val) = @_;
- $self->{admin_port} = as_bind_addr($val);
+ $self->{admin_port} = {
+ as_bind_addr($val) => 1,
+ };
+}
+
+sub add_config_adminport {
+ my ($self, $val) = @_;
+ $self->{admin_port}->{as_bind_addr($val)}++;
}
sub set_config_intradomainlisten {
my ($self, $val) = @_;
- $self->{cluster_listen} = $val;
+ $self->{cluster_listen} = {
+ $val => 1,
+ };
+}
+
+sub add_config_intradomainlisten {
+ my ($self, $val) = @_;
+ $self->{cluster_listen}->{$val}++;
}
sub set_config_pidfile {
@@ -294,13 +375,29 @@ sub run {
$self->start_cluster_server() if $self->{cluster_listen};
- $self->_start_server($self->{admin_port}, "DJabberd::Connection::Admin") if $self->{admin_port};
+ $self->_start_servers($self->{admin_port}, "DJabberd::Connection::Admin") if $self->{admin_port};
DJabberd::Connection::Admin->on_startup;
Danga::Socket->EventLoop();
unlink($self->{pid_file}) if (-f $self->{pid_file});
}
+# this will start up one server for each defined listening socket
+sub _start_servers {
+ my ($self, $localaddrs, $class) = @_;
+
+ my @addrs;
+ if(ref($localaddrs) eq 'HASH') {
+ @addrs = sort keys %$localaddrs;
+ } else {
+ @addrs = ($localaddrs);
+ }
+
+ foreach my $localaddr (@addrs) {
+ $self->_start_server($localaddr, $class);
+ }
+}
+
sub _start_server {
my ($self, $localaddr, $class) = @_;
@@ -326,7 +423,7 @@ sub _start_server {
Proto => IPPROTO_TCP,
Reuse => 1,
Listen => 10 )
- or $logger->logdie("Error creating socket: $@\n");
+ or $logger->logdie("Error creating socket for $localaddr: $@\n");
my $success = $server->blocking(0);
@@ -374,27 +471,36 @@ sub _start_server {
sub start_c2s_server {
my $self = shift;
- $self->_start_server($self->{c2s_port},
+ $self->_start_servers($self->{c2s_port},
"DJabberd::Connection::ClientIn");
- if ($self->{old_ssl}) {
- $self->_start_server(5223, "DJabberd::Connection::OldSSLClientIn");
+ # usually setting OldSSL 1 in the config file
+ # the server would magically listen on port 5223
+ # however this conflicts a litte with the new
+ # multi socket capabilities so add a new keyword
+ # SSLPort which works like e.g. ClientPort and
+ # have a fallback for old configs here
+ if ($self->{old_ssl} || $self->{ssl_port}) {
+ if(!$self->{ssl_port}) {
+ $self->{ssl_port}->{5223}++;
+ }
+ $self->_start_servers($self->{ssl_port}, "DJabberd::Connection::OldSSLClientIn");
}
if ($self->{unixdomainsocket}) {
- $self->_start_server($self->{unixdomainsocket}, "DJabberd::Connection::ClientIn");
+ $self->_start_servers($self->{unixdomainsocket}, "DJabberd::Connection::ClientIn");
}
}
sub start_s2s_server {
my $self = shift;
- $self->_start_server($self->{s2s_port},
+ $self->_start_servers($self->{s2s_port},
"DJabberd::Connection::ServerIn");
}
sub start_cluster_server {
my $self = shift;
- $self->_start_server($self->{cluster_listen},
+ $self->_start_servers($self->{cluster_listen},
"DJabberd::Connection::ClusterIn");
}
@@ -458,7 +564,12 @@ sub _load_config_ref {
my $key = lc $1;
my $val = $2;
my $inv = $plugin || $vhost || $self;
- my $meth = "set_config_$key";
+ my $meth = "add_config_$key";
+ if ($inv->can($meth)) {
+ $inv->$meth($val);
+ return;
+ }
+ $meth = "set_config_$key";
if ($inv->can($meth)) {
$inv->$meth($val);
return;
View
135 t/new-connects-multiport.t
@@ -0,0 +1,135 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More 'no_plan';
+use IO::Socket::INET6;
+use Devel::Peek;
+use lib 't/lib';
+require 'djabberd-test.pl';
+
+my @client_ports = qw(127.0.0.3:5222 127.0.0.1:11001 127.0.0.2:11001 [::1]:11001 11002 11003);
+my @server_ports = qw(127.0.0.3:5269 127.0.0.1:12001 127.0.0.2:12001 [::1]:12001 12002 12003);
+my @admin_ports = qw(127.0.0.3:5200 127.0.0.1:14001 127.0.0.2:14001 [::1]:14001 14002 14003);
+
+my $vhost = DJabberd::VHost->new(
+ server_name => 'jabber.example.com',
+ s2s => 0,
+ plugins => [
+ DJabberd::Authen::AllowedUsers->new(
+ policy => "deny",
+ allowedusers => [qw(tester)]
+ ),
+ DJabberd::Authen::StaticPassword->new(
+ password => "password"
+ ),
+ DJabberd::PresenceChecker::Local->new(),
+ DJabberd::Delivery::Local->new(),
+ DJabberd::Delivery::S2S->new(),
+ DJabberd::RosterStorage::InMemoryOnly->new(),
+ ],
+);
+
+my $server = DJabberd->new();
+
+foreach my $client_port (@client_ports) {
+ $server->add_config_clientport($client_port);
+}
+
+foreach my $server_port (@server_ports) {
+ $server->add_config_serverport($server_port);
+}
+
+foreach my $admin_port (@admin_ports) {
+ $server->add_config_adminport($admin_port);
+}
+
+$server->add_vhost($vhost);
+
+my $childpid = fork;
+if (!$childpid) {
+ $0 = "[djabberd]";
+ $server->run;
+ exit 0;
+}
+
+my $err = sub {
+ kill 9, $childpid;
+ die $_[0];
+};
+
+my $conn;
+
+foreach my $addr (@client_ports, @server_ports, @admin_ports) {
+ foreach my $try ( 1 .. 3 ) {
+ unless ($addr =~ /:\d+$/) {
+ $addr = '[::1]:'.$addr;
+ }
+ diag("Connecting to $addr ...");
+ $conn = IO::Socket::INET6->new(
+ PeerAddr => $addr,
+ Timeout => 1,
+ );
+ last if $conn;
+ sleep 1;
+ }
+ $err->("Can't connect to server")
+ unless $conn;
+
+ $conn->close;
+}
+
+END {
+ kill 9, $childpid;
+}
+
+foreach my $client_port (@client_ports) {
+ my $max_connections = 200;
+ foreach my $n (1 .. $max_connections) {
+
+ my @events;
+ my ($handler, $p);
+ $handler = DJabberd::TestSAXHandler->new(\@events);
+ $p = DJabberd::XMLParser->new( Handler => $handler );
+
+ my $get_event = sub {
+ while (! @events) {
+ my $byte;
+ my $rv = sysread($conn, $byte, 1);
+ $p->parse_more($byte);
+ }
+ return shift @events;
+ };
+
+ my $get_stream_start = sub {
+ my $ev = $get_event->();
+ die unless $ev && $ev->isa("DJabberd::StreamStart");
+ return $ev;
+ };
+
+ diag("connect $n/$max_connections\n") if $n % 50 == 0;
+ unless ($client_port =~ /:\d+$/) {
+ $client_port = '[::1]:'.$client_port;
+ }
+ $conn = IO::Socket::INET6->new(
+ PeerAddr => $client_port,
+ Timeout => 1,
+ );
+
+ print $conn qq{
+ <stream:stream
+ xmlns:stream='http://etherx.jabber.org/streams' to='jabber.example.com'
+ xmlns='jabber:client'>
+ };
+
+ my $ss = $get_stream_start->();
+ die unless $ss && $ss->id;
+
+ if ($n == 1) {
+ ok($ss, "got a stream back");
+ ok($ss->id, "got a stream id back");
+ }
+
+ $p->finish_push;
+ $conn->close;
+ }
+}

0 comments on commit 580a9f0

Please sign in to comment.
Something went wrong with that request. Please try again.