Robert S. Barnes
Robert S. Barnes

Reputation: 40568

Select returning 0 on a closed SCTP socket

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.

Upvotes: 3

Views: 1831

Answers (1)

mark4o
mark4o

Reputation: 60923

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.

Upvotes: 3

Related Questions