#!/usr/bin/env python3 """ plugins/subnet.py – Subnet calculator and network splitting plugin for Funguy Bot. Provides the following commands: !subnet info – Show detailed info about a network !subnet split --prefix – Split network into smaller subnets (new prefix length) !subnet split --diff – Split network into equal subnets (prefixlen delta) !subnet adjacent – Show given network and next adjacent ones !subnet help – Display this help Examples: !subnet info 192.168.4.0/26 !subnet split 192.168.4.0/24 --prefix 26 !subnet split 10.0.0.0/16 --diff 2 !subnet adjacent 192.168.4.0/26 3 """ import ipaddress import sys from typing import Union # ------------------------------- helper functions -------------------------------- def _fmt_subnet_info(net: Union[ipaddress.IPv4Network, ipaddress.IPv6Network]) -> str: """Return a human‑readable string with all relevant subnet details.""" nw = net.network_address bc = net.broadcast_address if hasattr(net, "broadcast_address") else None total = net.num_addresses if net.version == 4: if net.prefixlen == 32: usable_count = 1 first = last = nw elif net.prefixlen == 31: usable_count = 2 first = nw last = bc else: usable_count = max(0, total - 2) first = nw + 1 if usable_count > 0 else None last = bc - 1 if usable_count > 0 else None else: hosts_iter = net.hosts() try: first = next(hosts_iter) last = net.network_address + (total - 1) usable_count = total except StopIteration: first = last = None usable_count = 0 lines = [ f"CIDR: {net.with_prefixlen}", f"Network: {nw}", f"Broadcast: {bc if bc is not None else 'N/A'}", f"Netmask: {net.netmask if hasattr(net, 'netmask') else 'N/A'}", f"Wildcard Mask: {net.hostmask if hasattr(net, 'hostmask') else 'N/A'}", f"Total IPs: {total}", f"Usable Hosts: {usable_count}", ] if first is not None and last is not None: lines.append(f"First Usable: {first}") lines.append(f"Last Usable: {last}") lines.append(f"Usable Range: {first} - {last}") return "\n".join(lines) def _split_by_prefix(net, new_prefix: int) -> str: if new_prefix < net.prefixlen: return f"[!] New prefix /{new_prefix} is smaller than current prefix /{net.prefixlen}. Cannot split." out = [f"# Splitting {net.with_prefixlen} into /{new_prefix} subnets:"] for i, sub in enumerate(net.subnets(new_prefix=new_prefix)): out.append(f"\n-- Subnet #{i+1} --") out.append(_fmt_subnet_info(sub)) return "\n".join(out) def _split_by_diff(net, diff: int) -> str: new_prefix = net.prefixlen + diff return _split_by_prefix(net, new_prefix) def _adjacent_networks(net, count: int) -> str: out = [f"# Adjacent networks of size /{net.prefixlen} (starting at {net.with_prefixlen}):"] current = net for i in range(count + 1): out.append(f"\n-- Adjacent #{i} --") out.append(_fmt_subnet_info(current)) try: next_net_addr = current.network_address + current.num_addresses current = ipaddress.ip_network(f"{next_net_addr}/{current.prefixlen}", strict=True) except ValueError: out.append("[!] Reached address space limit.") break return "\n".join(out) # ------------------------------- bot plugin entry ------------------------------- async def handle_command(room, message, bot, prefix, config): import simplematrixbotlib as botlib match = botlib.MessageMatch(room, message, bot, prefix) if not (match.is_not_from_this_bot() and match.prefix() and match.command("subnet")): return args = match.args() if not args: await bot.api.send_text_message( room.room_id, "Usage: !subnet ...\n" " !subnet help – show full help" ) return subcmd = args[0].lower() # --- help --- if subcmd in ("help", "-h", "--help"): # Send nicely formatted HTML in a details tag via markdown html = "
!subnet – Subnet calculator and exploration\n" html += "

Calculate subnet details, split networks, or enumerate adjacent subnets.

\n" html += "

Commands

\n" html += "
    \n" html += "
  • info – Show detailed info for a network
    \n" html += "!subnet info <CIDR>
    \n" html += "Example: !subnet info 192.168.1.0/24
  • \n" html += "
  • split – Split a network into smaller subnets
    \n" html += "!subnet split <CIDR> --prefix <new_prefix>
    \n" html += "Example: !subnet split 192.168.1.0/24 --prefix 26
    \n" html += "Alternatively, use --diff to split by prefix delta:
    \n" html += "!subnet split <CIDR> --diff <delta>
    \n" html += "Example: !subnet split 10.0.0.0/16 --diff 2 (creates 4 subnets)
  • \n" html += "
  • adjacent – Show the current network and adjacent ones
    \n" html += "!subnet adjacent <CIDR> <count>
    \n" html += "Example: !subnet adjacent 192.168.4.0/26 3
  • \n" html += "
\n" html += "

Notes

\n" html += "
    \n" html += "
  • IPv4 /31 and /32 networks show both addresses as usable (RFC 3021).
  • \n" html += "
  • IPv6 networks list all addresses as hosts (no broadcast).
  • \n" html += "
\n" html += "
" await bot.api.send_markdown_message(room.room_id, html) return # --- info (or a CIDR passed directly) --- if subcmd == "info" or "/" in subcmd: cidr = args[1] if subcmd == "info" else subcmd try: net = ipaddress.ip_network(cidr, strict=False) except ValueError as e: await bot.api.send_text_message(room.room_id, f"[!] Invalid CIDR: {e}") return await bot.api.send_text_message(room.room_id, _fmt_subnet_info(net)) return # --- split --- if subcmd == "split": if len(args) < 2: await bot.api.send_text_message( room.room_id, "Usage: !subnet split --prefix OR --diff " ) return cidr = args[1] try: net = ipaddress.ip_network(cidr, strict=False) except ValueError as e: await bot.api.send_text_message(room.room_id, f"[!] Invalid CIDR: {e}") return if "--prefix" in args: try: idx = args.index("--prefix") new_prefix = int(args[idx + 1]) except (ValueError, IndexError): await bot.api.send_text_message( room.room_id, "Usage: !subnet split --prefix " ) return result = _split_by_prefix(net, new_prefix) elif "--diff" in args: try: idx = args.index("--diff") diff = int(args[idx + 1]) except (ValueError, IndexError): await bot.api.send_text_message( room.room_id, "Usage: !subnet split --diff " ) return result = _split_by_diff(net, diff) else: await bot.api.send_text_message( room.room_id, "You must provide either --prefix or --diff for split." ) return await bot.api.send_text_message(room.room_id, result) return # --- adjacent --- if subcmd == "adjacent": if len(args) < 3: await bot.api.send_text_message( room.room_id, "Usage: !subnet adjacent " ) return cidr = args[1] try: net = ipaddress.ip_network(cidr, strict=False) except ValueError as e: await bot.api.send_text_message(room.room_id, f"[!] Invalid CIDR: {e}") return try: count = int(args[2]) except ValueError: await bot.api.send_text_message( room.room_id, "Count must be an integer." ) return result = _adjacent_networks(net, count) await bot.api.send_text_message(room.room_id, result) return # Unknown subcommand await bot.api.send_text_message( room.room_id, f"Unknown subcommand '{subcmd}'. Use !subnet help to see available commands." ) # Plugin metadata __version__ = "1.0.1" __author__ = "Funguy Bot" __description__ = "Subnet calculator, splitter, and adjacent network enumerator" __help__ = """
!subnet – Subnet calculator and exploration

Calculate subnet details, split networks, or enumerate adjacent subnets.

  • !subnet info <CIDR> – Show detailed info for a network
    Example: !subnet info 192.168.1.0/24
  • !subnet split <CIDR> --prefix <new_prefix> – Split into smaller subnets
    Example: !subnet split 192.168.1.0/24 --prefix 26
  • !subnet split <CIDR> --diff <delta> – Split by prefix delta
    Example: !subnet split 10.0.0.0/16 --diff 2
  • !subnet adjacent <CIDR> <count> – Show adjacent networks
    Example: !subnet adjacent 192.168.4.0/26 3
"""