perl6 Changes in IO::Socket::INET from last year and broken promises - sockets

When I asked a question last year about promises, my echo server was working (see this link: perl6 how to get specific identity of promises? ). However, with the new versions of perl6, my echo server is no longer working.
I guess I can try the example from the perl6 documentation site ( https://docs.perl6.org/type/IO::Socket::INET ), but I want to find out what mistake I have made in my code. My current level has precluded me from seeing the difference between my codes and the codes on the perl6 documentation site. Please give me a hint; thanks !
my #result;
for 0 .. 2 -> $index {
#result[$index] = start {
my $myPromiseID = $index;
say "======> $myPromiseID\n";
my $rsSocket = IO::Socket::INET.new:
localhost => 'localhost',
localport => 1234 + $index,
listen => 1;
while $rsSocket.accept -> $rsConnection {
say "Promise $myPromiseID accepted connection";
while $rsConnection.recv -> $stuff {
say "Promise $myPromiseID Echoing $stuff";
$rsConnection.print($stuff);
}
$rsConnection.close;
}
}
}
await #result;
And the error messages are:
Tried to get the result of a broken Promise
in block <unit> at p6EchoMulti.pl line 24
Original exception:
Nothing given for new socket to connect or bind to
in block at p6EchoMulti.pl line 8
Actually thrown at:
in block at p6EchoMulti.pl line 13

This commit, which was announced in the Jan 2017 section of Rakudo's changelog as "Fixed bug where IPv6 URIs were not parsed correctly" did a lot more that just fix a URI parsing bug. It also completely redid the parameter binding/validation of an IO::Socket::INET.new call, and one consequence is it broke your code because the updated code requires that listen be an actual Bool, not merely coerce to one.
The old code (the code on the left of the commit link above) had a simple method new (*%args is copy). This matched your call. The error (fail "Nothing given for new socket to connect or bind to") did not trigger because 1 evaluates to True in a boolean context so %args<host> || %args<listen> was also True. So the rest of the code ran with listen set to 1 and it all worked out fine.
Rakudos from 2017.01 have the code on the right at the commit link above. Note how there are now multiple new methods (i.e. multiple multi method new ... declarations).
The multi(s) intended to handle a call that specifies a listen argument is/are of the form multi method new (..., Bool:D :$listen!, ...). Note the Bool:D.
A call to new, with the listen parameter set to True, matches this multi and works as expected.
But a call with :listen(1) will just match the generic multi method new (*%args) signature instead. This latter does an unconditional fail "Nothing given for new socket to connect or bind to";.

Okay, after some struggling, it seems to have improved if I changed listen=>1 to listen=>True.
Can anyone care to explain why 1 was not evaluated to True, and why it worked before?
Thanks.

Related

SNMPTraps: pysnmp and "disableAuthorization"

I have written an SNMP trap receiver but currently I have to hardcode all the SNMPv2 community strings to be able to receive the traps.
How do I emulate the 'disableAuthorization' functionality from snmptrapd in pysnmp?
I have tried to not set the community string: config.addV1System(snmpEngine, 'my-area') but this errors about a missing param. I have also tried an empty string: config.addV1System(snmpEngine, 'my-area', '') but this stops all traps being processed.
What is the best way to allow receiving all traps through pysnmp regardless of the community string they were sent with? I haven't found anything in the pysnmp docs that could help me
I had made progress on setting up an observer for V1/2 (V3 to be added later) that picked up on notifications with an unknown community string and then called addV1System on the fly to dynamically add it in, like so:
When setting up the transportDispatcher:
snmpEngine.observer.registerObserver(_handle_unauthenticated_snmptrap,
"rfc2576.prepareDataElements:sm-failure", "rfc3412.prepareDataElements:sm-failure")
And then:
def _handle_unauthenticated_snmptrap(snmpEngine, execpoint, variables, cbCtx):
if variables["securityLevel"] in [ 1, 2 ] and variables["statusInformation"]["errorIndication"] == errind.unknownCommunityName:
new_comm_string = "%s" % variables["statusInformation"].get("communityName", "")
config.addV1System(my_snmpEngine, 'my-area', new_comm_string)
return
else:
msg = "%s" % variables["statusInformation"]
print(f"Trap: { msg }")
However, this will always throw away the first trap received while adding any new community string (and then there is the problem whereby when the daemon is restarted the updated list of community strings is lost).
In looking to improve this I then found hidden away in the docs this little gem:
https://pysnmp.readthedocs.io/en/latest/examples/v3arch/asyncore/manager/ntfrcv/advanced-topics.html#serve-snmp-community-names-defined-by-regexp
This example receives a notification and rewrites the community string into 'public' so all traps will be correctly received.
In essence, when setting up the transportDispatcher:
my_snmpEngine.observer.registerObserver(_trap_observer,
'rfc2576.processIncomingMsg:writable', cbCtx='public')
And then:
def _trap_observer(snmpEngine, execpoint, variables, community_string):
variables['communityName'] = variables['communityName'].clone(community_string)

