Für die erfahrenen Programmierer unter euch ist das sicher nichts neues: Einfacher heisst nicht immer besser und es ist immer gut, wenn man weiß, wie die Maschine so arbeitet.

Da hatte ich gerade so ein Erlebnis: Ich lerne ja gerade Python und hab da nach einem Modul gesucht, mit dem man an einem Linux-System die Routing-Tabelle auslesen kann. Also erst mal wirklich nur lesen. Da hab ich so erst mal nichts gefunden, außer so Profi-Tipps wie os.system('ip route show') auf Stackoverflow.

Und ich so: Das muss doch auch anders gehen? Tatsächlich hab ich dann irgendwo einen Hinweis auf das Socket Modul gefunden und einen Code-Schnipsel, in dem jemand das ganze mit socket, struct und ein bisschen Regex zusammen getackert hat. Genau mein Ding.

Ich hab den Code dann mal ein bisschen abgewandelt und verwende den jetzt in einen anderen Kontext, aber im Prinzip ist das ganz einfach, wenn man die ganze Doku mal durch hat:

#!/usr/bin/env python3

import re
import struct
import socket

routing_table = []
# Regex to catch the lines actually containing routing information (the \\. expression is needed if subinterfaces are used)
pattern = re.compile('^[\\.a-z0-9]*\W([0-9A-F]{8})\W([0-9A-F]{8})[\W0-9]*([0-9A-F]{8})')

# open current (machine readable) routing table 
with open('/proc/net/route', 'r') as proc_routes:
    # read line by line, match real routing table entries
    for line in proc_routes.read().split('\n'):
        if pattern.match(line):
            entry = pattern.findall(line)[0]
            # first column contains destination, second is gateway 
            # format conversion needed, see https://docs.python.org/3/library/struct.html
            dst = socket.inet_ntoa(struct.pack("I", int(entry[0], 16)))
            gw = socket.inet_ntoa(struct.pack("I", int(entry[1], 16)))
            # directly connected not needed here
            if not gw == '0.0.0.0':
                routing_table.append((dst,gw))

for item in routing_table:
    if item[0] == '0.0.0.0':
        default_gateway = item[1]

print('Default gateway:',default_gateway)

Und es tut was es soll.

Was mich dann aber trotzdem interessiert hat: Meine Vermutung war, dass das so (also relativ “hässlich”) viel schneller geht, als über das os-Modul. Deswegen hab ich da mal zwei kleine Beispiele gemacht. Erstes Beispiel (test-rt-os.py) mit dem os-Modul. Fragt die Routing-Tabelle 1000 Mal ab und gibt dann das Ergebnis aus:

import subprocess
counter = 0
while counter < 1000:
    output = subprocess.run(['ip route show default'], shell=True, capture_output=True, text=True)
    gateway = str(output.stdout.split()[2])
    counter += 1
print('Default gateway:',gateway)

Schlank. Elegant. Schöner Code. Dann das (im Ergebnis) gleiche mit dem socket Modul (test-rt-proc.py):

import re
import struct
import socket

counter = 0
while counter < 1000:
    routing_table = []
    pattern = re.compile('^[\\.a-z0-9]*\W([0-9A-F]{8})\W([0-9A-F]{8})[\W0-9]*([0-9A-F]{8})')
    with open('/proc/net/route', 'r') as proc_routes:
        for line in proc_routes.read().split('\n'):
            if pattern.match(line):
                routing_table.append(pattern.findall(line)[0])
    for item in routing_table:
        if str(item[0]) == '00000000':
            default_gateway = socket.inet_ntoa(struct.pack("I", int(item[1], 16)))
    counter += 1

print('Default gateway:',default_gateway)

Das Ergebnis hätte ich schon erwartet, aber nicht in dieser Deutlichkeit:

root@imp:~# time python3 test-rt-proc.py
Default gateway: 192.168.1.1

real    0m0.104s
user    0m0.068s
sys     0m0.036s
root@imp:~# time python3 test-rt-os.py
Default gateway: 192.168.1.1

real    0m2.097s
user    0m1.690s
sys     0m0.480s

Fazit: Lohnt sich fast immer, nicht einen der “hübschen” Vorschläge von der ersten Seite der Google-Suche zu copypasten, sondern mal kurz nachzudenken und dann irgendwo anders zu klauen. 😏