Skip to content

Repository files navigation

MAXCONN

PyPI Python CI License: MIT

Leia em Português.

Zero-dependency network automation toolkit for Python: SSH/Telnet clients built on raw sockets (no Paramiko/Netmiko/Scrapli), a CLI for day-to-day network engineering tasks, and a themeable interactive terminal.

Quick Start

pip install maxconn
maxconn hosts add olt-01 --host 10.0.0.1 --port 22 --protocol ssh --username admin
maxconn ssh olt-01 --command "show version"

Prefer Python? Same idea, three lines:

import maxconn

with maxconn.connect("192.0.2.10", protocol="ssh", username="admin", password="secret") as conn:
    print(conn.run("display version", prompt_markers=(">", "#")).text)

Why MAXCONN

MAXCONN is meant to grow into a practical toolkit for network engineers and DevOps engineers who automate network tasks: connecting to devices, running commands, reading output, collecting data, validating state, building inventory, and later adding vendor-specific modules.

The project starts with the connection layer. Today MAXCONN has SSH and Telnet clients built on top of sockets, without using Paramiko, Netmiko, Scrapli, or Telnetlib as runtime clients.

Package on PyPI: https://pypi.org/project/maxconn/

Installation

Regular install:

pip install maxconn

For SSH:

pip install "maxconn[ssh]"

Telnet does not pull extra runtime dependencies. SSH uses cryptography through the ssh extra. Paramiko is test-only and is used to run a local SSH server for integration tests.

Development install:

git clone https://github.com/mmaxjr/maxconn
cd maxconn
pip install -e ".[dev]"
pytest -v
ruff check src tests

Current development version: 0.3.1.

Module Status

Area Status Interface
SSH/Telnet basic usage Python API and CLI
Ping/scan/traceroute basic usage Python API and CLI
MTR basic live table Python API and CLI
SNMP v2c GET/WALK basic usage Python API and CLI
SFTP basic file operations Python API and CLI
HTTP/FTP small client Python API

CLI Reference

Connection & saved hosts

maxconn ssh 192.0.2.10 --username admin --password secret --command "show version"
maxconn telnet 192.0.2.20 --username admin --password secret --command "show status"
maxconn ssh olt-01                                  # interactive session (no --command)
maxconn hosts add olt-01 --host 10.0.0.1 --port 22 --protocol ssh --username admin --profile huawei --tags olt,pop-centro
maxconn hosts list
maxconn hosts list --json
maxconn hosts show olt-01
maxconn hosts edit olt-01 --host 10.0.0.2           # alias: hosts set
maxconn hosts remove olt-01
maxconn hosts test olt-01
maxconn hosts test --all
maxconn hosts test --tag core
maxconn hosts export --file hosts-backup.json
maxconn hosts import --file hosts-backup.json
maxconn hosts run --all --command "show version"
maxconn hosts run --tag core --command "show version" --json
maxconn hosts recent
maxconn hosts save-recent 1 --name olt-01 --profile huawei --tags olt
maxconn start                                       # themeable interactive terminal

Shell completion (bash/zsh/PowerShell) for commands and flags:

source <(maxconn completion bash)     # add to ~/.bashrc
source <(maxconn completion zsh)      # add to ~/.zshrc
maxconn completion powershell | Out-String | Invoke-Expression   # add to $PROFILE

Local defaults, so you don't have to repeat --timeout/--concurrency/--workers/--ports on every call:

maxconn config set timeout 5
maxconn config set ports 22,80,443
maxconn config get timeout
maxconn config list
maxconn config unset timeout

Config backup & diff

maxconn backup olt-01                                       # uses the saved host's profile for the default command
maxconn backup 192.0.2.10 --username admin --password secret --command "show running-config" --to backup.cfg
maxconn diff backup-2026-08-01.cfg backup-2026-08-16.cfg     # exit code 1 if they differ
maxconn diff backup-2026-08-01.cfg backup-2026-08-16.cfg --json

Backups default to ~/.maxconn/backups/<host>/<timestamp>.cfg unless --to PATH is given. The default backup command is looked up from the saved host's --profile (cisco, huawei, mikrotik); pass --command explicitly for anything else.

Inventory

maxconn inventory
maxconn inventory --json
maxconn inventory --output csv --export inventory.csv
maxconn inventory --reconcile 192.168.0.0/24          # planned (saved hosts) vs. provisioned (live scan)
maxconn inventory --reconcile 192.168.0.0/24 --json

--reconcile runs a live discover scan of the given network and reports saved hosts that were not seen reachable ("documented but unreachable") alongside reachable hosts that aren't in your saved inventory ("undocumented"). Exit code is 1 when there's drift, 0 when everything matches - useful for scripted checks.

Discovery

