Python
snmp
udp
stub

SNMPのパケットキャプチャ&リプレイ

More than 3 years have passed since last update.

SNMP(UDP)のパケットキャプチャ&リプレイのコードを書いてみました。

普通の人はSNMP-Simulatorを使うのが良いかと思いますが。。


使い方


  1. 192.168.1.119でSNMP-Agentを起動

  2. snmp_stub.pyをcaptureモードで実行

  3. snmpwalk -c public -v 1 192.168.1.12を実行

  4. captureファイルが作成されたことを確認する

  5. 192.168.1.119でSNMP-Agentを停止

  6. snmp_stub.pyをreplayモードで実行

  7. snmpwalk -c public -v 1 192.168.1.12を実行

  8. SNMP-Agentが起動している時と同等の結果が返ってくる


環境

OS X El Capitan

Python 2.7.11

snmpwalk 5.6.2.1

自PC:192.168.1.12

SNMP取得先:192168.1.119


実行結果


snmp_stub.pyの実行

$ sudo python snmp_stub.py

Requested: 0%26%02%01%00%04%06public%A1%19%02%04%3E%0B%CC%E5%02%01%00%02%01%000%0B0%09%06%05%2B%06%01%02%01%05%00 192.168.1.12 65344
Responsed: 0%81%AA%02%01%00%04%06public%A2%81%9C%02%04%3E%0B%CC%E5%02%01%00%02%01%000%81%8D0%81%8A%06%08%2B%06%01%02%01%01%01%00%04%7EDarwin%20macbook.local%2010.8.0%20Darwin%20Kernel%20Version%2010.8.0%3A%20Tue%20Jun%20%207%2016%3A33%3A36%20PDT%202011%3B%20root%3Axnu-1504.15.3%7E1/RELEASE_I386%20i386 192.168.1.12 65344
---------------------------
Requested: 0%29%02%01%00%04%06public%A1%1C%02%04%3E%0B%CC%E6%02%01%00%02%01%000%0E0%0C%06%08%2B%06%01%02%01%01%01%00%05%00 192.168.1.12 65344
Responsed: 03%02%01%00%04%06public%A2%26%02%04%3E%0B%CC%E6%02%01%00%02%01%000%180%16%06%08%2B%06%01%02%01%01%02%00%06%0A%2B%06%01%04%01%BF%08%03%02%10 192.168.1.12 65344
---------------------------
<省略>



snmpwalkの実行

$ snmpwalk -c public -v 1 192.168.1.12

SNMPv2-MIB::sysDescr.0 = STRING: Darwin macbook.local 10.8.0 Darwin Kernel Version 10.8.0: Tue Jun 7 16:33:36 PDT 2011; root:xnu-1504.15.3~1/RELEASE_I386 i386
SNMPv2-MIB::sysObjectID.0 = OID: NET-SNMP-MIB::netSnmpAgentOIDs.16
DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (27736720) 3 days, 5:02:47.20
SNMPv2-MIB::sysContact.0 = STRING: SysAdmin
<省略>


ソースコード


snmp_stub.py

import socket

import base64
import urllib

UDP_IP_LISTEN = "192.168.1.12"
UDP_PORT_LISTEN = 161
UDP_IP_TO = "192.168.1.119"
UDP_PORT_TO = 161
BUFFER_SIZE = 4096

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP
sock.bind((UDP_IP_LISTEN, UDP_PORT_LISTEN))

cache = []
captured_data = []
captured_data_dic = {}

stub_mode = 'replay'
#stub_mode = 'capture'

if stub_mode == 'capture':
f = open('capture', 'w')
elif stub_mode == 'replay':
f = open('capture', 'r')
captured_data = f.readlines()
for i in range(len(captured_data)):
if 'Requested' in captured_data[i]:
key = urllib.unquote(captured_data[i+3].rstrip())
value = urllib.unquote(captured_data[i+3+4].rstrip())
key = key[:17] + 'XXXX' + key[21:]
captured_data_dic[key] = value
#print value, key

