ⓘ Accuracy Disclaimer
Technical content in this article was researched and compiled with AI assistance under the direct supervision of the author. While every effort has been made to ensure accuracy, errors may still be present. If you spot an inaccuracy or have a correction, the author welcomes feedback — please reach out at github@it-solutionsusa.com or open an issue at github.com/ikonstas70.
Date: May 2, 2026
Author: IT Solutions USA
Category: Cisco / Network Automation
Tags: Cisco, CSR1000v, Network Automation, Flask, socat, Cloudflare, Tailscale, IaC, ESXi, DevOps
Cisco CSR1000v instances running on ESXi expose their consoles via serial-over-network — Telnet sessions mapped to incremented ports (2301–2320). This is functional for manual access but completely incompatible with automation workflows. You cannot script reliably against raw Telnet: timing is unpredictable, output is noisy with escape characters, and there is no authentication or structured response format.
The goal was clear: abstract these legacy serial interfaces into a modern, programmatic API without introducing any external exposure to the underlying infrastructure.
The architecture is built on a "Total Isolation" principle. The ESXi hypervisor has no default gateway — it connects exclusively to a management host via a Layer 3 Point-to-Point link. This means the router fleet cannot be reached from the public internet by any direct path. All management traffic stays within a controlled, bounded network segment.
The routers themselves are addressed through ESXi's serial-over-network feature, with each CSR1000v mapped to a specific Telnet port in the 2301–2320 range. This is the physical foundation everything else builds on.
The stack is a multi-tier bridge that converts stateless HTTP requests into stateful terminal sessions. It has three core components working in sequence.
A Python Flask service running on port 8080 acts as the entry point. It validates the X-API-KEY header, checks that the requested port falls within the ALLOWED_PORTS list, parses the JSON payload, and triggers the automation engine.
The core automation engine manages the inherent fragility of virtual serial consoles through a precisely timed execution sequence: Connect → Sleep → Wake → Sleep → Execute → Sleep → Exit. This timing prevents the ESXi serial buffer from dropping characters.
shell_cmd = f'(sleep 1; printf "\\r\\n"; sleep 1; \
printf "terminal length 0\\r\\n"; sleep 1; \
printf "{command}\\r\\n"; sleep 2; \
printf "exit\\r\\n") | telnet {ROUTER_IP} {port}'
A dedicated telnet.sh script uses socat to relay TCP traffic from local ports (2301–2310) to the corresponding ports on the ESXi hypervisor at <esxi-host-ip>. The fork option handles multiple simultaneous sessions without crashing.
The socat gateway supports three distinct modes to balance accessibility with security requirements:
The final piece of the stack enables global management without opening a single inbound firewall port. A Cloudflare Tunnel creates a secure, outbound-only connection from the Mac Mini to Cloudflare's edge, assigning a public HTTPS URL to the local Flask service.
This means an engineer anywhere in the world can reach the API through a single curl command:
curl -X POST https://your-tunnel.trycloudflare.com/telnet \
-H "X-API-KEY: YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"port": 2301, "command": "show ip int brief"}'
The response comes back as clean, structured JSON — the raw Telnet noise stripped by the scrub_output() function:
{
"port": 2301,
"router": "R1",
"output": "Interface IP-Address OK? Method Status Protocol\n
GigabitEth0 10.0.0.1 YES NVRAM up up"
}
A critical design distinction: Cloudflare and Tailscale solve different problems in this stack and do not overlap.
Cloudflare Tunnel — Public Web Access Layer
Targets the Flask API at localhost:8080. Forwards HTTPS from Cloudflare's global edge to the local service. Adds WAF and Zero Trust protection.
Tailscale / WireGuard — Private Network Fabric
Targets raw console ports (2301–2310) via socat. Provides out-of-band engineering access across the private mesh VPN without touching the public API path.
Together they provide defense-in-depth: an encrypted, authenticated public surface for automated operations, and an encrypted, private surface for direct engineering access.
Because the router fleet is now exposed as a set of RESTful endpoints, it becomes immediately compatible with modern automation and AI tooling — without any modification to the underlying stack.
Compatible integrations include:
- Ansible — inventory-driven playbooks calling the Flask API.
- Python scripts — standard requests library, no Telnet libraries required.
- AI agents — LLM-driven network operations using the REST API as a tool.
- Monitoring platforms — polling router state via scheduled API calls.
The Alexander Full-Stack Project successfully solves the "last mile" automation problem for virtualized Cisco hardware. By layering Flask (logic), socat (transport), Cloudflare (global access), and Tailscale (private fabric), it transforms 20 legacy serial consoles into a secure, scalable, API-driven fleet.
The architecture is deliberately modular. Adding a new router means updating a port in the socat range and a value in the Flask allowlist. Changing the physical transport — from Telnet to SSH, or from a Point-to-Point link to a VPN — means editing telnet.sh without touching the core API. This separation of concerns is what makes it a professional-grade template rather than a one-off script.
You no longer manage consoles — you manage API endpoints. The routers are serverless.