views:

224

answers:

1

This is related to the question: SCTP with Multihoming as a Drop In Replacement for TCP

I have a simple echo client / concurrent server app that ran perfectly fine using TCP. I could pipe a file to stdin on the client and the client would receive all the data back, call select which would return 1 indicating the socket was readable, then the call to read would return 0 indicating EOF / FIN. The client would then exit. All is good.

However, the identical apps over SCTP cause problems. The only change made was from IPPROTO_TCP to IPPROTO_SCTP. The server forks, echo's back the data, the child exits and is reaped by the parent. The client receives all the data, but afterwards select keeps returning 0 descriptors ready ( without the time out I added it would hang forever ).

What in the world is going on?

Here is what the code for the client looks like:

#!/usr/bin/perl -w
use strict;
use Socket;

# forward declaration
sub logmsg;

my $remote = shift || "localhost";
my $port = 9877;

($port) = $port =~ /^(\d+)$/ or die "invalid port";

my $iaddr = inet_aton($remote) || die "no host: $remote";
my $paddr = sockaddr_in($port, $iaddr);
my $proto = getprotobyname('sctp');

my $sockfd;

socket($sockfd, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
connect($sockfd, $paddr) || die "connect: $!";

str_cli($sockfd);

exit(0);

#----- subs down here ------#

sub str_cli {
    my $sockfd = shift;

    my ($n, $buff, $stdineof, $s);
    my $rin = '';

    $stdineof = 0;
    while ( 1 ) {
        if ($stdineof == 0) {
            vec($rin, fileno(STDIN), 1) = 1;
        }
        vec($rin, fileno($sockfd), 1) = 1; 

        my $nfound = select($rin, undef, undef, 1.0);
        if ($nfound < 0) {
            next if $!{EINTR};
            die "select: $!";
        } else { print "\$nfound == $nfound\n"; }

        if (vec($rin, fileno($sockfd), 1) == 1) { # socket readable
            print "trying to read from sockfd\n";
            $n = sysread($sockfd, $buff, 1024);
            if (!defined($n) || $n < 0) {
                # resume if sysread() returned because a signal was received
                next if $!{EINTR};
                die "sysread: $!";
            } elsif($n == 0) {
                if ($stdineof == 1) { return; } # normal termination
                else { die "str_cli: server terminated prematurely"; }
            }
            writen(*STDOUT, $buff);
        }

        if (vec($rin, fileno(STDIN), 1) == 1) { # stdin readable
            $n = sysread(STDIN, $buff, 1024);
            if (!defined($n) || $n < 0) {
                # resume if sysread() returned because a signal was received
                next if $!{EINTR};
                die "sysread: $!";
            } elsif($n == 0) {
                $stdineof = 1;
                if (!defined($s = shutdown($sockfd, SHUT_WR)) 
                    || $s == 0) { die("shutdown: $!"); }
                vec($rin, fileno(STDIN), 1) = 0;
                next;
            }
            writen($sockfd, $buff);
        }
    }
}

sub writen {
    my ($connfd, $buff) = @_;
    my $nleft = length($buff);
    my $total = 0;
    my $nwritten = 0;
    while ($nleft) {
        if (($nwritten = syswrite($connfd, $buff, $nleft, $total)) <= 0) {
            # resume if syswrite() returned because a signal was received
            # 0 indicates an error in this context
            next if $!{EINTR};
            die "syswrite: $!";
        }
        $nleft -= $nwritten;
        $total += $nwritten;
    }
}

sub logmsg { print "$0 $$: @_ at ", scalar localtime, "\n" }

Remember, this works perfectly over TCP. I'm on Ubuntu 9.04 with all the needed sctp packages installed.

+2  A: 

You are relying on TCP's half-closed state, which is not available in SCTP.

Using TCP, shutdown($sockfd, SHUT_WR) will send a packet with the FIN bit set, closing the sending side of the connection but still allowing new data in the receiving direction. SCTP does not have such a half-closed state, and this call will initiate the SCTP shutdown sequence, in which the whole connection is closed. Further details can be found here.

mark4o
So is there a way in SCTP to tell a socket is closed other than application protocol level notifications?
Robert S. Barnes
If you want to send an indication that the you are done sending, but still want to keep the connection up to receive a response, and do not want to do this at the application layer, you can send your data in a separate SCTP stream. The receiver can then react to the closing of that stream and send back a response in a different stream.
mark4o
@mark4o: You'd need to use the sctp specific api calls to do that, correct? Is there a way using the standard sockets api? Just curious what can be done when porting an existing TCP app in which you don't want to introduce the sctp specific api calls.
Robert S. Barnes
If you don't want to use the sctp-specific calls like `sctp_sendmsg` (which takes the stream number as a parameter), you can do the same thing using `sendmsg`. This is technically a standard sockets api function, however the ancillary data that you pass to `sendmsg` would be sctp-specific.
mark4o
That's what I thought. I was just looking back at the link you provided re: `shutdown`. My understanding is that it closes the association but not the socket, and that's why select doesn't return any ready descriptors the way it does with TCP?
Robert S. Barnes
I think that your select would return if the other side had initiated the shutdown sequence, but in this case you are initiating it (and apparently it doesn't think that you need to be told about the shutdown that you initiated). With TCP each side shuts down its send direction independently, but with SCTP there is only one shutdown sequence that applies to both directions. You could request the SCTP_ASSOC_CHANGE or SCTP_SHUTDOWN_EVENT notification but your server still won't be able to send data after you shut down the association.
mark4o