Skip to content

Commit fef2443

Browse files
committed
add first attempt at pcap filtering by Community ID
1 parent 64a6fa1 commit fef2443

File tree

1 file changed

+183
-0
lines changed

1 file changed

+183
-0
lines changed

scripts/community-id-pcapfilter

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
#! /usr/bin/env python
2+
"""
3+
This script filters one or more provided pcap files (not pcapng), creating
4+
an output file containing only packets with a supplied Community ID hash.
5+
6+
The output file's packets retain timestamp and all packet data from the
7+
originally supplied pcap file(s).
8+
9+
This is based heavily on the "community-id-pcap" script in the same
10+
directory and retains all of its limitations and caveats at the time this
11+
script was created.
12+
13+
Currently supported protocols include IP, IPv6, ICMP, ICMPv6, TCP,
14+
UDP, SCTP.
15+
16+
Please note: the protocol parsing implemented in this script relies
17+
on the dpkt module and is somewhat simplistic:
18+
19+
- dpkt seems to struggle with some SCTP packets, for which it fails
20+
to register SCTP even though its header is correctly present.
21+
22+
- The script doesn't try to get nested network layers (IP over IPv6,
23+
IP in IP, etc) right. It expects either IP or IPv6, and it expects
24+
a transport-layer protocol (including the ICMPs here) as the
25+
immediate next layer.
26+
"""
27+
import argparse
28+
import gzip
29+
import sys
30+
31+
import communityid
32+
33+
try:
34+
import dpkt
35+
except ImportError:
36+
print('This needs the dpkt Python module')
37+
sys.exit(1)
38+
39+
from dpkt.ethernet import Ethernet #pylint: disable=import-error
40+
from dpkt.ip import IP #pylint: disable=import-error
41+
from dpkt.ip6 import IP6 #pylint: disable=import-error
42+
from dpkt.icmp import ICMP #pylint: disable=import-error
43+
from dpkt.icmp6 import ICMP6 #pylint: disable=import-error
44+
from dpkt.tcp import TCP #pylint: disable=import-error
45+
from dpkt.udp import UDP #pylint: disable=import-error
46+
from dpkt.sctp import SCTP #pylint: disable=import-error
47+
48+
class PcapFilter(object):
49+
def __init__(self, commid, pcap, commidfilter, outputwriter):
50+
self._commid = commid
51+
self._pcap = pcap
52+
self._commidfilter = commidfilter
53+
self._outputwriter = outputwriter
54+
55+
def process(self):
56+
if self._pcap.endswith('.gz'):
57+
opener=gzip.open
58+
else:
59+
opener=open
60+
61+
with opener(self._pcap, 'r+b') as inhdl:
62+
reader = dpkt.pcap.Reader(inhdl)
63+
for tstamp, pktdata in reader:
64+
self._process_packet(tstamp, pktdata, self._outputwriter)
65+
66+
def _process_packet(self, tstamp, pktdata, outputwriter):
67+
pkt = self._packet_parse(pktdata)
68+
69+
if not pkt:
70+
return
71+
72+
if IP in pkt:
73+
saddr = pkt[IP].src
74+
daddr = pkt[IP].dst
75+
elif IP6 in pkt:
76+
saddr = pkt[IP6].src
77+
daddr = pkt[IP6].dst
78+
else:
79+
return
80+
81+
tpl = None
82+
83+
if TCP in pkt:
84+
tpl = communityid.FlowTuple(
85+
dpkt.ip.IP_PROTO_TCP, saddr, daddr,
86+
pkt[TCP].sport, pkt[TCP].dport)
87+
88+
elif UDP in pkt:
89+
tpl = communityid.FlowTuple(
90+
dpkt.ip.IP_PROTO_UDP, saddr, daddr,
91+
pkt[UDP].sport, pkt[UDP].dport)
92+
93+
elif SCTP in pkt:
94+
tpl = communityid.FlowTuple(
95+
dpkt.ip.IP_PROTO_SCTP, saddr, daddr,
96+
pkt[SCTP].sport, pkt[SCTP].dport)
97+
98+
elif ICMP in pkt:
99+
tpl = communityid.FlowTuple(
100+
dpkt.ip.IP_PROTO_ICMP, saddr, daddr,
101+
pkt[ICMP].type, pkt[ICMP].code)
102+
103+
elif ICMP6 in pkt:
104+
tpl = communityid.FlowTuple(
105+
dpkt.ip.IP_PROTO_ICMP6, saddr, daddr,
106+
pkt[ICMP6].type, pkt[ICMP6].code)
107+
108+
if tpl is None:
109+
# Fallbacks to other IP protocols:
110+
if IP in pkt:
111+
tpl = communityid.FlowTuple(pkt[IP].p, saddr, daddr)
112+
elif IP6 in pkt:
113+
tpl = communityid.FlowTuple(pkt[IP].nxt, saddr, daddr)
114+
115+
if tpl is None:
116+
return
117+
118+
res = self._commid.calc(tpl)
119+
120+
if res == self._commidfilter:
121+
outputwriter.writepkt(pktdata, tstamp)
122+
123+
def _packet_parse(self, pktdata):
124+
"""
125+
Parses the protocols in the given packet data and returns the
126+
resulting packet (here, as a dict indexed by the protocol layers
127+
in form of dpkt classes).
128+
"""
129+
layer = Ethernet(pktdata)
130+
pkt = {}
131+
132+
if isinstance(layer.data, IP):
133+
pkt[IP] = layer = layer.data
134+
elif isinstance(layer.data, IP6):
135+
# XXX This does not correctly skip IPv6 extension headers
136+
pkt[IP6] = layer = layer.data
137+
else:
138+
return pkt
139+
140+
if isinstance(layer.data, ICMP):
141+
pkt[ICMP] = layer.data
142+
elif isinstance(layer.data, ICMP6):
143+
pkt[ICMP6] = layer.data
144+
elif isinstance(layer.data, TCP):
145+
pkt[TCP] = layer.data
146+
elif isinstance(layer.data, UDP):
147+
pkt[UDP] = layer.data
148+
elif isinstance(layer.data, SCTP):
149+
pkt[SCTP] = layer.data
150+
151+
return pkt
152+
153+
def main():
154+
parser = argparse.ArgumentParser(description='Community ID pcap filtering utility')
155+
parser.add_argument('pcaps', metavar='PCAP', nargs='+',
156+
help='PCAP packet capture files')
157+
parser.add_argument('--filter', metavar='FILTER', required=True,
158+
help='Community ID string in base64 format to filter from input pcap file(s)')
159+
parser.add_argument('--output', metavar='OUTPUT', required=True,
160+
help='Output pcap file to create and place matching packets into')
161+
parser.add_argument('--seed', type=int, default=0, metavar='NUM',
162+
help='Seed value for hash operations')
163+
args = parser.parse_args()
164+
165+
commid = communityid.CommunityID(args.seed)
166+
167+
# if outfile exists, quit
168+
try:
169+
outhdl = open(args.output, 'xb')
170+
except FileExistsError:
171+
print('Error: output file %s already exists. Exiting.' % (args.output))
172+
return 2
173+
else:
174+
writer = dpkt.pcap.Writer(outhdl)
175+
176+
for pcap in args.pcaps:
177+
itr = PcapFilter(commid, pcap, args.filter, writer)
178+
itr.process()
179+
180+
return 0
181+
182+
if __name__ == '__main__':
183+
sys.exit(main())

0 commit comments

Comments
 (0)