mirror of
https://github.com/infinition/Bjorn.git
synced 2025-12-12 15:44:58 +00:00
335 lines
12 KiB
Python
335 lines
12 KiB
Python
# Network topology mapping tool for discovering and visualizing network segments.
|
|
# Saves settings in `/home/bjorn/.settings_bjorn/yggdrasil_mapper_settings.json`.
|
|
# Automatically loads saved settings if arguments are not provided.
|
|
# -r, --range Network range to scan (CIDR format).
|
|
# -i, --interface Network interface to use (default: active interface).
|
|
# -d, --depth Maximum trace depth for routing (default: 5).
|
|
# -o, --output Output directory (default: /home/bjorn/Bjorn/data/output/topology).
|
|
# -t, --timeout Timeout for probes in seconds (default: 2).
|
|
|
|
import os
|
|
import json
|
|
import argparse
|
|
from datetime import datetime
|
|
import logging
|
|
import subprocess
|
|
import networkx as nx
|
|
import matplotlib.pyplot as plt
|
|
import nmap
|
|
import scapy.all as scapy
|
|
from scapy.layers.inet import IP, ICMP, TCP
|
|
import threading
|
|
import queue
|
|
|
|
|
|
b_class = "YggdrasilMapper"
|
|
b_module = "yggdrasil_mapper"
|
|
b_enabled = 0
|
|
|
|
# Configure logging
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
|
# Default settings
|
|
DEFAULT_OUTPUT_DIR = "/home/bjorn/Bjorn/data/output/topology"
|
|
DEFAULT_SETTINGS_DIR = "/home/bjorn/.settings_bjorn"
|
|
SETTINGS_FILE = os.path.join(DEFAULT_SETTINGS_DIR, "yggdrasil_mapper_settings.json")
|
|
|
|
class YggdrasilMapper:
|
|
def __init__(self, network_range, interface=None, max_depth=5, output_dir=DEFAULT_OUTPUT_DIR, timeout=2):
|
|
self.network_range = network_range
|
|
self.interface = interface or scapy.conf.iface
|
|
self.max_depth = max_depth
|
|
self.output_dir = output_dir
|
|
self.timeout = timeout
|
|
|
|
self.graph = nx.Graph()
|
|
self.hosts = {}
|
|
self.routes = {}
|
|
self.lock = threading.Lock()
|
|
|
|
# For parallel processing
|
|
self.queue = queue.Queue()
|
|
self.results = queue.Queue()
|
|
|
|
def discover_hosts(self):
|
|
"""Discover live hosts in the network range."""
|
|
try:
|
|
logging.info(f"Discovering hosts in {self.network_range}")
|
|
|
|
# ARP scan for local network
|
|
arp_request = scapy.ARP(pdst=self.network_range)
|
|
broadcast = scapy.Ether(dst="ff:ff:ff:ff:ff:ff")
|
|
packets = broadcast/arp_request
|
|
|
|
answered, _ = scapy.srp(packets, timeout=self.timeout, iface=self.interface, verbose=False)
|
|
|
|
for sent, received in answered:
|
|
ip = received.psrc
|
|
mac = received.hwsrc
|
|
self.hosts[ip] = {'mac': mac, 'status': 'up'}
|
|
logging.info(f"Discovered host: {ip} ({mac})")
|
|
|
|
# Additional Nmap scan for service discovery
|
|
nm = nmap.PortScanner()
|
|
nm.scan(hosts=self.network_range, arguments=f'-sn -T4')
|
|
|
|
for host in nm.all_hosts():
|
|
if host not in self.hosts:
|
|
self.hosts[host] = {'status': 'up'}
|
|
logging.info(f"Discovered host: {host}")
|
|
|
|
except Exception as e:
|
|
logging.error(f"Error discovering hosts: {e}")
|
|
|
|
def trace_route(self, target):
|
|
"""Perform traceroute to a target."""
|
|
try:
|
|
hops = []
|
|
for ttl in range(1, self.max_depth + 1):
|
|
pkt = IP(dst=target, ttl=ttl)/ICMP()
|
|
reply = scapy.sr1(pkt, timeout=self.timeout, verbose=False)
|
|
|
|
if reply is None:
|
|
continue
|
|
|
|
if reply.src == target:
|
|
hops.append(reply.src)
|
|
break
|
|
|
|
hops.append(reply.src)
|
|
|
|
return hops
|
|
except Exception as e:
|
|
logging.error(f"Error tracing route to {target}: {e}")
|
|
return []
|
|
|
|
def scan_ports(self, ip):
|
|
"""Scan common ports on a host."""
|
|
try:
|
|
common_ports = [21, 22, 23, 25, 53, 80, 443, 445, 3389]
|
|
open_ports = []
|
|
|
|
for port in common_ports:
|
|
tcp_connect = IP(dst=ip)/TCP(dport=port, flags="S")
|
|
response = scapy.sr1(tcp_connect, timeout=self.timeout, verbose=False)
|
|
|
|
if response and response.haslayer(TCP):
|
|
if response[TCP].flags == 0x12: # SYN-ACK
|
|
open_ports.append(port)
|
|
# Send RST to close connection
|
|
rst = IP(dst=ip)/TCP(dport=port, flags="R")
|
|
scapy.send(rst, verbose=False)
|
|
|
|
return open_ports
|
|
except Exception as e:
|
|
logging.error(f"Error scanning ports for {ip}: {e}")
|
|
return []
|
|
|
|
def worker(self):
|
|
"""Worker function for parallel processing."""
|
|
while True:
|
|
try:
|
|
task = self.queue.get()
|
|
if task is None:
|
|
break
|
|
|
|
ip = task
|
|
hops = self.trace_route(ip)
|
|
ports = self.scan_ports(ip)
|
|
|
|
self.results.queue.put({
|
|
'ip': ip,
|
|
'hops': hops,
|
|
'ports': ports
|
|
})
|
|
|
|
self.queue.task_done()
|
|
except Exception as e:
|
|
logging.error(f"Worker error: {e}")
|
|
self.queue.task_done()
|
|
|
|
def build_topology(self):
|
|
"""Build network topology by tracing routes and scanning hosts."""
|
|
try:
|
|
# Start worker threads
|
|
workers = []
|
|
for _ in range(5): # Number of parallel workers
|
|
t = threading.Thread(target=self.worker)
|
|
t.start()
|
|
workers.append(t)
|
|
|
|
# Add tasks to queue
|
|
for ip in self.hosts.keys():
|
|
self.queue.put(ip)
|
|
|
|
# Add None to queue to stop workers
|
|
for _ in workers:
|
|
self.queue.put(None)
|
|
|
|
# Wait for all workers to complete
|
|
for t in workers:
|
|
t.join()
|
|
|
|
# Process results
|
|
while not self.results.empty():
|
|
result = self.results.get()
|
|
ip = result['ip']
|
|
hops = result['hops']
|
|
ports = result['ports']
|
|
|
|
self.hosts[ip]['ports'] = ports
|
|
if len(hops) > 1:
|
|
self.routes[ip] = hops
|
|
|
|
# Add nodes and edges to graph
|
|
self.graph.add_node(ip, **self.hosts[ip])
|
|
for i in range(len(hops) - 1):
|
|
self.graph.add_edge(hops[i], hops[i + 1])
|
|
|
|
except Exception as e:
|
|
logging.error(f"Error building topology: {e}")
|
|
|
|
def generate_visualization(self):
|
|
"""Generate network topology visualization."""
|
|
try:
|
|
plt.figure(figsize=(12, 8))
|
|
|
|
# Position nodes using spring layout
|
|
pos = nx.spring_layout(self.graph)
|
|
|
|
# Draw nodes
|
|
nx.draw_networkx_nodes(self.graph, pos, node_size=500)
|
|
|
|
# Draw edges
|
|
nx.draw_networkx_edges(self.graph, pos)
|
|
|
|
# Add labels
|
|
labels = {}
|
|
for node in self.graph.nodes():
|
|
label = f"{node}\n"
|
|
if 'ports' in self.hosts[node]:
|
|
label += f"Ports: {', '.join(map(str, self.hosts[node]['ports']))}"
|
|
labels[node] = label
|
|
|
|
nx.draw_networkx_labels(self.graph, pos, labels, font_size=8)
|
|
|
|
# Save visualization
|
|
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
viz_path = os.path.join(self.output_dir, f"topology_{timestamp}.png")
|
|
plt.savefig(viz_path)
|
|
plt.close()
|
|
|
|
logging.info(f"Visualization saved to {viz_path}")
|
|
|
|
except Exception as e:
|
|
logging.error(f"Error generating visualization: {e}")
|
|
|
|
def save_results(self):
|
|
"""Save topology data to JSON file."""
|
|
try:
|
|
os.makedirs(self.output_dir, exist_ok=True)
|
|
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
|
|
results = {
|
|
'timestamp': datetime.now().isoformat(),
|
|
'network_range': self.network_range,
|
|
'hosts': self.hosts,
|
|
'routes': self.routes,
|
|
'topology': {
|
|
'nodes': list(self.graph.nodes()),
|
|
'edges': list(self.graph.edges())
|
|
}
|
|
}
|
|
|
|
output_file = os.path.join(self.output_dir, f"topology_{timestamp}.json")
|
|
with open(output_file, 'w') as f:
|
|
json.dump(results, f, indent=4)
|
|
|
|
logging.info(f"Results saved to {output_file}")
|
|
|
|
except Exception as e:
|
|
logging.error(f"Failed to save results: {e}")
|
|
|
|
def execute(self):
|
|
"""Execute the network mapping process."""
|
|
try:
|
|
logging.info(f"Starting network mapping of {self.network_range}")
|
|
|
|
# Discovery phase
|
|
self.discover_hosts()
|
|
if not self.hosts:
|
|
logging.error("No hosts discovered")
|
|
return
|
|
|
|
# Topology building phase
|
|
self.build_topology()
|
|
|
|
# Generate outputs
|
|
self.generate_visualization()
|
|
self.save_results()
|
|
|
|
logging.info("Network mapping completed")
|
|
|
|
except Exception as e:
|
|
logging.error(f"Error during execution: {e}")
|
|
|
|
def save_settings(network_range, interface, max_depth, output_dir, timeout):
|
|
"""Save settings to JSON file."""
|
|
try:
|
|
os.makedirs(DEFAULT_SETTINGS_DIR, exist_ok=True)
|
|
settings = {
|
|
"network_range": network_range,
|
|
"interface": interface,
|
|
"max_depth": max_depth,
|
|
"output_dir": output_dir,
|
|
"timeout": timeout
|
|
}
|
|
with open(SETTINGS_FILE, 'w') as f:
|
|
json.dump(settings, f)
|
|
logging.info(f"Settings saved to {SETTINGS_FILE}")
|
|
except Exception as e:
|
|
logging.error(f"Failed to save settings: {e}")
|
|
|
|
def load_settings():
|
|
"""Load settings from JSON file."""
|
|
if os.path.exists(SETTINGS_FILE):
|
|
try:
|
|
with open(SETTINGS_FILE, 'r') as f:
|
|
return json.load(f)
|
|
except Exception as e:
|
|
logging.error(f"Failed to load settings: {e}")
|
|
return {}
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Network topology mapping tool")
|
|
parser.add_argument("-r", "--range", help="Network range to scan (CIDR)")
|
|
parser.add_argument("-i", "--interface", help="Network interface to use")
|
|
parser.add_argument("-d", "--depth", type=int, default=5, help="Maximum trace depth")
|
|
parser.add_argument("-o", "--output", default=DEFAULT_OUTPUT_DIR, help="Output directory")
|
|
parser.add_argument("-t", "--timeout", type=int, default=2, help="Timeout for probes")
|
|
args = parser.parse_args()
|
|
|
|
settings = load_settings()
|
|
network_range = args.range or settings.get("network_range")
|
|
interface = args.interface or settings.get("interface")
|
|
max_depth = args.depth or settings.get("max_depth")
|
|
output_dir = args.output or settings.get("output_dir")
|
|
timeout = args.timeout or settings.get("timeout")
|
|
|
|
if not network_range:
|
|
logging.error("Network range is required. Use -r or save it in settings")
|
|
return
|
|
|
|
save_settings(network_range, interface, max_depth, output_dir, timeout)
|
|
|
|
mapper = YggdrasilMapper(
|
|
network_range=network_range,
|
|
interface=interface,
|
|
max_depth=max_depth,
|
|
output_dir=output_dir,
|
|
timeout=timeout
|
|
)
|
|
mapper.execute()
|
|
|
|
if __name__ == "__main__":
|
|
main() |