SNMP(UDP)のパケットキャプチャ&リプレイのコードを書いてみました。
普通の人はSNMP-Simulatorを使うのが良いかと思いますが。。
使い方
- 192.168.1.119でSNMP-Agentを起動
- snmp_stub.pyをcaptureモードで実行
- snmpwalk -c public -v 1 192.168.1.12を実行
- captureファイルが作成されたことを確認する
- 192.168.1.119でSNMP-Agentを停止
- snmp_stub.pyをreplayモードで実行
- snmpwalk -c public -v 1 192.168.1.12を実行
- SNMP-Agentが起動している時と同等の結果が返ってくる
環境
OS X El Capitan
Python 2.7.11
snmpwalk 5.6.2.1
自PC:192.168.1.12
SNMP取得先:192168.1.119
実行結果
$ 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 -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
<省略>
ソースコード
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を適宜増やしてください。
参考