Files
FunguyBot/plugins/subnet.py
T

258 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
plugins/subnet.py Subnet calculator and network splitting plugin for Funguy Bot.
Provides the following commands:
!subnet info <CIDR> Show detailed info about a network
!subnet split <CIDR> --prefix <N> Split network into smaller subnets (new prefix length)
!subnet split <CIDR> --diff <N> Split network into equal subnets (prefixlen delta)
!subnet adjacent <CIDR> <count> Show given network and next <count> 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 humanreadable 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 <info|split|adjacent> ...\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 = "<details><summary><strong>!subnet</strong> Subnet calculator and exploration</summary>\n"
html += "<p>Calculate subnet details, split networks, or enumerate adjacent subnets.</p>\n"
html += "<h4>Commands</h4>\n"
html += "<ul>\n"
html += "<li><b>info</b> Show detailed info for a network<br>\n"
html += "<code>!subnet info &lt;CIDR&gt;</code><br>\n"
html += "Example: <code>!subnet info 192.168.1.0/24</code></li>\n"
html += "<li><b>split</b> Split a network into smaller subnets<br>\n"
html += "<code>!subnet split &lt;CIDR&gt; --prefix &lt;new_prefix&gt;</code><br>\n"
html += "Example: <code>!subnet split 192.168.1.0/24 --prefix 26</code><br>\n"
html += "<i>Alternatively, use --diff to split by prefix delta:</i><br>\n"
html += "<code>!subnet split &lt;CIDR&gt; --diff &lt;delta&gt;</code><br>\n"
html += "Example: <code>!subnet split 10.0.0.0/16 --diff 2</code> (creates 4 subnets)</li>\n"
html += "<li><b>adjacent</b> Show the current network and adjacent ones<br>\n"
html += "<code>!subnet adjacent &lt;CIDR&gt; &lt;count&gt;</code><br>\n"
html += "Example: <code>!subnet adjacent 192.168.4.0/26 3</code></li>\n"
html += "</ul>\n"
html += "<h4>Notes</h4>\n"
html += "<ul>\n"
html += "<li>IPv4 /31 and /32 networks show both addresses as usable (RFC 3021).</li>\n"
html += "<li>IPv6 networks list all addresses as hosts (no broadcast).</li>\n"
html += "</ul>\n"
html += "</details>"
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 <CIDR> --prefix <new_prefix> OR --diff <delta>"
)
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 <CIDR> --prefix <number>"
)
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 <CIDR> --diff <delta>"
)
return
result = _split_by_diff(net, diff)
else:
await bot.api.send_text_message(
room.room_id,
"You must provide either --prefix <N> or --diff <N> 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 <CIDR> <count>"
)
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__ = """
<details>
<summary><strong>!subnet</strong> Subnet calculator and exploration</summary>
<p>Calculate subnet details, split networks, or enumerate adjacent subnets.</p>
<ul>
<li><code>!subnet info &lt;CIDR&gt;</code> Show detailed info for a network<br>
Example: <code>!subnet info 192.168.1.0/24</code></li>
<li><code>!subnet split &lt;CIDR&gt; --prefix &lt;new_prefix&gt;</code> Split into smaller subnets<br>
Example: <code>!subnet split 192.168.1.0/24 --prefix 26</code></li>
<li><code>!subnet split &lt;CIDR&gt; --diff &lt;delta&gt;</code> Split by prefix delta<br>
Example: <code>!subnet split 10.0.0.0/16 --diff 2</code></li>
<li><code>!subnet adjacent &lt;CIDR&gt; &lt;count&gt;</code> Show adjacent networks<br>
Example: <code>!subnet adjacent 192.168.4.0/26 3</code></li>
</ul>
</details>
"""