maxconn discover 192.168.0.0/24
maxconn discover 192.168.0.0/24 --ports 80,443 --json
maxconn discover 192.168.0.0/24 --only-open
maxconn discover 192.168.0.0/24 --save-found --name-prefix sw --tags discovered,lab
maxconn discover 10.0.0.0/20 --confirm             # required above the host-count threshold

Diagnostics

maxconn ping 192.0.2.1
maxconn ping 192.0.2.1 --output json --export ping.json
maxconn scan 192.0.2.1 --ports 22,23,80,443
maxconn traceroute 8.8.8.8
maxconn mtr 8.8.8.8 --count 5 --interval 1
maxconn snmp get 192.0.2.1 1.3.6.1.2.1.1.5.0 --community public
maxconn snmp walk 192.0.2.1 1.3.6.1.2.1.1 --community public
maxconn doctor
maxconn doctor --network                            # + DNS/gateway/internet/PyPI-version checks
maxconn history list --limit 20 --since today
maxconn history show 1
maxconn history replay 1
maxconn history clear
maxconn selftest
maxconn config set audit_log on                     # persist the audit trail to ~/.maxconn/audit.jsonl
maxconn audit tail
maxconn audit tail -n 50 --json
maxconn config set update_notify on                 # a passive "new version available" hint after commands

update_notify checks PyPI at most once every 24h (cached in ~/.maxconn/update_check.json) and only prints a one-line notice to stderr when a newer version exists - it never blocks or fails a command, even if the check itself fails.

File transfer (SFTP)

maxconn sftp ls 192.0.2.10 /configs --username admin --password secret
maxconn sftp get 192.0.2.10 /remote/startup.cfg ./startup.cfg --username admin --password secret
maxconn sftp put 192.0.2.10 ./backup.cfg /remote/backup.cfg --username admin --password secret
maxconn sftp stat 192.0.2.10 /remote/startup.cfg --username admin --password secret
maxconn sftp mkdir 192.0.2.10 /remote/new-folder --username admin --password secret
maxconn sftp rm 192.0.2.10 /remote/old.cfg --username admin --password secret
maxconn sftp rename 192.0.2.10 /remote/a.cfg /remote/b.cfg --username admin --password secret

Saved hosts live in ~/.maxconn/hosts.json. Recently used hosts live in ~/.maxconn/seen_hosts.json, without passwords. To save a password locally, use --save-password explicitly; it is never printed, and hosts list only shows a yes/no indicator for whether one is saved. Local command history lives in ~/.maxconn/history.jsonl; commands containing words such as password, token, or secret are stored with redaction.

To enter a device terminal, run maxconn ssh NAME or maxconn telnet NAME without --command. Inside the visual shell opened by maxconn start, use ssh NAME, telnet NAME, or open NAME.

Python API

Basic Usage

Telnet:

import maxconn

with maxconn.connect(
    "192.0.2.20",
    protocol="telnet",
    username="admin",
    password="secret",
) as conn:
    result = conn.run("show status", prompt_markers=(">", "#"))
    print(result.text)

SSH:

import maxconn

with maxconn.connect(
    "192.0.2.30",
    protocol="ssh",
    username="admin",
    password="secret",
) as conn:
    result = conn.run("show version", prompt_markers=(">", "#"))
    print(result.text)

For lower-level use, Connection.send(), Connection.recv(), Connection.read_until(), and Connection.send_command() are still available.

Command Result

Connection.run() returns a result object:

result = conn.run("display version", prompt_markers=(">", "#"))

print(result.command)
print(result.text)
print(result.bytes)
print(result.elapsed)
print(result.exit_status)
print(result.ok)

result.ok is true when exit_status is None or 0. Interactive CLI sessions, such as Telnet and shell-style SSH, usually do not provide an exit status, so None is expected.

Expect

For prompt-based automation, use ExpectSession directly:

from maxconn.automation import ExpectSession, PromptProfile

expect = ExpectSession(conn, prompt_markers=PromptProfile.CISCO)
output = expect.run("show running-config", timeout=20.0)

ExpectSession handles the common parts of a network device CLI:

  • waits for prompts
  • strips command echo
  • answers simple pagination markers such as --More--
  • includes partial output in timeout errors
  • answers simple confirmation prompts such as [Y/N]

Sessions and Ping

SessionManager controls named connections:

import maxconn

manager = maxconn.SessionManager(defaults={"protocol": "ssh", "username": "admin"})
conn = manager.connect("olt-01", "192.0.2.10", password="secret")
result = conn.run("display version", prompt_markers=(">", "#"))
manager.close_all()

Basic ping:

import maxconn

result = maxconn.ping("192.0.2.1")
print(result.reachable)

TCP scan:

import maxconn

for result in maxconn.scan("192.0.2.1", ports=[22, 23, 80, 443]):
    print(result.port, "open" if result.open else "closed")

