Skip to main content

routeros-client

PyPI Python CI Licence MIT

Client Python pour MikroTik RouterOS : une seule installation, quatre transports.

Transports interchangeables exposant exactement la même surface d'API — votre code métier ne change pas quand vous changez de transport.

Transport Classe Port par défaut
API binaire (défaut) Api 8728
API binaire sur TLS Api(use_ssl=True) 8729
SSH SshApi 22
REST / JSON (RouterOS v7) RestApi 80 / 443

Compatible RouterOS v6.x et v7.x, Python 3.9+. Validé en conditions réelles sur RouterOS 7.23.3 (stable).


Installation

pip install routeros-client

C'est tout : les quatre transports sont disponibles d'emblée. paramiko, requis par le transport SSH, est installé automatiquement et chargé paresseusement — un programme qui n'utilise que l'API binaire ne paie pas son coût de démarrage.

pip install "routeros-client[ssh]" reste accepté, et est strictement équivalent : l'extra est conservé comme alias sans effet pour ne casser aucun script existant.

Nom d'installation ≠ nom d'import

Le nom routeros-api étant déjà occupé sur PyPI par un autre projet, la distribution s'appelle routeros-client. Les deux noms d'import fonctionnent :

import routeros_client        # nom canonique, aligné sur la distribution
import routeros_api           # alias de compatibilité, pleinement supporté

Ce ne sont pas deux copies mais le même objet module : isinstance, attributs privés et monkeypatching se comportent à l'identique dans les deux cas. Le code écrit pour les versions précédentes continue donc de fonctionner sans la moindre modification.

📘 Guide d'utilisation complet : GUIDE.md.


Démarrage rapide

from routeros_api import connect

# Transport API binaire — le comportement par défaut
api = connect("192.168.88.1", "admin", "secret")

print(api.get_identity())                       # 'MikroTik'
for iface in api.print("/interface", proplist="name,type,running"):
    print(iface["name"], iface["type"], iface["running"])

api.close()

Avec fermeture garantie :

from routeros_api import api_session

with api_session("192.168.88.1", "admin", "secret") as api:
    adresses = api.get_resource("/ip/address").find(disabled=False)

Vue « menu » : ResourceProxy

fw = api.get_resource("/ip/firewall/address-list")

fw.add(list="blocked", address="203.0.113.7", comment="abus")
fw.find(list="blocked")                          # liste de dicts
fw.find_one(address="203.0.113.7")               # un dict ou None
fw.remove_where(list="blocked")                  # suppression par filtre

Idempotence

# Crée si absent, met à jour sinon. Retourne (.id, created)
item_id, cree = api.ensure(
    "/ip/firewall/address-list",
    {"list": "blocked", "address": "203.0.113.7"},   # critère de recherche
    {"comment": "mis à jour"},                       # valeurs à appliquer
)

Choisir un transport

Le transport API binaire est celui par défaut : le plus rapide, et le seul à savoir multiplexer (pipelining, listen(), cancel()).

from routeros_api import connect, ssh_session, auto_connect

api = connect(ip, "admin", pwd)                              # API binaire (défaut)
api = connect(ip, "admin", pwd, use_ssl=True)                # API sur TLS (8729)
ros = connect(ip, "admin", pwd, transport="ssh",             # SSH
              key_filename="~/.ssh/id_ed25519")

# Bascule automatique : prend l'API si elle répond, sinon SSH
ros = auto_connect(ip, "admin", pwd, order=("api", "ssh"))

Le code qui suit est identique quel que soit le transport :

with ssh_session(ip, "admin", password=pwd) as ros:
    print(ros.get_identity())
    ros.add("/ip/address", address="10.0.0.1/24", interface="ether1")
    for a in ros.get_resource("/ip/address").find():
        print(a[".id"], a["address"])

Quand utiliser SSH plutôt que l'API

  • le service API est désactivé sur l'équipement, mais SSH est ouvert ;
  • vous voulez un export de configuration : /export ne renvoie rien via l'API binaire sur RouterOS v7 (limite de RouterOS, pas de la bibliothèque), alors que SshApi.export() fonctionne ;
  • vous devez lancer des commandes hors du périmètre de l'API (scripts, /tool fetch, sauvegardes) : ros.run("/system script run mon-script") ;
  • vous voulez transférer des fichiers : ros.sftp_get() / ros.sftp_put().

