Source code for stepler.third_party.tcpdump
"""
---------------
tcpdump helpers
---------------
"""
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import contextlib
import tempfile
import time
from hamcrest import assert_that, is_in # noqa H301
import scapy.all as scapy
TYPE_ICMP_REPLY = 0
[docs]def read_pcap(path, lfilter=None):
"""Read pcap file and yields packets.
Args:
path (str): path to pcap file
lfilter (function, optional): function to filter returned packets. By
default all packets will be returned.
Yields:
obj: packet
"""
with scapy.PcapReader(path) as reader:
for packet in filter(lfilter, reader):
yield packet
[docs]def filter_icmp(packet):
"""Returns True if packet contains ICMP layer."""
return scapy.ICMP in packet
[docs]def filter_vxlan(packet):
"""Returns True if packet contains VxLAN layer."""
return scapy.VXLAN in packet
@contextlib.contextmanager
[docs]def tcpdump(remote, args='', prefix=None, latency=2, lfilter=None):
"""Non-blocking context manager for run tcpdump on backgroud.
It yields path to pcap file.
Args:
remote (SshClient): instance of ssh client
args (str, optional): additional ``tcpdump`` args
prefix (str, optional): prefix for command. It can be useful for
executing tcpdump on ip namespace.
latency (int, optional): time to wait after tcpdump's starting and
before tcpdump's termination to guarantee that all packets will be
captured
lfilter (function, optional): function to filter returned packets. By
default all packets will be returned.
Yields:
str: path to pcap file
"""
pcap_file = tempfile.mktemp()
stdout_file = tempfile.mktemp()
cmd = "tcpdump -w{pcap_file} {args}".format(args=args, pcap_file=pcap_file)
if prefix:
cmd = "{} {}".format(prefix, cmd)
with remote.sudo():
pid = remote.background_call(cmd, stdout=stdout_file)
# wait tcpdump to start
remote.execute('while [ ! -f {} ]; do sleep 1; done'.format(pcap_file))
# tcpdump need some more time to start packets capturing
time.sleep(latency)
yield pcap_file
# wait some time to allow tcpdump to process all packets
time.sleep(latency)
with remote.sudo():
remote.execute('kill -SIGINT {}'.format(pid))
remote.wait_process_done(pid, timeout=latency)
with remote.open(pcap_file) as src:
with open(pcap_file, 'wb') as dst:
dst.write(src.read())
remote.execute('rm {}'.format(pcap_file))
[docs]def get_last_ping_reply_ts(path):
"""Returns last ICMP echo response timestamp.
If there are no replies in packets - it returns None.
Args:
packets (list): list packets
Returns:
float|None: last ICMP reply timestamp or None
"""
last_replied_ts = None
for packet in read_pcap(path, lfilter=filter_icmp):
if not filter_icmp(packet):
continue
if packet[scapy.ICMP].type == TYPE_ICMP_REPLY:
last_replied_ts = max(last_replied_ts, packet.time)
return last_replied_ts