mcpcap 0.4.7__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mcpcap/modules/dns.py CHANGED
@@ -1,7 +1,5 @@
1
1
  """DNS analysis module."""
2
2
 
3
- import os
4
- import tempfile
5
3
  from datetime import datetime
6
4
  from typing import Any
7
5
 
@@ -19,128 +17,20 @@ class DNSModule(BaseModule):
19
17
  """Return the name of the protocol this module analyzes."""
20
18
  return "DNS"
21
19
 
22
- def list_dns_packets(self, pcap_file: str = "") -> dict[str, Any]:
20
+ def analyze_dns_packets(self, pcap_file: str) -> dict[str, Any]:
23
21
  """
24
- Analyze DNS packets from a PCAP file and return a summary of each packet.
22
+ Analyze DNS packets from a PCAP file and return comprehensive analysis results.
25
23
 
26
24
  Args:
27
- pcap_file: Path to the PCAP file to analyze. Leave empty for direct URL remotes
28
- or when using the first available file in local directories.
25
+ pcap_file: Path to local PCAP file or HTTP URL to remote PCAP file
29
26
 
30
27
  Returns:
31
28
  A structured dictionary containing DNS packet analysis results
32
29
  """
33
- # Handle remote files
34
- if self.config.is_remote:
35
- # For direct file URLs, always use the URL file (ignore pcap_file parameter)
36
- if self.config.is_direct_file_url:
37
- available_files = self.config.list_pcap_files()
38
- if not available_files:
39
- return {
40
- "error": "No PCAP file found at the provided URL",
41
- "pcap_url": self.config.pcap_url,
42
- }
43
- pcap_file = available_files[0] # Use the actual filename from URL
44
- elif not pcap_file:
45
- # For directory URLs, if no file specified, use the first available
46
- available_files = self.config.list_pcap_files()
47
- if not available_files:
48
- return {
49
- "error": "No PCAP files found at the provided URL",
50
- "pcap_url": self.config.pcap_url,
51
- "available_files": [],
52
- }
53
- pcap_file = available_files[0]
54
-
55
- # Download remote file to temporary location
56
- try:
57
- with tempfile.NamedTemporaryFile(
58
- suffix=".pcap", delete=False
59
- ) as tmp_file:
60
- temp_path = tmp_file.name
61
-
62
- local_path = self.config.download_pcap_file(pcap_file, temp_path)
63
- result = self.analyze_packets(local_path)
64
-
65
- # Clean up temporary file
66
- try:
67
- os.unlink(local_path)
68
- except OSError:
69
- pass # Ignore cleanup errors
70
-
71
- return result
30
+ return self.analyze_packets(pcap_file)
72
31
 