while True:
if stub_mode == 'capture':
# send to remote snmp
data_request, (host_request, port_request) = sock.recvfrom(BUFFER_SIZE)
while data_request in cache:
data_request, (host_request, port_request) = sock.recvfrom(BUFFER_SIZE)
print 'skipped due to duplicated'
cache.append(data_request)
if len(cache) > 10:
cache = cache[1:]
print 'Requested:', urllib.quote(data_request), host_request, port_request

f.write(urllib.quote('Requested data') + '\n')
f.write(urllib.quote(host_request) + '\n')
f.write(urllib.quote(str(port_request)) + '\n')
f.write(urllib.quote(data_request) + '\n')
sock.sendto(data_request, (UDP_IP_TO, UDP_PORT_TO))

# receive from remote snmp
data_response, (host_response, port_response) = sock.recvfrom(BUFFER_SIZE)
while data_response in cache:
data_response, (host_response, port_response) = sock.recvfrom(BUFFER_SIZE)
print 'skipped due to duplicated'
cache.append(data_response)
if len(cache) > 10:
cache = cache[1:]

f.write(urllib.quote('Responsed data') + '\n')
f.write(urllib.quote(host_response) + '\n')
f.write(urllib.quote(str(port_response)) + '\n')
f.write(urllib.quote(data_response) + '\n')
f.write('\n')
sock.sendto(data_response, (UDP_IP_LISTEN, port_request))
print 'responsed:', urllib.quote(data_response), UDP_IP_LISTEN, port_request
print '---------------------------'

elif stub_mode == 'replay':
data_request, (host_request, port_request) = sock.recvfrom(BUFFER_SIZE)
while data_request in cache:
data_request, (host_request, port_request) = sock.recvfrom(BUFFER_SIZE)
print 'skipped due to duplicated'
cache.append(data_request)
if len(cache) > 0:
cache = cache[1:]
print 'Requested:', urllib.quote(data_request), host_request, port_request

request_id = data_request[17:21]

data_request = data_request[:17] + 'XXXX' + data_request[21:]
ret_data = captured_data_dic[data_request]
if ord(ret_data[1]) >= 128:
length = ord(ret_data[1]) - 128
else:
length = 0

if ord(ret_data[1+length+13]) >= 128:
length2 = ord(ret_data[1+length+13]) - 128
else:
length2 = 0

pos = 1+length+13+length2+3
ret_data = ret_data[:pos] + request_id + ret_data[pos+4:]

sock.sendto(ret_data, (UDP_IP_LISTEN, port_request))
print 'Responsed:', urllib.quote(ret_data), UDP_IP_LISTEN, port_request
print '---------------------------'

else:
break


共通の解説

解説1

今回は1つのネットワークしか使わないので、ソケットは1つのみ作成。

もしLISTENとTOで分けたいのであれば、それぞれのソケットを作成し、それに合わせたフォワーディングルールを書く必要があります。

capture modeの解説

解説1

処理概要は、1リクエストずつ処理することを想定しています。

つまり、snmpwalkから1リクエストが来たら、本物のsnmp-agentに1リクエストを転送し、

本物のsnmp-agentから応答が来たら、snmpwalkに1リクエストを転送する、を

1パケットずつ処理しています。

snmpwalkのパケットの流れを見たところ、1リクエストずつ処理をしていたので、このような処理方法で問題なさそうです。

ただし、応答が遅いとすぐに再送されてしまうため、再送対策は必要です。

解説2

cacheは、UDP固有のされたパケットを無視する為。

とりあえず、過去10件のパケットを記録し、同じリクエストが来たら再送とみなし

次のリクエストを待つようにしています。

replay modeの解説

解説1

再送対策のコードをここにも入れていますが、実は無効化されています。もし、再送に悩まされたら0を適宜増やしてください。


参考

https://tools.ietf.org/html/rfc1592