svinota / pyroute2

Python Netlink and PF_ROUTE library — network configuration and monitoring
https://pyroute2.org/
Other
930 stars 244 forks source link

Deleting routing policies with same priority fails #734

Open insecure opened 3 years ago

insecure commented 3 years ago

Deleting a routing policy (rule) fails if another rule with the same priority exists. If pref is not specified when creating the rules, the priorities are different and everything works fine.

I have written an example demonstrating the behaviour. The NetNS code is only used so we do not accidentally screw up the current system. It is also reproducible without using a dedicated Linux network namespace.

#!/usr/bin/env python3
import shlex
import subprocess

import pyroute2

def main():
    netns_name = "pyroute2_del_rule_bug"
    pyroute2.netns.create(netns_name)
    pyroute2.netns.pushns(netns_name)
    for i in range(5, 8):
        subprocess.call(shlex.split(f"ip rule add iif lo to 1.2.3.{i} lookup 666 pref 5000"))
    print(" === ip rule show lookup 666 ===")
    subprocess.call(shlex.split("ip rule show lookup 666"))
    try:
        with pyroute2.IPDB(nl=pyroute2.NetNS(netns_name)) as ipdb:
            with ipdb.rules[{"dst": "1.2.3.7/32"}] as rule:
                print(f"\n* deleting rule {rule}")
                rule.remove()
    finally:
        print("\n === ip rule show lookup 666 ===")
        subprocess.call(shlex.split("ip rule show lookup 666"))
        pyroute2.netns.popns()
        pyroute2.netns.remove(netns_name)

if __name__ == '__main__':
    main()

When I run this code, I see:

 === ip rule show lookup 666 ===
5000:   from all to 1.2.3.5 iif lo lookup 666
5000:   from all to 1.2.3.6 iif lo lookup 666
5000:   from all to 1.2.3.7 iif lo lookup 666

* deleting rule {'dst': '1.2.3.7/32', 'iifname': 'lo', 'priority': 5000, 'suppress_prefixlen': 4294967295, 'table': 666, 'protocol': 0, 'family': 2, 'tos': 0, 'action': 1, 'flags': 0, 'ipdb_scope': 'system', 'ipdb_priority': 0}

 === ip rule show lookup 666 ===
5000:   from all to 1.2.3.6 iif lo lookup 666
5000:   from all to 1.2.3.7 iif lo lookup 666

I would expect to see that the rule with destination IP 1.2.3.7 has been deleted. Instead the rule with destination IP 1.2.3.5 is deleted.

I saw this behaviour with pyroute2 0.5.13 and also with 0.5.3 (without the netns code, of course).

ljluestc commented 7 months ago
#!/usr/bin/env python3
import shlex
import subprocess

import pyroute2

def main():
    netns_name = "pyroute2_del_rule_bug"
    pyroute2.netns.create(netns_name)
    pyroute2.netns.pushns(netns_name)

    # Create rules with different priorities and specify dst IPs
    rules_to_create = [
        {"iif": "lo", "to": "1.2.3.5", "table": 666, "pref": 5000},
        {"iif": "lo", "to": "1.2.3.6", "table": 666, "pref": 5000},
        {"iif": "lo", "to": "1.2.3.7", "table": 666, "pref": 5000}
    ]

    for rule_data in rules_to_create:
        rule_command = (
            f"ip rule add iif {rule_data['iif']} to {rule_data['to']} "
            f"lookup {rule_data['table']} pref {rule_data['pref']}"
        )
        subprocess.call(shlex.split(rule_command))

    print(" === ip rule show lookup 666 ===")
    subprocess.call(shlex.split("ip rule show lookup 666"))

    try:
        with pyroute2.IPDB(nl=pyroute2.NetNS(netns_name)) as ipdb:
            # Define the criteria for the rule you want to delete
            delete_rule_criteria = {"dst": "1.2.3.7/32", "iifname": "lo", "priority": 5000}

            # Find the rule that matches the criteria
            matching_rule = None
            for rule in ipdb.rules:
                if all(rule.get(key) == value for key, value in delete_rule_criteria.items()):
                    matching_rule = rule
                    break

            if matching_rule:
                print(f"\n* deleting rule {matching_rule}")
                matching_rule.remove()
            else:
                print("No matching rule found to delete.")
    finally:
        print("\n === ip rule show lookup 666 ===")
        subprocess.call(shlex.split("ip rule show lookup 666"))
        pyroute2.netns.popns()
        pyroute2.netns.remove(netns_name)

if __name__ == '__main__':
    main()