Subnet discovery:

import maxconn

for host in maxconn.discover("192.168.0.0/24"):
    if host.reachable:
        print(host.host, host.open_ports, host.banner)

In the terminal, maxconn discover NETWORK/CIDR tests common TCP ports across the subnet. The default ports include at least 80 and 443, plus common network ports such as SSH, Telnet, SNMP, MikroTik, and alternate HTTP/HTTPS. Use --ports to limit or change the list. Networks above the host-count threshold require --confirm (or confirm=True in Python).

Traceroute and mini MTR:

import maxconn

trace = maxconn.traceroute("8.8.8.8")
for hop in trace.hops:
    print(hop.hop, hop.address)

report = maxconn.mtr("8.8.8.8", count=5)
print(report.loss_percent, report.avg)

In the terminal, maxconn mtr HOST runs continuously and refreshes a table per hop. Stop it with Ctrl+C. For a bounded run, pass --count. Hops that do not answer are shown as No response from host, so the path is not hidden and the internal * marker does not leak into the table. By default the route is discovered once and known hops are measured every round, which makes refreshes closer to WinMTR. On networks with many silent hops, increase --trace-timeout. To refresh the route periodically, use --rediscover-every N. For automation and reports, use --json, --output json, --export path.txt, and --no-clear.

Examples

The examples/ folder has small scripts that can be used as starting points:

  • ssh_run_command.py
  • sftp_backup.py
  • mtr_report.py
  • snmp_walk.py
  • scan_ports.py

Before publishing a version, run:

python scripts/release_check.py

HTTP and FTP

Basic HTTP/HTTPS:

from maxconn.protocol.http import HTTPClient

response = HTTPClient(timeout=5.0).get("https://example.com")
print(response.status_code)
print(response.text)

Basic FTP:

from maxconn.protocol.ftp import FTPClient

with FTPClient.connect(
    "192.0.2.40",
    username="user",
    password="secret",
) as ftp:
    print(ftp.list())
    data = ftp.download("backup.cfg")

Initial SFTP:

import maxconn

sftp = maxconn.connect_sftp(
    "192.0.2.40",
    username="user",
    password="secret",
)
try:
    print(sftp.listdir("/configs"))
    print(sftp.stat("/configs/startup.cfg"))
    sftp.download("/configs/startup.cfg", "startup.cfg")
    sftp.upload("backup.cfg", "/configs/backup.cfg")
    sftp.mkdir("/configs/archive")
    sftp.rename("/configs/backup.cfg", "/configs/archive/backup.cfg")
    sftp.remove("/configs/archive/old.cfg")
finally:
    sftp.close()

Basic SNMP v2c:

from maxconn.protocol.snmp import SNMPClient

snmp = SNMPClient("192.0.2.1", community="public")
hostname = snmp.get("1.3.6.1.2.1.1.5.0")
print(hostname.value)

for item in snmp.walk("1.3.6.1.2.1.1"):
    print(item.oid, item.value)

Timeouts

connect() accepts separate timeouts:

conn = maxconn.connect(
    "192.0.2.30",
    protocol="ssh",
    username="admin",
    password="secret",
    connect_timeout=5.0,
    auth_timeout=10.0,
    command_timeout=5.0,
    prompt_timeout=10.0,
)

The older timeout= argument still works. When connect_timeout or auth_timeout is not provided, timeout= is used as the default.

Logging

Command execution writes audit events through the maxconn.audit logger:

import logging

logging.basicConfig(level=logging.INFO)

Command fragments with words such as password, secret, token, or key are redacted before logging.

Errors

Use the project exception hierarchy:

import maxconn

try:
    with maxconn.connect(
        "192.0.2.30",
        protocol="ssh",
        username="admin",
        password="bad-password",
    ) as conn:
        print(conn.run("show status", prompt_markers=(">", "#")).text)
except maxconn.AuthenticationError:
    print("Login failed")
except maxconn.ConnectionTimeoutError:
    print("Connection timed out")
except maxconn.ProtocolError as exc:
    print(f"Protocol problem: {exc}")
except maxconn.MaxConnError as exc:
    print(f"maxconn error: {exc}")

Project Direction

  • Do not turn the project into a wrapper around Paramiko, Netmiko, Scrapli, or Telnetlib.
  • Keep optional dependencies behind extras.
  • Keep raw bytes available for code that needs them.
  • Keep the common API simple.
  • Test against local Telnet and SSH servers when it makes sense.
  • Publish new versions to PyPI by tag, using GitHub Actions and Trusted Publishing.

About

Zero-dependency Python network automation toolkit: SSH/Telnet over raw sockets, a CLI (hosts, discover, doctor, SFTP, SNMP, MTR), and a themeable interactive terminal.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages