From 98d3ff422570ae47c2f19f7d8115431e0e2b6ed3 Mon Sep 17 00:00:00 2001
From: Pierre Lalet <pierre@droids-corp.org>
Date: Wed, 13 Apr 2016 09:51:24 +0000
Subject: [PATCH] PacketList.conversations(): add labels, support ARP by
 default (#122)

* PacketList.conversations(): include ARP packets by default

* PacketList.conversations(): accept a label instead of counting

* PacketList.conversations(): display the label in the graph
---
 scapy/plist.py | 30 +++++++++++++++++++++++-------
 1 file changed, 23 insertions(+), 7 deletions(-)

diff --git a/scapy/plist.py b/scapy/plist.py
index 3dc64866..987d39db 100644
--- a/scapy/plist.py
+++ b/scapy/plist.py
@@ -284,25 +284,41 @@ lfilter: truth function to apply to each packet to decide whether it will be dis
     def conversations(self, getsrcdst=None,**kargs):
         """Graphes a conversations between sources and destinations and display it
         (using graphviz and imagemagick)
-        getsrcdst: a function that takes an element of the list and return the source and dest
-                   by defaults, return source and destination IP
+        getsrcdst: a function that takes an element of the list and
+                   returns the source, the destination and optionally
+                   a label. By default, returns the IP source and
+                   destination from IP and ARP layers
         type: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option
         target: filename or redirect. Defaults pipe to Imagemagick's display program
         prog: which graphviz program to use"""
         if getsrcdst is None:
-            getsrcdst = lambda x:(x['IP'].src, x['IP'].dst)
+            def getsrcdst(pkt):
+                if IP in pkt:
+                    return (pkt[IP].src, pkt[IP].dst)
+                if ARP in pkt:
+                    return (pkt[ARP].psrc, pkt[ARP].pdst)
+                raise TypeError()
         conv = {}
         for p in self.res:
             p = self._elt2pkt(p)
             try:
                 c = getsrcdst(p)
             except:
-                #XXX warning()
+                # No warning here: it's OK that getsrcdst() raises an
+                # exception, since it might be, for example, a
+                # function that expects a specific layer in each
+                # packet. The try/except approach is faster and
+                # considered more Pythonic than adding tests.
                 continue
-            conv[c] = conv.get(c,0)+1
+            if len(c) == 3:
+                conv.setdefault(c[:2], set()).add(c[2])
+            else:
+                conv[c] = conv.get(c, 0) + 1
         gr = 'digraph "conv" {\n'
-        for s,d in conv:
-            gr += '\t "%s" -> "%s"\n' % (s,d)
+        for (s, d), l in conv.iteritems():
+            gr += '\t "%s" -> "%s" [label="%s"]\n' % (
+                s, d, ', '.join(str(x) for x in l) if isinstance(l, set) else l
+            )
         gr += "}\n"        
         return do_graph(gr, **kargs)
 
-- 
GitLab