Search code examples
perlsocketsudpconnection

Perl `send` pitfall with "connected" UDP `IO::Socket::INET`, ignoring destination address?


I'm debugging some odd issue when a packet sent via an INET UDP socket is not sent to the given destination address.

The documentation (perldoc -f send) says:

On unconnected sockets, you must specify a destination to send to, in which case it does a sendto(2) syscall.

That's all it says about the TO parameter an connected sockets.

However after hours of debugging it seems what the manual does not say is:

On connected sockets the TO parameter is being ignored.

So I'd like to know:

  • Are UDP sockets ever connected?
  • Is my finding correct that the TO parameter for send is being ignored when PeerAddr was specified when creating a IO::Socket::INET (thus "connecting" the socket)?
  • Shouldn't send return an error when the socket is "connected" and a destination address is specified, or is the implementation faulty (i.e.: Shouldn't a given destination override the socket's peer address when sending)? At least that would follow the principle of least surprise.

A kind of an example

As it was requested to provide an example, here is one:

#!/usr/bin/perl
use strict;
use warnings;

use IO::Socket;

sub _socket(;$$)
{
    my ($l, $p) = @_;
    my %params = ('Proto' => 'udp');

    @params{'LocalAddr', 'LocalPort'} = split(/:/, $l)
        if ($l);
    @params{'PeerAddr', 'PeerPort'} = split(/:/, $p)
        if ($p);
    return IO::Socket::INET->new(%params);
}

my $sock1 = _socket('localhost:1514');
my $sock2 = _socket('localhost:0', 'localhost:1514');
my $pkt;

while (defined(my $peer = $sock1->recv($pkt, 1000, 0))) {
    print "received $pkt\n";
    $sock2->send("response " . $pkt, 0, $peer);
}

Run it, and on the same host try netcat -u localhost 1514. Then enter one line like "junk", and you'll see that the sample program does not send back to the sender, but to itself, causing a loop.

Running the example under strace, I see that connect() is being used to "connect" $sock2.


Solution

  • See your system's man pages for details on systems calls such as connect and send.

    On POSIX systems, connect says

    For SOCK_DGRAM sockets, the peer address identifies where all datagrams are sent on subsequent send() functions, and limits the remote sender for subsequent recv() functions.

    So using connect overrides any destination address you might provide elsewhere.


    Shouldn't send return an error when the socket is "connected" and a destination address is specified

    The docs indicate error EISCONN "may or may not be returned for connection mode sockets" when "a destination address was specified and the socket is already connected."

    Now, it's not clear to me what makes a socket a "connection mode socket", but it appears to be based on protocol. But given an apparent lack of other guidance for connected UDP sockets, I find it reasonable to expect the same behaviour as for protocols like TCP. That is, the address is ignored or error EISCONN is returned.