如何在 Perl 中解析原始 SNMP 陷阱?

发布于 2024-07-27 03:28:10 字数 1118 浏览 5 评论 0原文

几周前,我为我们的操作组编写了一个 SNMP 中继器。 他们有一些只能向单个 IP 发送陷阱的哑设备,而我们有一个监控系统,可以监听多个 IP 的可用性。 代码非常简单,本质上:

while (recv($packet)) {
  foreach $target (@targets) {
    send($target, $packet);
  }
}

它基本上有效,但现在明显的缺点是它不包含发起者 IP,这是一个问题(显然第一类设备包含作为 varbind 的信息,而一些新类不包含该信息) )。

我想做的是将我的代码更改为如下所示:

while ($server->recv($packet)) {
  my $obj = decompile($packet)
  if (!$obj->{varbind}{snmpTrapAddress}) {
    $obj->{varbind}{snmpTrapAddress} = inet_ntoa($server->peeraddr());
  }
  $packet = compile($obj);
  foreach $target (@targets) {
    send($target, $packet);
  }
}

换句话说,如果我的发件人不包括 snmpTrapAddress,请添加它。 问题是,我为 Perl 查看的每个 SNMP 包似乎都非常关注接收陷阱和执行获取的基础结构。

那么:是否有一个简单的 Perl 模块可以让我说“这是代表 snmp 陷阱的数据 blob。将其解码为我可以轻松操作的内容,然后将其重新编译回我可以通过网络抛出的 blob”?

如果您给出的答案是“使用 SNMP 虚拟”,您能提供相关示例吗? 我可能只是盲目的,但从 perldoc SNMP 的输出来看,我不清楚如何以这种方式使用它。

编辑:

环顾四周后发现“SNMP 编码”实际上是 ASN.1 BER(基本编码规则)。 基于此,我尝试使用 Convert::BER。 我仍然欢迎任何简单的 SNMP 分解/编辑/重建技巧。

A few weeks ago I wrote an SNMP relayer for our ops group. They have some dumb devices that can only send traps to a single IP, and we have a monitoring system that listens on multiple IPs for availability. The code's dead simple, and essentially:

while (recv($packet)) {
  foreach $target (@targets) {
    send($target, $packet);
  }
}

It's worked, basically, but now the obvious short coming that it doesn't include the originator IP is an issue (apparently the first class of device included the info as a varbind and some new class does not).

What I would like to do is change my code to something like this:

while ($server->recv($packet)) {
  my $obj = decompile($packet)
  if (!$obj->{varbind}{snmpTrapAddress}) {
    $obj->{varbind}{snmpTrapAddress} = inet_ntoa($server->peeraddr());
  }
  $packet = compile($obj);
  foreach $target (@targets) {
    send($target, $packet);
  }
}

In other words, if my sender isn't including snmpTrapAddress, add it. The problem is that every SNMP package I've looked at for Perl seems very heavily focused on the infrastructure of receiving traps and performing gets.

So: Is there a simple Perl module that will allow me to say "here's a blob of data representing an snmp trap. decode it into something I can easily manipulate, then recompile it back into a blob I can throw over the network"?

If the answer you give is "use SNMP dummy", can you provide examples of this? I may just be blind, but from the output of perldoc SNMP it's not obvious to me how to use it in this manner.

EDIT:

Turns out after looking around a bit that "SNMP encoding" is really ASN.1 BER (Basic Encoding Rules). Based on this I'm having a go with Convert::BER. I would still welcome any easy break down/edit/rebuild tips for SNMP.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

旧梦荧光笔 2024-08-03 03:28:10

我从来没有找到完美的解决方案。 Net::SNMP::Message(Net::SNMP) 可能允许这样做,但似乎没有公开定义的接口,并且 Net::SNMP 接口似乎都没有特别相关。 NSNMP 最接近我所寻找的精神,但是它很脆弱,对我开箱即用的数据包不起作用,如果我要支持脆弱代码,那将是我自己的脆弱代码=)。

