Skip to content

Commit 8e30e11

Browse files
committed
Saves inv messages from pcap files in Redis
1 parent 102ff2f commit 8e30e11

File tree

5 files changed

+184
-1
lines changed

5 files changed

+184
-1
lines changed

Diff for: pcap.conf

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[pcap]
2+
3+
# Logfile
4+
logfile = pcap.log
5+
6+
# Print debug output
7+
debug = False
8+
9+
# Relative path to directory containing pcap files
10+
pcap_dir = data/pcap

Diff for: pcap.py

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# pcap.py - Saves inv messages from pcap files in Redis.
5+
#
6+
# Copyright (c) 2014 Addy Yeow Chin Heng <[email protected]>
7+
#
8+
# Permission is hereby granted, free of charge, to any person obtaining
9+
# a copy of this software and associated documentation files (the
10+
# "Software"), to deal in the Software without restriction, including
11+
# without limitation the rights to use, copy, modify, merge, publish,
12+
# distribute, sublicense, and/or sell copies of the Software, and to
13+
# permit persons to whom the Software is furnished to do so, subject to
14+
# the following conditions:
15+
#
16+
# The above copyright notice and this permission notice shall be
17+
# included in all copies or substantial portions of the Software.
18+
#
19+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26+
27+
"""
28+
Saves inv messages from pcap files in Redis.
29+
"""
30+
31+
import dpkt
32+
import glob
33+
import logging
34+
import os
35+
import redis
36+
import socket
37+
import sys
38+
import threading
39+
import time
40+
from ConfigParser import ConfigParser
41+
42+
from protocol import ProtocolError, Serializer
43+
44+
# Redis connection setup
45+
REDIS_SOCKET = os.environ.get('REDIS_SOCKET', "/tmp/redis.sock")
46+
REDIS_PASSWORD = os.environ.get('REDIS_PASSWORD', None)
47+
REDIS_CONN = redis.StrictRedis(unix_socket_path=REDIS_SOCKET,
48+
password=REDIS_PASSWORD)
49+
50+
SETTINGS = {}
51+
52+
53+
def save_invs(timestamp, node, invs):
54+
"""
55+
Adds inv messages into the inv set in Redis.
56+
"""
57+
timestamp = int(timestamp * 1000) # in ms
58+
redis_pipe = REDIS_CONN.pipeline()
59+
for inv in invs:
60+
logging.debug("[{}] {}:{}".format(timestamp, inv['type'], inv['hash']))
61+
key = "inv:{}:{}".format(inv['type'], inv['hash'])
62+
redis_pipe.zadd(key, timestamp, node)
63+
redis_pipe.expire(key, 28800) # Expires in 8 hours
64+
redis_pipe.execute()
65+
66+
67+
def get_invs(filepath):
68+
"""
69+
Extracts inv messages from the specified pcap file.
70+
"""
71+
serializer = Serializer()
72+
pcap_file = open(filepath)
73+
pcap_reader = dpkt.pcap.Reader(pcap_file)
74+
for timestamp, buf in pcap_reader:
75+
frame = dpkt.ethernet.Ethernet(buf)
76+
ip_packet = frame.data
77+
if isinstance(ip_packet.data, dpkt.tcp.TCP):
78+
tcp_packet = ip_packet.data
79+
payload = tcp_packet.data
80+
if len(payload) > 0:
81+
try:
82+
(msg, _) = serializer.deserialize_msg(payload)
83+
except ProtocolError as err:
84+
pass
85+
else:
86+
if msg['command'] == "inv":
87+
if ip_packet.v == 6:
88+
address = socket.inet_ntop(socket.AF_INET6,
89+
ip_packet.src)
90+
else:
91+
address = socket.inet_ntop(socket.AF_INET,
92+
ip_packet.src)
93+
node = (address, tcp_packet.sport)
94+
save_invs(timestamp, node, msg['inventory'])
95+
pcap_file.close()
96+
97+
98+
def cron():
99+
"""
100+
Periodically fetches oldest pcap file to extract inv messages from.
101+
"""
102+
while True:
103+
time.sleep(5)
104+
105+
try:
106+
oldest = min(glob.iglob("{}/*.pcap".format(SETTINGS['pcap_dir'])))
107+
except ValueError as err:
108+
logging.warning(err)
109+
continue
110+
latest = max(glob.iglob("{}/*.pcap".format(SETTINGS['pcap_dir'])))
111+
if oldest == latest:
112+
continue
113+
dump = oldest
114+
logging.info("Dump: {}".format(dump))
115+
116+
start = time.time()
117+
get_invs(dump)
118+
end = time.time()
119+
elapsed = end - start
120+
logging.info("Elapsed: {}".format(elapsed))
121+
122+
os.remove(dump)
123+
124+
125+
def init_settings(argv):
126+
"""
127+
Populates SETTINGS with key-value pairs from configuration file.
128+
"""
129+
conf = ConfigParser()
130+
conf.read(argv[1])
131+
SETTINGS['logfile'] = conf.get('pcap', 'logfile')
132+
SETTINGS['debug'] = conf.getboolean('pcap', 'debug')
133+
SETTINGS['pcap_dir'] = conf.get('pcap', 'pcap_dir')
134+
if not os.path.exists(SETTINGS['pcap_dir']):
135+
os.makedirs(SETTINGS['pcap_dir'])
136+
137+
138+
def main(argv):
139+
if len(argv) < 2 or not os.path.exists(argv[1]):
140+
print("Usage: pcap.py [config]")
141+
return 1
142+
143+
# Initialize global settings
144+
init_settings(argv)
145+
146+
# Initialize logger
147+
loglevel = logging.INFO
148+
if SETTINGS['debug']:
149+
loglevel = logging.DEBUG
150+
151+
logformat = ("%(asctime)s,%(msecs)05.1f %(levelname)s (%(funcName)s) "
152+
"%(message)s")
153+
logging.basicConfig(level=loglevel,
154+
format=logformat,
155+
filename=SETTINGS['logfile'],
156+
filemode='w')
157+
print("Writing output to {}, press CTRL+C to terminate..".format(
158+
SETTINGS['logfile']))
159+
160+
threading.Thread(target=cron).start()
161+
162+
return 0
163+
164+
165+
if __name__ == '__main__':
166+
sys.exit(main(sys.argv))

Diff for: protocol.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,12 @@ def serialize_ping_payload(self, nonce):
272272

273273
def deserialize_ping_payload(self, data):
274274
data = StringIO(data)
275+
try:
276+
nonce = struct.unpack("<Q", data.read(8))[0]
277+
except struct.error as err:
278+
raise ReadError(err)
275279
msg = {
276-
'nonce': struct.unpack("<Q", data.read(8))[0],
280+
'nonce': nonce,
277281
}
278282
return msg
279283

Diff for: requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
-e svn+http://dpkt.googlecode.com/svn/trunk@90#egg=dpkt-1.8-py2.7-dev_r90
12
gevent==1.0.1
23
redis==2.9.1

Diff for: start.sh

+2
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,5 @@ python -u export.py export.conf > export.out 2>&1 &
1616
python -u chart.py chart.conf > chart.out 2>&1 &
1717

1818
python -u seeder.py seeder.conf > seeder.out 2>&1 &
19+
20+
python -u pcap.py pcap.conf > pcap.out 2>&1 &

0 commit comments

Comments
 (0)