michaeluskov
michaeluskov

Reputation: 1828

IO::Socket::INET "send: Cannot determine peer address"

I have this Perl code snippet:

my $serverSocket = new IO::Socket::INET(
    LocalPort  => 53,
    Proto      => 'udp',
    ReuseAddr  => 1,
) or Error("Could't open server socket.");
print "Server started.\n";
my $clientQuery;
while (1) {
    $serverSocket->recv($clientQuery, 1024, 0);
    my $clientQueryParsed = IncomingDNSPacket::parseDNSPacket($clientQuery);
    my @questions = @{ $clientQueryParsed->{questions} };
    my @answers = map {getAnswer($_, $forwarder)} @questions;
    my $errorCode = @answers == 0 ? 3 : '';
    my $clientResponseParsed = {
        errorCode     => $errorCode,
        id            => $clientQueryParsed->{id},
        questions     => \@questions,
        answerRRs     => \@answers,
        additionalRRs => [],
        authorityRRs  => [],
    };
    my $clientResponse = OutgoingDNSPacket::makeAnswer(%$clientResponseParsed);
    $serverSocket->send($clientResponse, 0);
}

Sometimes $serverSocket->send($clientResponse, 0); returns this error: send: Cannot determine peer address. I tried to google it and found that this situation can be if socket was closed by remote host. But this is not TCP, this is UDP. Why do I get this error?

EDIT: Here's code of getAnswer:

sub getAnswer {
    my $question = shift;
    my $forwarder = shift;
    my @answer;
    @answer = $cache -> get($question);
    if (@answer == 0) {
        my $forwarderPacket = getAnswerFromForwarder($question);
        $cache -> add ($forwarderPacket);
        @answer = $cache -> get($question);
    }
    return @answer;
}

sub getAnswerFromForwarder {
    my $question = shift;
    my $forwQuery = OutgoingDNSPacket::makeQuestion(id => 0, questions => [$question]);
    my $forwSock = new IO::Socket::INET (
            PeerAddr => $forwarder,
            PeerPort => 53,
            Proto => 'udp'
        ) or Error("Couldn't create forwarder socket");
    $forwSock -> send($forwQuery, 0);
    my $select = new IO::Select;
    $select -> add ($forwSock);
    my @socks = $select -> can_read(2);
    if (@socks > 0) {
        my $forwAnswer;
        $forwSock -> recv($forwAnswer, 1024, 0);
        my $forwUnpacked = IncomingDNSPacket::parseDNSPacket($forwAnswer);
        return $forwUnpacked;
    } else {
        return {
            errorCode => 3,
            questions => [],
            answerRRs => [],
            authorityRRs => [],
            additionalRRs => []
            };
    }
}

$forwSock's send can be unsuccessful. And if it is unsuccessful, I can't send anything from $serverSocket. Why?

Upvotes: 0

Views: 2071

Answers (1)

pilcrow
pilcrow

Reputation: 58741

Short Answer

Your problem is that you are not handling $udpSocket->recv(...) errors, and this is confounding your later send.

Most likely the recv fails with ECONNREFUSED. You want to ignore this error and try again, and do the same for the send call, too.

Longer Answer

To clarify, your call to send does not return the error "Cannot determine peer address" — it croaks with that message.

IO::Socket remembers the remote peer address after a successful call to $io_socket->recv(). This peer is the implicit destination for the next call to $io_socket->send(). An unsuccessful recv, however, leaves no peer address available, and the following send simply croaks for want of a destination.

(Your code doesn't choke because the recv buffer, $clientQuery, is unmodified after a failed recv. You are processing the previously recvd packet again.)

Now, because this is UDP, the underlying error probably pertains to a previous failed send. Most likely you sent a datagram, an ICMP "dest unreach" came back sometime later, and the subsequent send/recv call on the socket reports this as ECONNREFUSED.

Pseudocoded Answer

use Errno;    # for %!
#use caution; this is untested and not thoroughly thought through

while (1) {
  my $ok = $udpSocket->recv($buf, $len, 0);
  if (! $ok) {
    redo if $!{ECONNREFUSED};  # Error from a previous UDP send
    die "recv: $!\n";
  }

  ...

  do {
    $ok = $udpSocket->send($buf, 0);
  } while (! $ok and $!{ECONNREFUSED});
}

Upvotes: 2

Related Questions