1+ from  scapy .all  import  * 
2+ import  psutil 
3+ from  collections  import  defaultdict 
4+ import  os 
5+ from  threading  import  Thread 
6+ import  pandas  as  pd 
7+ 
8+ # get the all network adapter's MAC addresses 
9+ all_macs  =  {iface .mac  for  iface  in  ifaces .values ()}
10+ # A dictionary to map each connection to its correponding process ID (PID) 
11+ connection2pid  =  {}
12+ # A dictionary to map each process ID (PID) to total Upload (0) and Download (1) traffic 
13+ pid2traffic  =  defaultdict (lambda : [0 , 0 ])
14+ # the global Pandas DataFrame that's used to track previous traffic stats 
15+ global_df  =  None 
16+ # global boolean for status of the program 
17+ is_program_running  =  True 
18+ 
19+ def  get_size (bytes ):
20+     """ 
21+     Returns size of bytes in a nice format 
22+     """ 
23+     for  unit  in  ['' , 'K' , 'M' , 'G' , 'T' , 'P' ]:
24+         if  bytes  <  1024 :
25+             return  f"{ bytes :.2f} { unit }  B" 
26+         bytes  /=  1024 
27+ 
28+ 
29+ def  process_packet (packet ):
30+     global  pid2traffic 
31+     try :
32+         # get the packet source & destination IP addresses and ports 
33+         packet_connection  =  (packet .sport , packet .dport )
34+     except  (AttributeError , IndexError ):
35+         # sometimes the packet does not have TCP/UDP layers, we just ignore these packets 
36+         pass 
37+     else :
38+         # get the PID responsible for this connection from our `connection2pid` global dictionary 
39+         packet_pid  =  connection2pid .get (packet_connection )
40+         if  packet_pid :
41+             if  packet .src  in  all_macs :
42+                 # the source MAC address of the packet is our MAC address 
43+                 # so it's an outgoing packet, meaning it's upload 
44+                 pid2traffic [packet_pid ][0 ] +=  len (packet )
45+             else :
46+                 # incoming packet, download 
47+                 pid2traffic [packet_pid ][1 ] +=  len (packet )
48+ 
49+ 
50+ def  get_connections ():
51+     """A function that keeps listening for connections on this machine  
52+     and adds them to `connection2pid` global variable""" 
53+     global  connection2pid 
54+     while  is_program_running :
55+         # using psutil, we can grab each connection's source and destination ports 
56+         # and their process ID 
57+         for  c  in  psutil .net_connections ():
58+             if  c .laddr  and  c .raddr  and  c .pid :
59+                 # if local address, remote address and PID are in the connection 
60+                 # add them to our global dictionary 
61+                 connection2pid [(c .laddr .port , c .raddr .port )] =  c .pid 
62+                 connection2pid [(c .raddr .port , c .laddr .port )] =  c .pid 
63+         # sleep for a second, feel free to adjust this 
64+         time .sleep (1 )
65+ 
66+         
67+ def  print_pid2traffic ():
68+     global  global_df 
69+     # initialize the list of processes 
70+     processes  =  []
71+     for  pid , traffic  in  pid2traffic .items ():
72+         # `pid` is an integer that represents the process ID 
73+         # `traffic` is a list of two values: total Upload and Download size in bytes 
74+         try :
75+             # get the process object from psutil 
76+             p  =  psutil .Process (pid )
77+         except  psutil .NoSuchProcess :
78+             # if process is not found, simply continue to the next PID for now 
79+             continue 
80+         # get the name of the process, such as chrome.exe, etc. 
81+         name  =  p .name ()
82+         # get the time the process was spawned 
83+         try :
84+             create_time  =  datetime .fromtimestamp (p .create_time ())
85+         except  OSError :
86+             # system processes, using boot time instead 
87+             create_time  =  datetime .fromtimestamp (psutil .boot_time ())
88+         # construct our dictionary that stores process info 
89+         process  =  {
90+             "pid" : pid , "name" : name , "create_time" : create_time , "Upload" : traffic [0 ],
91+             "Download" : traffic [1 ],
92+         }
93+         try :
94+             # calculate the upload and download speeds by simply subtracting the old stats from the new stats 
95+             process ["Upload Speed" ] =  traffic [0 ] -  global_df .at [pid , "Upload" ]
96+             process ["Download Speed" ] =  traffic [1 ] -  global_df .at [pid , "Download" ]
97+         except  (KeyError , AttributeError ):
98+             # If it's the first time running this function, then the speed is the current traffic 
99+             # You can think of it as if old traffic is 0 
100+             process ["Upload Speed" ] =  traffic [0 ]
101+             process ["Download Speed" ] =  traffic [1 ]
102+         # append the process to our processes list 
103+         processes .append (process )
104+     # construct our Pandas DataFrame 
105+     df  =  pd .DataFrame (processes )
106+     try :
107+         # set the PID as the index of the dataframe 
108+         df  =  df .set_index ("pid" )
109+         # sort by column, feel free to edit this column 
110+         df .sort_values ("Download" , inplace = True , ascending = False )
111+     except  KeyError  as  e :
112+         # when dataframe is empty 
113+         pass 
114+     # make another copy of the dataframe just for fancy printing 
115+     printing_df  =  df .copy ()
116+     try :
117+         # apply the function get_size to scale the stats like '532.6KB/s', etc. 
118+         printing_df ["Download" ] =  printing_df ["Download" ].apply (get_size )
119+         printing_df ["Upload" ] =  printing_df ["Upload" ].apply (get_size )
120+         printing_df ["Download Speed" ] =  printing_df ["Download Speed" ].apply (get_size ).apply (lambda  s : f"{ s }  /s" )
121+         printing_df ["Upload Speed" ] =  printing_df ["Upload Speed" ].apply (get_size ).apply (lambda  s : f"{ s }  /s" )
122+     except  KeyError  as  e :
123+         # when dataframe is empty again 
124+         pass 
125+     # clear the screen based on your OS 
126+     os .system ("cls" ) if  "nt"  in  os .name  else  os .system ("clear" )
127+     # print our dataframe 
128+     print (printing_df .to_string ())
129+     # update the global df to our dataframe 
130+     global_df  =  df 
131+     
132+     
133+ def  print_stats ():
134+     """Simple function that keeps printing the stats""" 
135+     while  is_program_running :
136+         time .sleep (1 )
137+         print_pid2traffic ()
138+         
139+         
140+ 
141+ if  __name__  ==  "__main__" :
142+     # start the printing thread 
143+     printing_thread  =  Thread (target = print_stats )
144+     printing_thread .start ()
145+     # start the get_connections() function to update the current connections of this machine 
146+     connections_thread  =  Thread (target = get_connections )
147+     connections_thread .start ()
148+     # start sniffing 
149+     print ("Started sniffing" )
150+     sniff (prn = process_packet , store = False )
151+     # setting the global variable to False to exit the program 
152+     is_program_running  =  False     
153+     
154+     
0 commit comments