Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

getsockopt truncating values to 256 bytes ?

by Yaribz (Beadle)
on May 20, 2022 at 13:42 UTC ( #11144024=perlquestion: print w/replies, xml ) Need Help??

Yaribz has asked for the wisdom of the Perl Monks concerning the following question:

Hello, I'm trying to retrieve the full TCP_INFO socket structure on a macOS system using Perl.

I have no problem retrieving this structure on other systems (Linux, FreeBSD...) using the getsockopt function, but on these systems the structure is less than 256 bytes long.
On macOS, this structure is supposed to be 292 bytes long (296 with padding), as one can see here. When I try to retrieve this structure from Perl using getsockopt on macOS 12.0.1 (Darwin 21.1.0), I only get 256 bytes of data: the last values are missing from the structure.

Is it due to this code from Perl core, which seems to limit getsockopt results to 256 bytes ? And in this case, what can I do to retrieve the full TCP_INFO structure ?

Here is a code snippet to reproduce the problem on macOS:

use Socket qw'PF_INET SOCK_STREAM IPPROTO_TCP inet_aton sockaddr_in'; my $TCP_INFO = ($^O eq 'darwin' ? 0x200 : eval { Socket::TCP_INFO() }) or die "This system doesn't support the TCP_INFO structure.\n"; my ($testHost,$testPort)=('perl.org',443); socket(my $sock, PF_INET, SOCK_STREAM, IPPROTO_TCP) or die "Could not create socket - $!\n"; my $iaddr=inet_aton($testHost); my $paddr=sockaddr_in($testPort,$iaddr); connect($sock,$paddr) or die "Failed to connect to $testHost:$testPort - $!\n"; my $tcpInfoData=getsockopt($sock,IPPROTO_TCP,$TCP_INFO) or die "Error while calling getsockopt - $!\n"; my $tcpInfoLength=length($tcpInfoData); if($tcpInfoLength < 256) { print "This system doesn't have a TCP_INFO structure large enough to + reproduce the problem.\n"; }elsif($tcpInfoLength == 256) { print "The TCP_INFO structure seems to be truncated to 256 bytes on +this system.\n"; }else{ print "This system doesn't seem to be affected by the problem.\n"; }

Thanks !

Replies are listed 'Best First'.
Re: getsockopt truncating values to 256 bytes ?
by Fletch (Bishop) on May 20, 2022 at 14:34 UTC

    At first glance that looks to be what's happening (presuming my rusty C is right) and I can duplicate with your script on my mac. This is probably something you should open an issue for (see perlbug); my guess is that hard-coded 256 should really be changed to maybe sizeof(struct tcp_info) (but again, rusty).

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      I can duplicate with your script on my mac

      Thanks for taking time to reproduce.

      This is probably something you should open an issue for

      I was hoping I was wrong and missed something, but I guess I should indeed... I just opened this issue.

      my guess is that hard-coded 256 should really be changed to maybe sizeof(struct tcp_info)

      As far as I understand this code is responsible for all getsockopt calls, not just the calls for the TCP_INFO structure (for example on macOS one can also retrieve the TCP_CONNECTION_INFO structure using getsockopt...). So unless we are sure the TCP_INFO structure will always be the largest one, it would require checking the size of the requested structure, i.e. translating the getsockopt macro name to the actual C structure name to be able to compute the length, which seems quite complicated...

      I guess adding an optional parameter to the getsockopt function to allow the calling code to override the default 256 max size would be ok though.
      Or the ugly way: simply doubling current limit and hoping all structures retrievable with getsockopt will always have a size below 512 ;)

