Debugging Complex Rule Interactions

Debugging Complex Rule Interactions

When multiple firewall rules interact, unexpected behaviors can emerge. Understanding rule precedence and interaction helps diagnose complex issues.

Create a firewall rule analyzer:

#!/usr/bin/env python3
import re
import ipaddress
import json

class FirewallRuleAnalyzer:
    def __init__(self):
        self.rules = []
        self.conflicts = []
        
    def parse_iptables_rules(self, output):
        """Parse iptables-save output into structured format"""
        
        current_table = None
        current_chain = None
        
        for line in output.split('\n'):
            line = line.strip()
            
            # Table definition
            if line.startswith('*'):
                current_table = line[1:]
                continue
                
            # Chain definition
            if line.startswith(':'):
                parts = line.split()
                current_chain = parts[0][1:]
                continue
                
            # Rule definition
            if line.startswith('-A'):
                rule = self.parse_rule(line, current_table, current_chain)
                if rule:
                    self.rules.append(rule)
        
        return self.rules
    
    def parse_rule(self, line, table, chain):
        """Parse individual iptables rule"""
        
        rule = {
            'table': table,
            'chain': chain,
            'raw': line,
            'source': None,
            'destination': None,
            'protocol': None,
            'dport': None,
            'sport': None,
            'action': None,
            'modules': []
        }
        
        # Extract rule components
        parts = line.split()
        i = 0
        while i < len(parts):
            if parts[i] == '-s' and i + 1 < len(parts):
                rule['source'] = parts[i + 1]
                i += 2
            elif parts[i] == '-d' and i + 1 < len(parts):
                rule['destination'] = parts[i + 1]
                i += 2
            elif parts[i] == '-p' and i + 1 < len(parts):
                rule['protocol'] = parts[i + 1]
                i += 2
            elif parts[i] == '--dport' and i + 1 < len(parts):
                rule['dport'] = parts[i + 1]
                i += 2
            elif parts[i] == '--sport' and i + 1 < len(parts):
                rule['sport'] = parts[i + 1]
                i += 2
            elif parts[i] == '-j' and i + 1 < len(parts):
                rule['action'] = parts[i + 1]
                i += 2
            elif parts[i] == '-m' and i + 1 < len(parts):
                rule['modules'].append(parts[i + 1])
                i += 2
            else:
                i += 1
        
        return rule
    
    def find_conflicts(self):
        """Identify potentially conflicting rules"""
        
        for i, rule1 in enumerate(self.rules):
            for j, rule2 in enumerate(self.rules[i+1:], i+1):
                conflict = self.check_rule_conflict(rule1, rule2)
                if conflict:
                    self.conflicts.append({
                        'rule1': rule1,
                        'rule2': rule2,
                        'type': conflict
                    })
        
        return self.conflicts
    
    def check_rule_conflict(self, rule1, rule2):
        """Check if two rules conflict"""
        
        # Skip if different chains
        if rule1['chain'] != rule2['chain']:
            return None
            
        # Check for overlapping IP ranges
        if rule1['source'] and rule2['source']:
            try:
                net1 = ipaddress.ip_network(rule1['source'], strict=False)
                net2 = ipaddress.ip_network(rule2['source'], strict=False)
                
                if net1.overlaps(net2):
                    # Check if actions differ
                    if rule1['action'] != rule2['action']:
                        return 'overlapping_source_different_action'
            except:
                pass
        
        # Check for same port different actions
        if (rule1['dport'] == rule2['dport'] and 
            rule1['protocol'] == rule2['protocol'] and
            rule1['action'] != rule2['action']):
            return 'same_port_different_action'
        
        # Check for redundant rules
        if (rule1['source'] == rule2['source'] and
            rule1['destination'] == rule2['destination'] and
            rule1['dport'] == rule2['dport'] and
            rule1['action'] == rule2['action']):
            return 'redundant_rule'
        
        return None
    
    def simulate_packet(self, src_ip, dst_ip, dst_port, protocol='tcp'):
        """Simulate packet flow through rules"""
        
        print(f"\n=== Simulating packet flow ===")
        print(f"Packet: {src_ip} -> {dst_ip}:{dst_port}/{protocol}")
        
        for i, rule in enumerate(self.rules):
            if self.packet_matches_rule(src_ip, dst_ip, dst_port, protocol, rule):
                print(f"\nRule #{i+1} MATCHES:")
                print(f"  Chain: {rule['chain']}")
                print(f"  Action: {rule['action']}")
                print(f"  Raw: {rule['raw']}")
                
                if rule['action'] in ['DROP', 'REJECT', 'ACCEPT']:
                    print(f"\nFINAL VERDICT: {rule['action']}")
                    return rule['action']
        
        print("\nFINAL VERDICT: Default policy")
        return 'DEFAULT'