Perl Modification of a read-only value attempted for hash

I have a server that also counts the number of distinct IPs connected in a minute. I get a "Modification of a read-only value attempted" in the marked line in the code below:
$lsock=new IO::Socket::INET(LocalPort=>$port,Proto=>'tcp',Listen=>1,Reuse=>1);
$clients={};
while (1) {
$sock=$lsock->accept();
if ($sock) {
$clients->{$sock->peerhost()}=1; # THIS LINE !!!
...
}
}
# then later from an alarm signal I do:
sub save_stats {
my $cnt=scalar keys %$clients;
$clients={};
...
}
The error appears very rarely, once a month or less, but it's driving me insane, can someone please explain it to me why and what can I do?
After a lot of investigation I found the answer. It can happen that the connection is lost between accept() and peerhost(). In this case the peeerhost() function's return value is not suitable as a hash key. The solution is very simple, if peerhost() returns a "false" ignore the connection alltogether.

Exim getting random credential in exim.conf

I have been trying to get perl subroutine value and substitution to get the required part of string from randomips subroutine in exim.conf. However when i use string substitution i get error as follow:
Here is what I am trying to achieve
I am trying to split string by colon and get first occurrence as "interface". I'll be using second occurrence as the "helo_data.
exim.pl
sub randomhosts {
#inet = ("x.x.x.1:hostname1.domain.com","x.x.x.2:hostname2.domain.com","x.x.x.3:hostname3.domain.com"
);
return $inet[int rand($#inet+1)];
}
exim.conf
dkim_remote_smtp:
driver = smtp
interface = "${perl{randomhosts}%:*}"
helo_data = "${sender_address_domain}"
Error I get is as follow:
"failed to expand "interface" option for dkim_remote_smtp transport: missing '}' after 'perl'".
Probably the syntax.
Any help?
The code that you are trying to copy was written by someone who doesn't know much about Perl. It includes this line:
return $inet[int rand($#inet+1)];
A Perl programmer would write this as
return $inet[rand #inet];
I think there are a couple of issues here - one with your Exim syntax and one with your Perl syntax.
Exim is giving you this error:
failed to expand "interface" option for dkim_remote_smtp transport: missing '}' after 'perl'
I don't know anything about calling Perl from Exim, but this page mentions a syntax like ${perl{foo}} (which is similar to the one used in the page you are copying from) and one like ${perl{foo}{argument}} for calling a subroutine and passing it an argument. Nowhere does it mention syntax like yours:
${perl{randomhosts}%:*}
I'm not sure where you have got that syntax from, but it seems likely that this is what is causing your first error.
In a comment, you say
I am stying to get first part of string before colon for each random array value for "interface" and part after colon for "helo_data"
It seems to me that Exim doesn't support this requirement. You would need to call the function twice to get the two pieces of information that you require. You might be able to do this in the Perl using something like state variables - but it would be far more complex than the code you currently have there.
Secondly, your Perl code has a syntax error, so even if Exim was able to call your code, it wouldn't work.
The code you're copying sets up #inet like this:
#inet = ("x.x.x.1", "x.x.x.2", "x.x.x.3", "x.x.x.4");
Your equivalent code is this:
#inet = (
"x.x.x.1:hostname1.domain.com",
"x.x.x.2:hostname2.domain.com,
x.x.x.3:hostname3.domain.com
);
I've reformatted it, to make the problems more obvious. You are missing a number of quote marks around the elements of the array. (Note: I see that while I have been writing this answer, you have fixed that.)
Update: Ok, here is some code to put into exim.pl that does what you want.
use feature qw[state];
sub randomhosts {
state $current;
my #inet = (
"x.x.x.1:hostname1.domain.com",
"x.x.x.2:hostname2.domain.com",
"x.x.x.3:hostname3.domain.com"
);
if ($_[0] eq 'generate') {
shift;
#{$current}{qw[ip host]} = split /:/, $inet[rand #inet];
}
return $current->{$_[0]};
}
It generates a new ip/host pair if its first argument is 'generate'. It will then return either the hostname or the ip address from the generated pair. I think you can probably call it from your Exim config file like this:
dkim_remote_smtp:
driver = smtp
interface = "${perl{randomhosts}{generate}{ip}}"
helo_data = "${perl{randomhosts}{host}}"
But I'm no expert in Exim, so that syntax might need tweaking.
First I would like to note I have not worked with exim so I cannot say what exactly you are trying to do and why you have done things exactly so.
In the link you posted, a method called 'randinet' is added to exim.pl and the interface line in exim.conf is replaced by
interface = "${perl{randinet}}"
You have implemented a 'randomhosts' method and replaced the interface line with
interface = "${perl{randomhosts}%:*}"
Now the parser complains about not finding the closing bracket. That is likely due to the symbols you felt free to add but the parser does not have the freedom to ignore.
I suggest you try
interface = "${perl{randomhosts}}"

Perl Module Net::DNS - calling "tsig" method on resolver object results in error

recently the linux-distribution i use (recent gentoo)
upgraded the net-dns package to version 0.74 (from 0.66).
from this time using TSIG on queries and updates
does not work anymore.
former i used:
$resolver = Net::DNS::Resolver->new(...);
$resolver->tsig( $keyname, $key );
# ($key as base64 representation)
or
$resolver->tsig( Net::DNS::RR->new( "$keyname TSIG $key" ) );
calling tsig now results in an expeption:
"zone file representation not defined for TSIG at /usr/lib/perl5/vendor_perl/5.18.2/i686-linux/Net/DNS/RR.pm line 683."
according to http://search.cpan.org/~nlnetlabs/Net-DNS-0.74/lib/Net/DNS/Resolver.pm#tsig
tsig() - Get or set the TSIG record used to automatically sign outgoing queries and updates.
my usage of tsig() should be correct.
using another way of pre-creating the tsig RR-Object with:
my $tsig = Net::DNS::RR->new( type => "TSIG", name => "KEYNAME", key => "KEY" );
$resolver->tsig($tsig);
results in "tsig verify failure (BADSIG)" Errors in BIND at server side.
using $tsig for update packets only:
my $update = Net::DNS::Update->new( ... );
$update->sign_tsig($tsig);
also does not work (BADSIG); the 'simpler' way
$update->sign_tsig($keyname, $key);
does work.
What is the correct way to use TSIG for both query and update packets with the resolver object in Net::DNS >= V0.74 ?
Perl Version is 5.18.2 .
what am i doing wrong ? - thanks a lot for your hints.
The TSIG functionality in Net::DNS had a complete rewrite around 0.74, and most releases since then have had bugfixes for some aspect of TSIG. I'd suggest that you try to forget how it used to work, re-read all the relevant documentation and then change your own code as needed.
Also, 0.74 is (in this context) pretty old. It would probably be a good idea to upgrade to something closer to current (which is 0.82 when I write this).

Perl: how to validate successful call to "XML::eXistDB::RPC"

i am writing a small perl app using the eXist database, and i am wondering is:
how can i see that my call
my $eXist = XML::eXistDB::RPC->new( destination=>$eXist_db, repository=>$bank, user=>"admin", password=>"pass" ) ;
is successful or not ?
thanx
When object initialisation fails, it will be messaged through Log::Report, so hook into that.
This only happens if the programmer to neglected to set either rpc or destination parameter. The new constructor will always return an object instance.
According to the docs:
All methods return a LIST, where the
first scalar is a return code (RC).
When that code is 0, all went well.
Otherwise, the code represent the
transport error or the exception
(refusal) as reported by the server
logic. In either case, the second
scalar in the returned list contains
the error message. For instance,
Maybe this applies also for the constructor, try:
my ($rc,$eXist) = XML::eXistDB::RPC->new( destination=>$eXist_db, repository=>$bank, user=>"admin", password=>"pass" );
now, if $rc != 0 there was an error.