73
- except Exception as e:
74
- # List available PCAP files for help
75
- available_files = self.config.list_pcap_files()
76
- return {
77
- "error": f"Failed to download PCAP file '{pcap_file}': {str(e)}",
78
- "available_files": available_files,
79
- "pcap_source": self.config.pcap_url,
80
- }
81
- else:
82
- # Local file handling
83
- if not pcap_file:
84
- # If no file specified, use the first available local file
85
- available_files = self.config.list_pcap_files()
86
- if not available_files:
87
- return {
88
- "error": "No PCAP files found in directory",
89
- "pcap_directory": self.config.pcap_path,
90
- "available_files": [],
91
- }
92
- pcap_file = available_files[0]
93
-
94
- full_path = self.config.get_pcap_file_path(pcap_file)
95
-
96
- # Check if local file exists
97
- if not os.path.exists(full_path):
98
- # List available PCAP files for help
99
- available_files = self.config.list_pcap_files()
100
- return {
101
- "error": f"PCAP file '{pcap_file}' not found",
102
- "available_files": available_files,
103
- "pcap_directory": self.config.pcap_path,
104
- }
105
-
106
- return self.analyze_packets(full_path)
107
-
108
- def list_pcap_files(self) -> str:
109
- """
110
- List all available PCAP files in the configured directory or remote URL.
111
-
112
- Returns:
113
- A list of available PCAP files that can be analyzed
114
- """
115
- files = self.config.list_pcap_files()
116
- source = (
117
- self.config.pcap_url if self.config.is_remote else self.config.pcap_path
118
- )
119
-
120
- if files:
121
- if self.config.is_remote and self.config.is_direct_file_url:
122
- return f"Direct PCAP file URL: {source}\\n- {files[0]}"
123
- elif not self.config.is_remote and self.config.is_direct_file_path:
124
- return f"Direct PCAP file path: {source}\\n- {files[0]}"
125
- else:
126
- source_type = "remote server" if self.config.is_remote else "directory"
127
- return (
128
- f"Available PCAP files in {source_type} {source}:\\n"
129
- + "\\n".join(f"- {f}" for f in sorted(files))
130
- )
131
- else:
132
- source_type = "remote server" if self.config.is_remote else "directory"
133
- return f"No PCAP files found in {source_type} {source}"
134
-
135
- def analyze_packets(self, pcap_file: str) -> dict[str, Any]:
136
- """Analyze DNS packets in a PCAP file.
137
-
138
- Args:
139
- pcap_file: Full path to the PCAP file
140
-
141
- Returns:
142
- Analysis results as a dictionary
143
- """
32
+ def _analyze_protocol_file(self, pcap_file: str) -> dict[str, Any]:
33
+ """Perform the actual DNS packet analysis on a local PCAP file."""
144
34
  try:
145
35
  packets = rdpcap(pcap_file)
146
36
  dns_packets = [pkt for pkt in packets if pkt.haslayer(DNS)]
mcpcap/modules/icmp.py ADDED
@@ -0,0 +1,327 @@
1
+ """ICMP analysis module."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+ from fastmcp import FastMCP
7
+ from scapy.all import ICMP, IP, IPv6, rdpcap
8
+
9
+ from .base import BaseModule
10
+
11
+
12
+ class ICMPModule(BaseModule):
13
+ """Module for analyzing ICMP packets in PCAP files."""
14
+
15
+ @property
16
+ def protocol_name(self) -> str:
17
+ """Return the name of the protocol this module analyzes."""
18
+ return "ICMP"
19
+
20
+ def analyze_icmp_packets(self, pcap_file: str) -> dict[str, Any]:
21
+ """
22
+ Analyze ICMP packets from a PCAP file and return comprehensive analysis results.
23
+
24
+ Args:
25
+ pcap_file: Path to local PCAP file or HTTP URL to remote PCAP file
26
+
27
+ Returns:
28
+ A structured dictionary containing ICMP packet analysis results
29
+ """
30
+ return self.analyze_packets(pcap_file)
31
+
32
+ def _analyze_protocol_file(self, pcap_file: str) -> dict[str, Any]:
33
+ """Perform the actual ICMP packet analysis on a local PCAP file."""
34
+ try:
35
+ packets = rdpcap(pcap_file)
36
+ icmp_packets = [pkt for pkt in packets if pkt.haslayer(ICMP)]
37
+
38
+ if not icmp_packets:
39
+ return {
40
+ "file": pcap_file,
41
+ "total_packets": len(packets),
42
+ "icmp_packets_found": 0,
43
+ "message": "No ICMP packets found in this capture",
44
+ }
45
+
46
+ # Apply max_packets limit if specified
47
+ packets_to_analyze = icmp_packets
48
+ limited = False
49
+ if self.config.max_packets and len(icmp_packets) > self.config.max_packets:
50
+ packets_to_analyze = icmp_packets[: self.config.max_packets]
51
+ limited = True
52
+
53
+ packet_details = []
54
+ for i, pkt in enumerate(packets_to_analyze, 1):
55
+ packet_info = self._analyze_icmp_packet(pkt, i)
56
+ packet_details.append(packet_info)
57
+
58
+ # Generate statistics
59
+ stats = self._generate_statistics(packet_details)
60
+
61
+ result = {
62
+ "file": pcap_file,
63
+ "analysis_timestamp": datetime.now().isoformat(),
64
+ "total_packets": len(packets),
65
+ "icmp_packets_found": len(icmp_packets),
66
+ "icmp_packets_analyzed": len(packet_details),
67
+ "statistics": stats,
68
+ "packets": packet_details,
69
+ }
70
+
71
+ if limited:
72
+ result["note"] = (
73
+ f"Analysis limited to first {self.config.max_packets} ICMP packets due to --max-packets setting"
74
+ )
75
+
76
+ return result
77
+
78
+ except Exception as e:
79
+ return {
80
+ "error": f"Error reading PCAP file '{pcap_file}': {str(e)}",
81
+ "file": pcap_file,
82
+ }
83
+
84
+ def _analyze_icmp_packet(self, packet, packet_num: int) -> dict[str, Any]:
85
+ """Analyze a single ICMP packet and extract relevant information."""
86
+ info = {
87
+ "packet_number": packet_num,
88
+ "timestamp": datetime.fromtimestamp(float(packet.time)).isoformat(),
89
+ }
90
+
91
+ # Basic IP information
92
+ if packet.haslayer(IP):
93
+ ip_layer = packet[IP]
94
+ info.update(
95
+ {
96
+ "src_ip": ip_layer.src,
97
+ "dst_ip": ip_layer.dst,
98
+ "ip_version": 4,
99
+ "ttl": ip_layer.ttl,
100
+ "packet_size": len(packet),
101
+ }
102
+ )
103
+ elif packet.haslayer(IPv6):
104
+ ipv6_layer = packet[IPv6]
105
+ info.update(
106
+ {
107
+ "src_ip": ipv6_layer.src,
108
+ "dst_ip": ipv6_layer.dst,
109
+ "ip_version": 6,
110
+ "hop_limit": ipv6_layer.hlim,
111
+ "packet_size": len(packet),
112
+ }
113
+ )
114
+
115
+ # ICMP information
116
+ if packet.haslayer(ICMP):
117
+ icmp_layer = packet[ICMP]
118
+
119
+ # Map ICMP types to human-readable names
120
+ icmp_types = {
121
+ 0: "Echo Reply",
122
+ 3: "Destination Unreachable",
123
+ 4: "Source Quench",
124
+ 5: "Redirect",
125
+ 8: "Echo Request",
126
+ 11: "Time Exceeded",
127
+ 12: "Parameter Problem",
128
+ 13: "Timestamp Request",
129
+ 14: "Timestamp Reply",
130
+ 15: "Information Request",
131
+ 16: "Information Reply",
132
+ }
133
+
134
+ # Map destination unreachable codes
135
+ dest_unreach_codes = {
136
+ 0: "Network Unreachable",
137
+ 1: "Host Unreachable",
138
+ 2: "Protocol Unreachable",
139
+ 3: "Port Unreachable",
140
+ 4: "Fragmentation Required",
141
+ 5: "Source Route Failed",
142
+ }
143
+
144
+ # Map time exceeded codes
145
+ time_exceeded_codes = {
146
+ 0: "TTL Exceeded in Transit",
147
+ 1: "Fragment Reassembly Time Exceeded",
148
+ }
149
+
150
+ icmp_type = icmp_layer.type
151
+ icmp_code = icmp_layer.code
152
+
153
+ info.update(
154
+ {
155
+ "icmp_type": icmp_type,
156
+ "icmp_code": icmp_code,
157
+ "icmp_type_name": icmp_types.get(
158
+ icmp_type, f"Unknown Type ({icmp_type})"
159
+ ),
160
+ "icmp_id": getattr(icmp_layer, "id", None),
161
+ "icmp_seq": getattr(icmp_layer, "seq", None),
162
+ "checksum": icmp_layer.chksum,
163
+ }
164
+ )
165
+
166
+ # Add code descriptions for specific types
167
+ if icmp_type == 3: # Destination Unreachable
168
+ info["icmp_code_name"] = dest_unreach_codes.get(
169
+ icmp_code, f"Unknown Code ({icmp_code})"
170
+ )
171
+ elif icmp_type == 11: # Time Exceeded
172
+ info["icmp_code_name"] = time_exceeded_codes.get(
173
+ icmp_code, f"Unknown Code ({icmp_code})"
174
+ )
175
+ else:
176
+ info["icmp_code_name"] = f"Code {icmp_code}"
177
+
178
+ return info
179
+
180
+ def _generate_statistics(self, packets: list[dict[str, Any]]) -> dict[str, Any]:
181
+ """Generate statistics from analyzed ICMP packets."""
182
+ stats = {
183
+ "icmp_types": {},
184
+ "unique_sources": set(),
185
+ "unique_destinations": set(),
186
+ "echo_pairs": {},
187
+ "unreachable_destinations": set(),
188
+ }
189
+
190
+ for pkt in packets:
191
+ # Count ICMP types
192
+ if "icmp_type_name" in pkt:
193
+ type_name = pkt["icmp_type_name"]
194
+ stats["icmp_types"][type_name] = (
195
+ stats["icmp_types"].get(type_name, 0) + 1
196
+ )
197
+
198
+ # Track unique IPs
199
+ if "src_ip" in pkt:
200
+ stats["unique_sources"].add(pkt["src_ip"])
201
+ if "dst_ip" in pkt:
202
+ stats["unique_destinations"].add(pkt["dst_ip"])
203
+
204
+ # Track echo request/reply pairs
205
+ if pkt.get("icmp_type") in [0, 8] and pkt.get("icmp_id") is not None:
206
+ echo_id = pkt["icmp_id"]
207
+ if echo_id not in stats["echo_pairs"]:
208
+ stats["echo_pairs"][echo_id] = {"requests": 0, "replies": 0}
209
+
210
+ if pkt["icmp_type"] == 8: # Echo Request
211
+ stats["echo_pairs"][echo_id]["requests"] += 1
212
+ elif pkt["icmp_type"] == 0: # Echo Reply
213
+ stats["echo_pairs"][echo_id]["replies"] += 1
214
+
215
+ # Track unreachable destinations
216
+ if pkt.get("icmp_type") == 3: # Destination Unreachable
217
+ if "dst_ip" in pkt:
218
+ stats["unreachable_destinations"].add(pkt["dst_ip"])
219
+
220
+ # Convert sets to lists for JSON serialization
221
+ return {
222
+ "icmp_type_counts": stats["icmp_types"],
223
+ "unique_sources_count": len(stats["unique_sources"]),
224
+ "unique_destinations_count": len(stats["unique_destinations"]),
225
+ "unique_sources": list(stats["unique_sources"]),
226
+ "unique_destinations": list(stats["unique_destinations"]),
227
+ "echo_sessions": len(stats["echo_pairs"]),
228
+ "echo_pairs": stats["echo_pairs"],
229
+ "unreachable_destinations_count": len(stats["unreachable_destinations"]),
230
+ "unreachable_destinations": list(stats["unreachable_destinations"]),
231
+ }
232
+
233
+ def setup_prompts(self, mcp: FastMCP) -> None:
234
+ """Set up ICMP-specific analysis prompts for the MCP server."""
235
+
236
+ @mcp.prompt
237
+ def icmp_network_diagnostics():
238
+ """Prompt for analyzing ICMP traffic from a network diagnostics perspective"""
239
+ return """You are a network engineer analyzing ICMP traffic for network diagnostics. Focus on:
240
+
241
+ 1. **Connectivity Testing:**
242
+ - Analyze ping (echo request/reply) patterns and success rates
243
+ - Identify network reachability issues from failed pings
244
+ - Check ping response times and latency patterns
245
+ - Look for asymmetric routing issues
246
+
247
+ 2. **Network Path Analysis:**
248
+ - Examine TTL values and time exceeded messages for traceroute data
249
+ - Identify network hops and routing paths
250
+ - Look for routing loops or suboptimal paths
251
+ - Check for fragmentation issues
252
+
253
+ 3. **Error Diagnostics:**
254
+ - Analyze destination unreachable messages and their causes
255
+ - Identify network, host, protocol, or port unreachability
256
+ - Look for fragmentation required messages
257
+ - Check source quench messages for congestion
258
+
259
+ 4. **Network Health Assessment:**
260
+ - Monitor ICMP message frequencies and patterns
261
+ - Identify potential network congestion indicators
262
+ - Look for unusual ICMP traffic that might indicate problems
263
+ - Assess overall network responsiveness
264
+
265
+ Provide specific recommendations for network troubleshooting and optimization."""
266
+
267
+ @mcp.prompt
268
+ def icmp_security_analysis():
269
+ """Prompt for analyzing ICMP traffic from a security perspective"""
270
+ return """You are a security analyst examining ICMP traffic for threats and anomalies. Focus on:
271
+
272
+ 1. **Reconnaissance Detection:**
273
+ - Identify ping sweeps and network scanning activities
274
+ - Look for systematic probing of network ranges
275
+ - Check for unusual ping patterns that might indicate reconnaissance
276
+ - Monitor for traceroute-based network mapping
277
+
278
+ 2. **Covert Channel Analysis:**
279
+ - Examine ICMP payloads for potential data exfiltration
280
+ - Look for unusual ICMP packet sizes or timing patterns
281
+ - Check for non-standard ICMP types or codes
282
+ - Identify potential ICMP tunneling activities
283
+
284
+ 3. **DoS Attack Detection:**
285
+ - Monitor for ICMP flood attacks (ping floods)
286
+ - Look for smurf attack patterns (broadcast ping amplification)
287
+ - Check for fragmentation attacks using ICMP
288
+ - Identify potential death of death or similar attacks
289
+
290
+ 4. **Policy Compliance:**
291
+ - Verify ICMP traffic matches network security policies
292
+ - Check for unauthorized ICMP types (if policy restricts certain types)
293
+ - Monitor for ICMP traffic from unexpected sources
294
+ - Identify potential firewall bypass attempts
295
+
296
+ Provide threat assessment and recommended security controls."""
297
+
298
+ @mcp.prompt
299
+ def icmp_forensic_investigation():
300
+ """Prompt for forensic analysis of ICMP traffic"""
301
+ return """You are conducting a digital forensics investigation involving ICMP traffic. Approach systematically:
302
+
303
+ 1. **Timeline Reconstruction:**
304
+ - Create chronological sequence of ICMP events
305
+ - Map ping activities to potential user or system actions
306
+ - Correlate ICMP traffic with incident timeframes
307
+ - Track network connectivity patterns over time
308
+
309
+ 2. **Attribution and Tracking:**
310
+ - Trace ICMP traffic to source systems and networks
311
+ - Identify systems involved in ping exchanges
312
+ - Map network topology from traceroute data
313
+ - Document unique identifiers in ICMP packets
314
+
315
+ 3. **Evidence Collection:**
316
+ - Preserve all ICMP packet details with precise timestamps
317
+ - Document network paths and hop information
318
+ - Record error messages and their contexts
319
+ - Note any unusual or suspicious ICMP characteristics
320
+
321
+ 4. **Impact Assessment:**
322
+ - Determine scope of network reconnaissance or scanning
323
+ - Assess potential information leakage through ICMP
324
+ - Identify systems that may have been probed
325
+ - Evaluate ongoing security implications
326
+
327
+ Present findings with precise timestamps, evidence preservation notes, and clear documentation suitable for legal proceedings."""