Re: getsockopt truncating values to 256 bytes ?
by stevieb (Canon) on May 21, 2022 at 01:27 UTC

    This is a very interesting find. Please do keep us updated, and give a shout out if you want help further troubleshooting it.

    On:

    steve@maezi ~/scratch >system_profiler SPSoftwareDataType Software: System Software Overview: System Version: macOS 12.2.1 (21D62) Kernel Version: Darwin 21.3.0

    I experience the same issue:

    steve@maezi ~/scratch >perl 256.pl The TCP_INFO structure seems to be truncated to 256 bytes on this syst +em.

      Thanks. I guess what will happen is that the hardcoded limit will just be increased in Perl core code.

      However I wanted to be able to retrieve the full TCP_INFO structure even when using older Perl versions... So regarding my original question "what can I do to retrieve the full TCP_INFO structure ?", here is the workaround I came up with, which seems to be working fine according to first results:

      use constant { DARWIN_SYS_getsockopt => 118, # from bsd/kern/syscalls.master GETSOCKOPT_MAXLEN => 512, # enough for now I guess... }; sub darwin_getsockopt { my $optval = "\0" x GETSOCKOPT_MAXLEN; my $optlen=pack('i',GETSOCKOPT_MAXLEN); my $rv=syscall(DARWIN_SYS_getsockopt,fileno($_[0]),$_[1],$_[2],$optv +al,$optlen); return $rv < 0 ? undef : substr($optval,0,unpack('i',$optlen)); }

      Here is a revised version of the test program, using the workaround when run on macOS:
      use Socket qw'PF_INET SOCK_STREAM IPPROTO_TCP inet_aton sockaddr_in'; use constant { DARWIN_TCP_INFO => 0x200, # from bsd/netinet/tcp.h DARWIN_SYS_getsockopt => 118, # from bsd/kern/syscalls.master GETSOCKOPT_MAXLEN => 512, # enough for now I guess... }; sub darwin_getsockopt { my $optval = "\0" x GETSOCKOPT_MAXLEN; my $optlen=pack('i',GETSOCKOPT_MAXLEN); my $rv=syscall(DARWIN_SYS_getsockopt,fileno($_[0]),$_[1],$_[2],$optv +al,$optlen); return $rv < 0 ? undef : substr($optval,0,unpack('i',$optlen)); } my $GETSOCKOPT_FUNC = $^O eq 'darwin' ? \&darwin_getsockopt : \&CORE:: +getsockopt; my $TCP_INFO = ($^O eq 'darwin' ? DARWIN_TCP_INFO : eval { Socket::TCP +_INFO() }) or die "This system doesn't support the TCP_INFO structure.\n"; my ($testHost,$testPort)=('perl.org',443); socket(my $sock, PF_INET, SOCK_STREAM, IPPROTO_TCP) or die "Could not create socket - $!\n"; my $iaddr=inet_aton($testHost); my $paddr=sockaddr_in($testPort,$iaddr); connect($sock,$paddr) or die "Failed to connect to $testHost:$testPort - $!\n"; my $tcpInfoData=$GETSOCKOPT_FUNC->($sock,IPPROTO_TCP,$TCP_INFO) or die "Error while calling getsockopt - $!\n"; my $tcpInfoLength=length($tcpInfoData); if($tcpInfoLength < 256) { print "This system doesn't have a TCP_INFO structure large enough to + reproduce the problem.\n"; }elsif($tcpInfoLength == 256) { print "The TCP_INFO structure seems to be truncated to 256 bytes on +this system.\n"; }else{ print "This system doesn't seem to be affected by the problem (TCP_I +NFO length: $tcpInfoLength).\n"; }
      And finally, here is another implementation of the workaround, using a more generic approach to let the user specify a buffer length if needed (optional argument), the ideal solution in my opinion (if it was implemented in Perl core to be more efficient):
      sub getsockopt_darwin_gen { return &CORE::getsockopt if(@_ < 4); my $optlen = pop; my $optval = "\0" x $optlen; $optlen=pack('i',$optlen); my $rv=syscall(DARWIN_SYS_getsockopt,fileno($_[0]),$_[1],$_[2],$optv +al,$optlen); return $rv < 0 ? undef : substr($optval,0,unpack('i',$optlen)); }

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11144024]
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others scrutinizing the Monastery: (3)
As of 2022-06-26 17:59 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    My most frequent journeys are powered by:









    Results (86 votes). Check out past polls.

    Notices?