Fonctionnement interne : les commandes protocolaires sont traduites en ligne de commande RouterOS, exécutées dans un canal exec (pas d'analyse d'invite), puis la sortie est re-parsée en enregistrements identiques à ceux de l'API. Trois formats sont tentés, du plus fidèle au plus tolérant : :serialize to=json (v7, .id inclus) → as-value (v6/v7) → print terse. Le mode retenu est mémorisé après la première commande.

Limites du SSH : pas de multiplexage — batch(pipeline=True) retombe en séquentiel, cancel() est sans objet, listen() est émulé par print follow. Compter ~2 à 5 ms de surcoût par commande.


Performance

Pipelining — le gain le plus important

Envoie N commandes en une seule trame et relit les réponses par tag : N allers-retours réseau deviennent 1.

with api.pipeline() as p:
    for ip_bloquee in blocklist:                 # 5 000 entrées
        p.add("/ip/firewall/address-list/add",
              "=list=blocked", f"=address={ip_bloquee}")
print(len(p.responses))

# ou directement sur les écritures en masse
api.bulk_add("/ip/firewall/address-list", items, pipeline=True)

Mesuré sur RouterOS 7.23.3 en LAN, 40 ajouts : 145 ms → 37 ms (×3,9). Le gain croît avec la latence : sur un lien WAN à 20 ms, 200 commandes passent d'environ 4 s à 0,1 s.

À ne pas utiliser pour des commandes qui coupent la session (/system/reboot) ni pour des séquences dont l'ordre d'exécution importe : RouterOS traite les commandes taguées concurremment.

Autres leviers

# Réduire le volume transféré : le routeur n'envoie que les champs demandés
api.print("/ip/dhcp-server/lease", proplist=".id,address,mac-address")

# Mémoire constante sur les très grosses tables (aucune matérialisation)
for bail in api.iter_print("/ip/dhcp-server/lease", proplist="address,mac-address"):
    traiter(bail)