Mon::SNMP 也接近我正在寻找的内容,但它也被打破了。 它似乎已被放弃,最后一个版本是 2001 年,开发人员的最后一个 CPAN 版本是 2002 年。当时我没有意识到这一点,但我现在认为它已经被破坏了,因为 Convert::BER 的接口发生了变化。它使用的模块。

Mon::SNMP 让我指向 Convert::BER 。 数千次阅读 Convert::BER POD、Mon::SNMP 源代码和 RFC 1157< /a> (特别是 4.1.6,“Trap-PDU”)之后,我想出了这段代码作为概念证明来完成我想做的事情。 这只是概念证明(原因我将在代码后详细说明),因此它可能并不完美,但我认为它可能为未来在该领域工作的 Perl 人员提供有用的参考,所以就是这样:

#!/usr/bin/perl

use Convert::BER;
use Convert::BER qw(/^(\$|BER_)/);

my $ber = Convert::BER->new();

# OID I want to add to the trap if not already present
my $snmpTrapAddress = '1.3.6.1.6.3.18.1.3';

# this would be from the incoming socket in production
my $source_ip = '10.137.54.253';

# convert the octets into chars to match SNMP standard for IPs
my $source_ip_str = join('', map { chr($_); } split(/\./, $source_ip));

# Read the binary trap data from STDIN or ARGV.  Normally this would
# come from the UDP receiver
my $d = join('', <>);

# Stuff my trap data into $ber
$ber->buffer($d);

print STDERR "Original packet:\n";
$ber->dump();

# Just decode the first two fields so we can tell what version we're dealing with
$ber->decode(
                SEQUENCE => [
                    INTEGER => \$version,
                    STRING => \$community,
                    BER => \$rest_of_trap,
                ],
) || die "Couldn't decode packet: ".$ber->error()."\n";

if ($version == 0) {
  #print STDERR "This is a version 1 trap, proceeding\n";

  # decode the PDU up to but not including the VARBINDS
  $rest_of_trap->decode(
    [ SEQUENCE => BER_CONTEXT | BER_CONSTRUCTOR | 0x04 ] =>
      [
        OBJECT_ID => \$enterprise_oid,
        [ STRING => BER_APPLICATION | 0x00 ] => \$agentaddr,
        INTEGER => \$generic,
        INTEGER => \$specific,
        [ INTEGER => BER_APPLICATION | 0x03 ] => \$timeticks,
        SEQUENCE => [ BER => \$varbind_ber, ],
      ],
  ) || die "Couldn't decode packet: ".$extra->error()."\n";;

  # now decode the actual VARBINDS (just the OIDs really, to decode the values
  # We'd have to go to the MIBs, which I neither want nor need to do
  my($r, $t_oid, $t_val, %varbinds);
  while ($r = $varbind_ber->decode(
    SEQUENCE => [
      OBJECT_ID => \$t_oid,
      ANY       => \$t_val,
    ], ))
  {
    if (!$r) {
      die "Couldn't decode SEQUENCE: ".$extra->error()."\n";
    }
    $varbinds{$t_oid} = $t_val;
  }

  if ($varbinds{$snmpTrapAddress} || $varbinds{"$snmpTrapAddress.0"}) {
    # the original trap already had the data, just print it back out
    print $d;
  } else {
    # snmpTrapAddress isn't present, create a new object and rebuild the packet
    my $new_trap = new Convert::BER;
    $new_trap->encode(
      SEQUENCE => [
        INTEGER => $version,
        STRING => $community,
        [ SEQUENCE => BER_CONTEXT | BER_CONSTRUCTOR | 0x04 ] =>
          [
            OBJECT_ID => $enterprise_oid,
            [ STRING => BER_APPLICATION | 0x00 ] => $agentaddr,
            INTEGER => $generic,
            INTEGER => $specific,
            [ INTEGER => BER_APPLICATION | 0x03 ] => $timeticks,
            SEQUENCE => [
              BER => $varbind_ber,
              # this next oid/val is the only mod we should be making
              SEQUENCE => [
                OBJECT_ID => "$snmpTrapAddress.0",
                [ STRING => BER_APPLICATION | 0x00 ] => $source_ip_str,
              ],
            ],
          ],
      ],
    );
    print STDERR "New packet:\n";
    $new_trap->dump();
    print $new_trap->buffer;
  }
} else {
  print STDERR "I don't know how to decode non-v1 packets yet\n";
  # send back the original packet
  print $d;  
}

