Perl에서 Raw SNMP 트랩을 어떻게 구문 분석 할 수 있습니까?
문제
몇 주 전에 저는 OPS 그룹을위한 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 패키지가 트랩을 받고 공연을하는 인프라에 매우 집중된 것처럼 보인다는 것입니다.
그래서 : "SNMP 트랩을 나타내는 데이터의 덩어리가 있습니다. 쉽게 조작 할 수있는 무언가로 디코딩 한 다음 네트워크 위로 던질 수있는 블로브로 다시 컴파일 할 수있는 간단한 Perl 모듈이 있습니까?
당신이주는 대답이 "SNMP 더미 사용"이라면 이것의 예를 제공 할 수 있습니까? 나는 맹인 일지 모르지만 출력에서 Perldoc SNMP 이런 식으로 사용하는 방법은 분명하지 않습니다.
편집하다:
"SNMP 인코딩"이 실제로 ASN.1 BER (기본 인코딩 규칙)이라는 점을 둘러 본 후에 밝혀졌습니다. 이것을 바탕으로 나는 Convert :: ber와 함께 가고 있습니다. SNMP에 대한 쉬운 분리/편집/재 구축 팁을 여전히 환영합니다.
해결책
나는 이것에 대한 완벽한 해결책을 찾지 못했습니다. net :: snmp :: 메시지 (일부 net :: snmp)이를 허용 할 수는 있지만 공개적으로 정의 된 인터페이스가없는 것 같습니다. Net :: SNMP 인터페이스는 특히 관련이없는 것 같습니다. NSNMP 내가 찾고 있던 것의 정신에 가장 가깝지만 부서지기 쉬우 며 상자 밖으로 내 패킷에 효과가 없었으며 부서지기 쉬운 코드를 지원하려면 내 자신의 부서지기 쉬운 코드가 될 것입니다 =).
월 :: snmp 또한 내가 찾고 있던 것과 가까워졌지만 상자에서 나왔습니다. 2001 년 마지막 릴리스와 2002 년 개발자의 마지막 CPAN 릴리스와 함께 버려진 것으로 보입니다. 당시에는 몰랐지만 이제는 Convert :: BER의 인터페이스의 변화로 인해 깨진 것이라고 생각합니다. 사용하는 모듈.
Mon :: SNMP가 나를 향해 지적했습니다 변환 :: ber. 개종자의 수천 개의 읽기 :: Ber Pod, Mon :: SNMP 소스 및 RFC 1157 (Esp. 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;
}
그래서 그게 다야. 여기 키커가 있습니다. 나는 그들의 말에 Ops를 덫에 원래 발신자의 IP를 얻지 못했다는 말을 가져 갔다. 이 예제를 통해 작업하는 동안 적어도 그들이 준 예에서 원래 IP는 트랩의 에이전트 ADDR 필드에 있음을 알았습니다. 그들에게 이것을 보여준 후, 도구의 API에서 이것을 사용하는 곳은 노출되었다. 그들은 그들의 끝을 바꾸려고 노력했다. 나는 그들이 실제로 패킷에 뭉쳐야 할 것을 요구할 때에 대해 위의 코드를 정리하고 있지만, 현재 위의 것은 강력하게 테스트되지 않은 개념 코드 증명으로 남아있을 것입니다. 언젠가 누군가가 도움이되기를 바랍니다.
다른 팁
시도해 보았 니 NSNMP?
snmp_session을 확실히 확인하십시오.
http://code.google.com/p/snmp-sestion/
몇 가지 예제가있는 이전 배포 사이트에 대한 링크를 따라야합니다.
나는 기본적으로 Mon :: SNMP, Convert :: BER, TCP/IP 등을 통해 동일한 경로를 여행했습니다. 작업한다는 것은 UDP 포트 162에서 SNMP 트랩을 수락하고 여러 바퀴를 재창조하지 않고 로깅을 위해 문자열에 해당하는 문자열로 디코딩한다는 의미입니다. 나는 수신 트랩 기능 만 사용하고 있지만 그것이 당신이 원하는 것을 할 수 있다고 생각합니다.
그래도 CPAN이 아닌 Google 코드에 있으므로 찾기가 조금 어렵습니다.