TCP_NODELAY est activé par défaut (jusqu'à ~40 ms de latence évités par commande) et la lecture du tampon est en O(n) au lieu de O(n²).

paramiko est importé paresseusement, à la première connexion SSH : import routeros_client coûte 475 ms au lieu de 982 ms si vous n'utilisez que l'API — bien qu'il soit toujours installé.


Points d'attention en production

api-ssl (8729) sans certificat

Sans certificat configuré — le cas par défaut — RouterOS ne propose que des suites anonymes (ADH-AES256-SHA256), que Python refuse : le handshake échoue. La bibliothèque les réactive automatiquement quand ssl_verify=False :

api = connect(ip, "admin", pwd, use_ssl=True, ssl_verify=False)   # fonctionne

Le chiffrement reste actif mais le pair n'est pas authentifié. Pour une vraie sécurité, installez un certificat sur le routeur et gardez ssl_verify=True.

Clé d'hôte SSH

strict_host_key=True par défaut : la clé du routeur doit être connue (known_hosts). Pour un premier contact en laboratoire, strict_host_key=False désactive la vérification — un avertissement est journalisé, et vous devenez vulnérable à une attaque active.

Erreurs

from routeros_api import RouterOSCommandError, RouterOSConnectionError, RouterOSAuthError

try:
    api.add("/ip/address", address="pas-une-ip")
except RouterOSCommandError as exc:
    print(exc, exc.category)

# Variante sans exception
resp = api.talk("/ip/address/print", raise_on_trap=False)
if not resp.ok:
    print(resp.error_message)

Robustesse

api = connect(ip, "admin", pwd,
              timeout=10,              # timeout socket
              auto_reconnect=True,     # reconnexion transparente
              enable_resilience=True,  # retry avec backoff sur erreur réseau
              rate_limit=20)           # 20 commandes/s maximum

enable_resilience rejoue la commande après une coupure. Pour les commandes non idempotentes (add), préférez ensure().


Développement et tests

python -m venv .venv
.venv/bin/activate                       # Windows : .\.venv\Scripts\Activate.ps1
pip install -e ".[dev]"

# Hors-ligne — 105 tests, aucun routeur nécessaire
python -m unittest test_routeros_api -v

# Contre un routeur réel — 62 tests (ÉCRIT sur l'équipement : laboratoire uniquement)
ROS_HOST=192.168.88.1 ROS_USER=admin ROS_PASSWORD=secret python test_live_router.py

La campagne hors-ligne simule le protocole binaire (faux socket) et le transport SSH (faux backend) : elle couvre l'encodage/décodage, les parseurs CLI, la traduction des commandes, le quoting anti-injection, l'équivalence des deux noms d'import et chaque régression corrigée (tests test_bugN_…).

La campagne réelle couvre les quatre transports, les écritures, listen(), export(), ping_host(), le pipelining, les bascules de transport et la cohérence des données entre API et SSH. Tous les objets créés sont supprimés en fin de campagne. Paramétrage via .env.example.

Publication

Le workflow .github/workflows/publish.yml publie sur PyPI via Trusted Publishing (OIDC, aucun token à stocker) lorsqu'une étiquette de version est poussée. Il refuse de publier si l'étiquette ne correspond pas à __version__.

Une seule fois, déclarer l'éditeur de confiance sur https://pypi.org/manage/account/publishing/ (rubrique Add a new pending publisher) :

Champ Valeur
PyPI Project Name routeros-client
Owner jackarten
Repository name routeros-client
Workflow name publish.yml
Environment name pypi

À chaque version :

git tag v0.6.1 && git push origin v0.6.1

Contenu du dépôt

Fichier Rôle
routeros_client.py La bibliothèque — module autonome
routeros_api.py Alias de compatibilité vers routeros_client
GUIDE.md Guide d'utilisation complet, 20 chapitres
test_routeros_api.py Tests hors-ligne (105)
test_live_router.py Campagne contre un routeur réel (62)
CHANGELOG.md 27 bugs corrigés, 11 optimisations, transport SSH — détail et justification
pyproject.toml Packaging, configuration ruff
requirements.txt Dépendance d'exécution (paramiko)
.env.example Modèle de configuration des tests réels

routeros_api_v0.5.0.bak.py conserve la version précédente ; il est exclu du dépôt par .gitignore.


Migration depuis la v0.5.0

Le code existant fonctionne sans modification : classes, signatures et valeurs de retour sont préservées, les ajouts sont strictement additifs.

Trois différences de comportement, toutes des corrections de bugs (détail dans CHANGELOG.md) :

  1. split_command() retire de nouveau les guillemets englobants — =comment="a b" créait un commentaire contenant littéralement les guillemets. Ancien comportement : RouterOSProtocol.KEEP_QUOTES = True.
  2. Un !trap est signalé après lecture du !done : l'exception est la même, mais le flux n'est plus désynchronisé pour toutes les commandes suivantes.
  3. Les booléens Python sont convertis : disabled=True produit =disabled=yes et non =disabled=True, que RouterOS refusait.

Si vous utilisiez RestApi.add(), il était cassé en v0.5.0 (POST → HTTP 400) : il utilise de nouveau PUT, le verbe de création de l'API REST RouterOS.


Licence

MIT — voir le fichier LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

routeros_client-0.6.3.tar.gz (92.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

routeros_client-0.6.3-py3-none-any.whl (49.9 kB view details)

Uploaded Python 3

File details

Details for the file routeros_client-0.6.3.tar.gz.

File metadata

  • Download URL: routeros_client-0.6.3.tar.gz
  • Upload date:
  • Size: 92.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for routeros_client-0.6.3.tar.gz
Algorithm Hash digest
SHA256 094553b82c08610509c211857f4cf4cd13f695b000352221bc6a5f43da113fda
MD5 d2924344cea4abeb11f3a92ebe7e5a17
BLAKE2b-256 26561f8efe259e2fc7d22942614d1d25107b3a9f5c6cccf030676f1615c138c6

See more details on using hashes here.

Provenance

The following attestation bundles were made for routeros_client-0.6.3.tar.gz:

Publisher: publish.yml on Jackarten/routeros-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file routeros_client-0.6.3-py3-none-any.whl.

File metadata

  • Download URL: routeros_client-0.6.3-py3-none-any.whl
  • Upload date:
  • Size: 49.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for routeros_client-0.6.3-py3-none-any.whl
Algorithm Hash digest
SHA256 5a5881981a64cb42312095b01b480d24b99a2e890e0a1c76d87a66210d33ceb6
MD5 a3850e2899fc378d435cb9541806338f
BLAKE2b-256 f01cbd602222c3ebcf8347c3c0053bfabfacf61e68fb0f316deccec2815c7e1e

See more details on using hashes here.

Provenance

The following attestation bundles were made for routeros_client-0.6.3-py3-none-any.whl:

Publisher: publish.yml on Jackarten/routeros-client

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.6.4

2 files

This release

0.6.3 This release

2 files

0.6.1

2 files

0.6.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page