所以,就是这样。 这是最关键的。 我听从了他们的说法,他们没有得到陷阱中原始发件人的 IP。 在研究这个例子时,我发现,至少在他们给我的例子中,原始IP位于陷阱的agent-addr字段中。 在向他们展示了这一点以及他们使用该工具的 API 中暴露的位置后,他们开始尝试进行更改。 我正在写上面的代码,以防止他们要求我提供一些我实际上需要在数据包中处理的东西,但现在上面的代码仍将是未经严格测试的概念验证代码。 希望有一天它能帮助别人。

I never found a perfect solution to this. Net::SNMP::Message (part of Net::SNMP) might allow this but doesn't seem to have a publicly defined interface, and none of the Net::SNMP interface seemed especially relevant. NSNMP is closest to the spirit of what I was looking for, but it's brittle and didn't work for my packet out of the box and if I'm going to support brittle code, it's going to be my own brittle code =).

Mon::SNMP also got close to what I was looking for, but it too was broken out of the box. It appears to be abandoned, with the last release in 2001 and the developer's last CPAN release in 2002. I didn't realize it at the time but I now think that it's broken because of a change in the interface to the Convert::BER module it uses.

Mon::SNMP got me pointed toward Convert::BER. A few thousand reads of the Convert::BER POD, the Mon::SNMP source, and RFC 1157 (esp. 4.1.6, "The Trap-PDU") later and I came up with this code as a proof of concept to do what I wanted. This is just proof of concept (for reasons I'll detail after the code) so it may not be perfect, but I thought it might provide useful reference for future Perl people working in this area, so here it is:

#!/usr/bin/perl

use Convert::BER;
use Convert::BER qw(/^(\$|BER_)/);

my $ber = Convert::BER->new();

# OID I want to add to the trap if not already present
my $snmpTrapAddress = '1.3.6.1.6.3.18.1.3';

# this would be from the incoming socket in production
my $source_ip = '10.137.54.253';

# convert the octets into chars to match SNMP standard for IPs
my $source_ip_str = join('', map { chr($_); } split(/\./, $source_ip));

# Read the binary trap data from STDIN or ARGV.  Normally this would
# come from the UDP receiver
my $d = join('', <>);

# Stuff my trap data into $ber
$ber->buffer($d);

print STDERR "Original packet:\n";
$ber->dump();

# Just decode the first two fields so we can tell what version we're dealing with
$ber->decode(
                SEQUENCE => [
                    INTEGER => \$version,
                    STRING => \$community,
                    BER => \$rest_of_trap,
                ],
) || die "Couldn't decode packet: ".$ber->error()."\n";

if ($version == 0) {
  #print STDERR "This is a version 1 trap, proceeding\n";

  # decode the PDU up to but not including the VARBINDS
  $rest_of_trap->decode(
    [ SEQUENCE => BER_CONTEXT | BER_CONSTRUCTOR | 0x04 ] =>
      [
        OBJECT_ID => \$enterprise_oid,
        [ STRING => BER_APPLICATION | 0x00 ] => \$agentaddr,
        INTEGER => \$generic,
        INTEGER => \$specific,
        [ INTEGER => BER_APPLICATION | 0x03 ] => \$timeticks,
        SEQUENCE => [ BER => \$varbind_ber, ],
      ],
  ) || die "Couldn't decode packet: ".$extra->error()."\n";;

  # now decode the actual VARBINDS (just the OIDs really, to decode the values
  # We'd have to go to the MIBs, which I neither want nor need to do
  my($r, $t_oid, $t_val, %varbinds);
  while ($r = $varbind_ber->decode(
    SEQUENCE => [
      OBJECT_ID => \$t_oid,
      ANY       => \$t_val,
    ], ))
  {
    if (!$r) {
      die "Couldn't decode SEQUENCE: ".$extra->error()."\n";
    }
    $varbinds{$t_oid} = $t_val;
  }

  if ($varbinds{$snmpTrapAddress} || $varbinds{"$snmpTrapAddress.0"}) {
    # the original trap already had the data, just print it back out
    print $d;
  } else {
    # snmpTrapAddress isn't present, create a new object and rebuild the packet
    my $new_trap = new Convert::BER;
    $new_trap->encode(
      SEQUENCE => [
        INTEGER => $version,
        STRING => $community,
        [ SEQUENCE => BER_CONTEXT | BER_CONSTRUCTOR | 0x04 ] =>
          [
            OBJECT_ID => $enterprise_oid,
            [ STRING => BER_APPLICATION | 0x00 ] => $agentaddr,
            INTEGER => $generic,
            INTEGER => $specific,
            [ INTEGER => BER_APPLICATION | 0x03 ] => $timeticks,
            SEQUENCE => [
              BER => $varbind_ber,
              # this next oid/val is the only mod we should be making
              SEQUENCE => [
                OBJECT_ID => "$snmpTrapAddress.0",
                [ STRING => BER_APPLICATION | 0x00 ] => $source_ip_str,
              ],
            ],
          ],
      ],
    );
    print STDERR "New packet:\n";
    $new_trap->dump();
    print $new_trap->buffer;
  }
} else {
  print STDERR "I don't know how to decode non-v1 packets yet\n";
  # send back the original packet
  print $d;  
}

So, that's it. Here's the kicker. I took ops at their word that they weren't getting the IP of the original sender in the trap. While working through this example, I found that, at least in the example they gave me, the original IP was in the agent-addr field in the trap. After showing them this and where in the API of the tool their using this is exposed they went off to try to make the change on their end. I'm daving the code above against the time they ask me for something I actually need to muck in the packet for, but for now the above will remain non-rigorously-tested proof of concept code. Hope it helps someone someday.

亣腦蒛氧 2024-08-03 03:28:10

您尝试过 NSNMP 吗?

Did you try NSNMP?

酒中人 2024-08-03 03:28:10

一定要检查 SNMP_Session。

http://code.google.com/p/snmp-session/

请务必点击旧分发站点的链接,其中有几个示例。

我基本上通过 Mon::SNMP、Convert::BER、TCP/IP Illustrated 等走过了相同的道路。SNMP_Session 是我唯一能够工作的东西。 我所说的工作是指在 UDP 端口 162 上接受 SNMP 陷阱并将其解码为等效字符串以进行记录,而无需重新发明几个轮子。 我只使用接收陷阱功能,但我认为它也可以实现您想要的功能。

它位于 Google Code 上,而不是 CPAN 上,所以有点难找到。

Definitely check out SNMP_Session.

http://code.google.com/p/snmp-session/

Make sure to follow the links to the old distribution site which has several examples.

I've basically traveled the same path through Mon::SNMP, Convert::BER, TCP/IP Illustrated, etc.. SNMP_Session is the only thing I have been able to make work. By work, I mean accept a SNMP trap on UDP port 162 and decode it into string equivalents for logging without reinventing several wheels. I'm only using the receive trap functionality but I think it may do what you want too.

It's on Google Code, not CPAN though, so it's a little bit hard to find.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文