hi Clayton,

for testing purposes, I have separated the problematic part of the rule
into a simple Single rule. As you can see, the rule calls the 'eval' action
for resolving an IP address and then writes it to standard output with
'write' action:

type=single
ptype=regexp
pattern=neighbor \*?(\d+\.\d+\.\d+\.\d+) Down
desc=BGP Neighbor $1 Down
action=eval %hostname ( $line = `perl -MSocket -E "say scalar
gethostbyaddr(inet_aton(\"$1\"), AF_INET)"`; \
           if ($line !~ //) { "$$1"; } else { "$1"; } ); \
       write - hostname: %hostname

The code fragment provided for 'eval' action has two issues, and I'll try
to explain them below. The first issue is related to interpretation of the
command line
perl -MSocket -E "say scalar gethostbyaddr(inet_aton(\"$1\"), AF_INET)"
At first glance, this command line appears to work when one tries it with a
specific IP address -- for example, when executing
perl -MSocket -E "say scalar gethostbyaddr(inet_aton(\"127.0.0.1\"),
AF_INET)"
in a terminal window, the command line returns string "localhost" as
expected.

However, when sec calls the 'eval' action which essentially means executing
this code as a Perl program, this code gets interpreted and backslashes in
front of " symbols are removed. Therefore, the command line which gets
forked from 'eval' action is actually
perl -MSocket -E "say scalar gethostbyaddr(inet_aton("127.0.0.1"), AF_INET)"
This command line gets interpreted by shell before it gets executed by
separate Perl process, and as a result, the Perl process will see the
following code:
say scalar gethostbyaddr(inet_aton(127.0.0.1), AF_INET)
However, since the parameter for inet_aton() is not a string, the function
fails.

For addressing this issue, you might use a pair of two backslashes for
masking, for example:
$line = `perl -MSocket -E "say scalar gethostbyaddr(inet_aton(\\"$1\\"),
AF_INET)"`
As an alternative, you could also enclose the entire say statement in
apostrophes which disables interpretation for it:
$line = `perl -MSocket -E 'say scalar gethostbyaddr(inet_aton("$1"),
AF_INET)'`

The other issue is related to the following code fragment:
if ($line !~ //) { "$$1"; } else { "$1"; }
Firstly, the regular expression // matches an empty string which can be
found in any input string, and therefore the check ($line !~ //) is always
false. As a result, the 'eval' action would always execute the else-branch
and return an IP address. Also, the regular expression // does not contain
any capture groups and therefore reference $$1 can't possibly return a
value. For addressing this issue, the code fragment could be rewritten as:
if ($line =~ /^(\S+)/) { "$$1"; } else { "$1"; }

After introducing the changes above, the rule would return a hostname as it
should. However, the rule is still sub-optimal, since it essentially first
compiles and executes Perl code, in order to fork another process for
compiling and executing another piece of Perl code. Below is a much more
efficient alternative which loads perl Socket module when sec starts up,
and resolves IP addresses to hostnames via fast 'lcall' action that avoids
expensive code compilation before each execution:

type=Single
ptype=SubStr
pattern=SEC_STARTUP
context=SEC_INTERNAL_EVENT
continue=TakeNext
desc=Load the Socket module and terminate if it is not found
action=eval %ret (require Socket); \
       if %ret ( logonly Socket module loaded ) else ( eval %o exit(1) )

type=single
ptype=regexp
pattern=neighbor \*?(\d+\.\d+\.\d+\.\d+) Down
desc=BGP Neighbor $1 Down
action=lcall %hostname $1 -> ( sub { my $host = scalar
gethostbyaddr(Socket::inet_aton($_[0]), Socket::AF_INET); if
(!defined($host)) { $host = $_[0]; } return $host; } ); \
       write - hostname: %hostname

All the work is done by anonymous function
sub { my $host = scalar gethostbyaddr(Socket::inet_aton($_[0]),
Socket::AF_INET); if (!defined($host)) { $host = $_[0]; } return $host; }
which resolves IP address parameter to hostname with gethostbyaddr(), and
returns the IP address itself if it does not resolve.

Hopefully these examples are helpful,
risto

Kontakt Clayton Dukes (<cdu...@logzilla.net>) kirjutas kuupäeval K, 28.
august 2019 kell 08:25:

> Hi folks,
>
> I have a user with a rule that is trying to use perl gethostbyaddr but it
> doesn’t seem to be returning anything.
>
> Can someone point out what’s wrong here?
>
> I added the `write` statement at the end and the file just writes the IP,
> not the reverse lookup hostname.
>
>
>
> # Tracks BGP tunnel downtime
>
> type=pair
>
> ptype=regexp
>
> continue=dontcont
>
> pattern=neighbor \*?(\d+\.\d+\.\d+\.\d+) Down
>
> desc=BGP Neighbor $1 Down
>
> action=eval %hostname ( $line = `perl -MSocket -E "say scalar
> gethostbyaddr(inet_aton(\"$1\"), AF_INET)"`; \
>
>     if ($line !~ //) { "$$1"; } else { "$1"; } ); \
>
>     eval %storenumber ( if ("%hostname" =~ /lo/) { "$$1"; } else { "NA"; }
> ); \
>
>     eval %tunnel ( if ("%hostname" =~ /lo(\d+)/) { "$$1"; } else { "NA"; }
> ); \
>
>     eval %TS (time()); \
>
>     tcpsock 10.1.0.85:514 SEC BGP Neighbor status="Down"
> hostname="%hostname" store="%storenumber" tunnel="%tunnel"
> ECRule="01-bgp-flap-detection" ECRulenum="1"%{.nl}; \
>
>     write /var/log/debug.sec.log %hostname
>
> ptype2=regexp
>
> pattern2=neighbor \*?($1) Up
>
> desc2=BGP Neighbor $1 Up
>
> action2=eval %TT ( time() - %TS ); \
>
>     tcpsock 10.1.0.85:514 SEC BGP Neighbor status="Up"
> hostname="%hostname" store="%storenumber" tunnel="%tunnel" downtime="%TT"
> ECRule="01-bgp-flap-detection" ECRulenum="2"%{.nl}
>
>
>
>
>
>
>
>
> _______________________________________________
> Simple-evcorr-users mailing list
> Simple-evcorr-users@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/simple-evcorr-users
>
_______________________________________________
Simple-evcorr-users mailing list
Simple-evcorr-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/simple-evcorr-users

Reply via email to