1

I have a program (too complex to present here) that uses an UDP socket created by IO::Socket::INET->new() with both, Local... and Peer... address and port. So expectation is that a plain $sock->send($data, $flags) will send $data to the peer address specified when creating the socket. That seems to work.

However when I try to send an individual packet to a different destination by using $sock->send($data, $flags, $dest), $dest seems to be ignored (and the packet is being sdent to the socket's peer address). I added lots of debug messages, and the parameter $dest is passed correctly to send, but strace shows that sendto() is called with NULL for sockaddr and 0 for socklen.

perldoc -f send doesn't help me. So why is the destination address ignored?

As requested, here's the (somewhat extra verbose) send_packet code:

$RE_saddr = qr/^(.+):(\d+)$/;

sub send_packet($$;$)
{
    my ($sock, $packet, $dest) = @_;
    my @params = ($packet, 0);

    if (defined($dest)) {
        if (my ($addr, $port) = $dest =~ $RE_saddr) {
            if (my $addr_bin = inet_aton($addr)) {
                if (defined($dest = sockaddr_in($port, $addr_bin))) {
                    push(@params, $dest);
                } else {
                    warn "bad address or socket for $addr:$port";
                }
            } else {
                warn "bad address $addr";
            }
        } else {
            warn "bad destination address $dest";
        }
    }
    return 1
        if ($sock->send(@params));
    return undef;
}

So obviously the destination is passed as "host : port" (one string without blanks (blanks are due to markup deficits here)).

U. Windl
  • 3,480
  • 26
  • 54
  • 1
    Can you at least add the code that shows how you construct `$dest`? – Stefan Becker Mar 14 '19 at 12:27
  • An observation: if you use `Peer*` to create an `IO::Socket::INET` socket then `connect()` is called. This seems to affect the behaviour of `send()`, i.e. it no longer passes on the `TO` parameter to `sendto()`. I would suggest to study the Perl source code to discover the reason why. – Stefan Becker Mar 14 '19 at 14:08
  • Good point, but strictly speaking `connect()` does not make sense for UDP. At least it's poor documentation on the Perl side. – U. Windl Mar 14 '19 at 14:11
  • F.ex. [IO::Socket::send()](https://github.com/Perl/perl5/blob/blead/dist/IO/lib/IO/Socket.pm#L276) ignores `$peer` when `peername($sock)` returns a peer address. That will be the peer address set by `connect()`. – Stefan Becker Mar 14 '19 at 14:16
  • As said before: The documentation is incomplete; it says: "On unconnected sockets, you must specify a destination to send to, in which case it does a sendto(2) syscall." That logical: You need a destination address. But my assumption was that a third (forth) argument will automatically call `sendto()` with that parameter, temporarily overriding the socket's peer address. Upvote my question anyone? – U. Windl Mar 14 '19 at 14:26
  • You are quoting from the documentation of `CORE::send()`, which behaves exactly like [the documentation describes](https://github.com/Perl/perl5/blob/blead/pp_sys.c#L2007). But your question describes the behaviour of `IO::Socket::send()` which is an [OO wrapper](https://github.com/Perl/perl5/blob/blead/dist/IO/lib/IO/Socket.pm#L276) around `CORE::send()` – Stefan Becker Mar 14 '19 at 15:54
  • @Stefan Becker: I failed to find the corresponding documentation: `IO::Socket::INET` definitely doesn't document it. – U. Windl Mar 16 '19 at 22:33

1 Answers1

2

If you use Peer* when creating an IO::Socket::INET then connect() will be called, which sets the peername of the socket. On such a socket you'll never have to specify the remote socket address, because it has a default one.

IO::Socket::send() has a "feature": it ignores the TO parameter when the socket has a valid peername:

 my $r = defined(getpeername($sock))
     ? send($sock, $_[1], $flags)
     : send($sock, $_[1], $flags, $peer);

That change was introduced in IO 1.12, which unfortunately predates the history of the current git repository:

Modified IO::Socket::send so not to pass 4 arguments to send if the socket is connected

If you don't like this behaviour, you'll have to use raw CORE::send() instead, i.e.:

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

use IO::Socket::INET;
use Socket qw(pack_sockaddr_in);

my $client = IO::Socket::INET->new(
    PeerAddr  => '127.0.0.1',
    PeerPort  => 2000,
    Proto      => 'udp',
) or die "client socket: $!\n";

my $addr = pack_sockaddr_in(2000, inet_aton('127.0.0.1'));

$client->send('ABCD', 0)
    or die "IO::Socket::send() no addr: $!\n";
$client->send('ABCD', 0, $addr)
    or die "IO::Socket::send() with addr: $!\n";
send($client, 'ABCD', 0, $addr)
    or die "send() with addr: $!\n";

exit 0;

Test run:

$ strace -e sendto,connect,socket perl dummy.pl
socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_UDP) = 4
connect(4, {sa_family=AF_INET, sin_port=htons(2000), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
sendto(4, "ABCD", 4, 0, NULL, 0)        = 4
sendto(4, "ABCD", 4, 0, NULL, 0)        = 4
sendto(4, "ABCD", 4, 0, {sa_family=AF_INET, sin_port=htons(2000), sin_addr=inet_addr("127.0.0.1")}, 16) = 4

The strace from my Linux machine indicates that send() is mapped to sendto(), because the Perl code does call send() and sendto().


UPDATE: I've created the upstream ticket #133936.

Stefan Becker
  • 5,695
  • 9
  • 20
  • 30
  • So I would call the IO::Socket version "poorly documented object-oriented confusion" ;-) Thanks for the solution! Why didn't they use `my $r = defined($peer) ? send($sock, $_[1], $flags, $peer) : send($sock, $_[1], $flags);`? – U. Windl Mar 15 '19 at 00:38
  • That won't help because the `IO::Socket::send()` implementation starts with `my $peer = $_[3] || $sock->peername;`. With your suggestion the 3-parameter variant of `CORE::send()` would only be used for unconnected sockets, i.e. it would always fail :-( – Stefan Becker Mar 15 '19 at 06:00
  • 1
    FYI: I wrote a comment on [GitHub Dual-Life/IO PR#17](https://github.com/Dual-Life/IO/issues/17#issuecomment-473189242). – Stefan Becker Mar 15 '19 at 07:47
  • FYI: I've created [RT Ticket #133936](https://rt.perl.org/Ticket/Display.html?id=133936) – Stefan Becker Mar 17 '19 at 10:32