diff --git a/policy_routing.py b/policy_routing.py index 9801f7d..8be7ff5 100644 --- a/policy_routing.py +++ b/policy_routing.py @@ -11,8 +11,6 @@ import sys import json import re import ipaddress -import time -import threading from datetime import datetime from pathlib import Path @@ -24,6 +22,7 @@ class PolicyBasedRoutingManager: level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" ) self.logger = logging.getLogger(__name__) + self.config_file_path = Path("/etc/pbr_nics.json") # 권한 확인 if os.geteuid() != 0: @@ -176,6 +175,133 @@ class PolicyBasedRoutingManager: ) return config + def _save_nic_config(self, config_data): + """현재 NIC 설정을 파일에 저장""" + try: + with open(self.config_file_path, "w") as f: + json.dump(config_data, f, indent=2) + self.logger.info(f"NIC 설정 저장 완료: {self.config_file_path}") + except Exception as e: + self.logger.error(f"NIC 설정 저장 실패: {e}") + + def _load_nic_config(self): + """이전에 저장된 NIC 설정을 파일에서 로드""" + if self.config_file_path.exists(): + try: + with open(self.config_file_path, "r") as f: + return json.load(f) + except json.JSONDecodeError as e: + self.logger.error(f"NIC 설정 파일 읽기 오류 (JSON): {e}") + except Exception as e: + self.logger.error(f"NIC 설정 파일 로드 실패: {e}") + return {"nics": {}} + + def detect_nic_changes(self): + """NIC 변경 사항 (추가/제거) 감지""" + current_nics = self.auto_detect_network_config()["nics"] + previous_config = self._load_nic_config() + previous_nics = previous_config.get("nics", {}) + + added_nics = {} + removed_nics = {} + + # 추가된 NIC 감지 + for nic_name, nic_config in current_nics.items(): + if nic_name not in previous_nics: + added_nics[nic_name] = nic_config + self.logger.info( + f"새로운 NIC 감지됨: {nic_name} ({nic_config['interface']})" + ) + + # 제거된 NIC 감지 + for nic_name, nic_config in previous_nics.items(): + if nic_name not in current_nics: + removed_nics[nic_name] = nic_config + self.logger.info( + f"NIC 제거 감지됨: {nic_name} ({nic_config['interface']})" + ) + + # 기존 NIC 중 변경된 정보가 있는지 확인 (IP, Gateway 등) + # 이 부분은 현재 스크립트의 auto_detect_network_config가 nicX 이름을 순서대로 부여하므로 + # 인터페이스 이름으로 비교하는 것이 더 정확할 수 있습니다. + # 여기서는 단순화하여 nic_name 기준으로만 추가/제거를 판단합니다. + # 더 정교한 변경 감지가 필요하면 interface 이름으로 매핑하여 비교 로직을 추가해야 합니다. + + return added_nics, removed_nics, current_nics + + def _add_single_nic_config(self, nic_name, nic_config): + """단일 NIC에 대한 라우팅 테이블, 라우트, 정책 규칙 추가""" + self.logger.info(f"NIC {nic_name} ({nic_config['interface']}) 설정 추가 중...") + + # 1. 라우팅 테이블 설정 + rt_tables_path = Path("/etc/iproute2/rt_tables") + table_entry = f"{nic_config['table_id']} {nic_name}" + + existing_content = "" + if rt_tables_path.exists(): + existing_content = rt_tables_path.read_text() + + if table_entry not in existing_content: + with open(rt_tables_path, "a") as f: + f.write(f"\n{table_entry}\n") + self.logger.info(f"라우팅 테이블 '{nic_name}' 추가됨") + + # 2. NIC별 라우팅 테이블 구성 + interface = nic_config["interface"] + gateway = nic_config["gateway"] + ip_addr = nic_config["ip"] + network = nic_config["network"] + + self.run_command( + f"ip route add {network} dev {interface} src {ip_addr} table {nic_name}" + ) + self.run_command( + f"ip route add default via {gateway} dev {interface} table {nic_name}" + ) + + # 3. Policy Rules 설정 + self.run_command(f"ip rule add from {ip_addr}/32 table {nic_name} priority 100") + self.run_command(f"ip rule add to {ip_addr}/32 table {nic_name} priority 101") + self.logger.info(f"NIC {nic_name} 설정 추가 완료") + + def _remove_single_nic_config(self, nic_name, nic_config): + """단일 NIC에 대한 라우팅 테이블, 라우트, 정책 규칙 제거""" + self.logger.info(f"NIC {nic_name} ({nic_config['interface']}) 설정 제거 중...") + + ip_addr = nic_config["ip"] + table_id = nic_config["table_id"] + + # 1. Policy rules 제거 + self.run_command( + f"ip rule del from {ip_addr}/32 table {nic_name}", ignore_error=True + ) + self.run_command( + f"ip rule del to {ip_addr}/32 table {nic_name}", ignore_error=True + ) + + # 2. 라우팅 테이블 내용 정리 + self.run_command(f"ip route flush table {nic_name}", ignore_error=True) + + # 3. /etc/iproute2/rt_tables 에서 항목 제거 + rt_tables_path = Path("/etc/iproute2/rt_tables") + if rt_tables_path.exists(): + try: + lines = rt_tables_path.read_text().splitlines() + new_lines = [ + line + for line in lines + if not ( + line.strip().startswith(str(table_id)) + and nic_name in line.strip() + ) + ] + rt_tables_path.write_text("\n".join(new_lines) + "\n") + self.logger.info(f"라우팅 테이블 '{nic_name}' 항목 제거됨") + except Exception as e: + self.logger.warning(f"rt_tables 파일 수정 중 오류 발생: {e}") + + self.logger.info(f"NIC {nic_name} 설정 제거 완료") + def print_detected_config(self): """감지된 설정 출력""" print("\n=== 감지된 네트워크 설정 ===") @@ -189,311 +315,6 @@ class PolicyBasedRoutingManager: print(f" 테이블 ID: {nic_config['table_id']}") print() - def create_udev_rules(self): - """udev 규칙 생성 - 네트워크 인터페이스 변경 감지""" - self.logger.info("udev 규칙 생성 중...") - - udev_rule_path = Path("/etc/udev/rules.d/99-pbr-network.rules") - - # udev 규칙 내용 - udev_rule_content = """# Policy Based Routing - Network Interface Detection -# NIC가 추가되거나 IP가 변경될 때 자동으로 PBR 재설정 - -# 네트워크 인터페이스 UP 이벤트 -SUBSYSTEM=="net", ACTION=="add", RUN+="/usr/local/bin/pbr-udev-handler.py add %k" -SUBSYSTEM=="net", ACTION=="change", KERNEL!="lo", RUN+="/usr/local/bin/pbr-udev-handler.py change %k" - -# IP 주소 변경 감지를 위한 추가 규칙 -SUBSYSTEM=="net", ACTION=="change", ATTR{operstate}=="up", RUN+="/usr/local/bin/pbr-udev-handler.py ip-change %k" -""" - - try: - udev_rule_path.write_text(udev_rule_content) - self.logger.info(f"udev 규칙 생성 완료: {udev_rule_path}") - - # udev 규칙 다시 로드 - self.run_command("udevadm control --reload-rules") - - except Exception as e: - self.logger.error(f"udev 규칙 생성 실패: {e}") - - def create_udev_handler_script(self): - """udev 이벤트 처리 스크립트 생성""" - self.logger.info("udev 핸들러 스크립트 생성 중...") - - handler_script_path = Path("/usr/local/bin/pbr-udev-handler.py") - - handler_script_content = f'''#!/usr/bin/env python3 -""" -PBR udev 이벤트 핸들러 -네트워크 인터페이스 변경 시 Policy Based Routing 자동 재설정 -""" - -import sys -import subprocess -import time -import logging -from pathlib import Path - -# 로깅 설정 -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s', - handlers=[ - logging.FileHandler('/var/log/pbr-udev.log'), - logging.StreamHandler() - ] -) -logger = logging.getLogger(__name__) - -def run_command(cmd, ignore_error=False): - """시스템 명령어 실행""" - try: - result = subprocess.run(cmd, shell=True, capture_output=True, text=True) - if result.returncode != 0 and not ignore_error: - logger.warning(f"명령어 실행 경고: {{cmd}}") - logger.warning(f"오류: {{result.stderr}}") - return result - except Exception as e: - logger.error(f"명령어 실행 실패: {{cmd}} - {{e}}") - return None - -def wait_for_ip_assignment(interface, max_wait=30): - """인터페이스에 IP가 할당될 때까지 대기""" - logger.info(f"인터페이스 {{interface}}의 IP 할당 대기 중...") - - for i in range(max_wait): - result = run_command(f"ip addr show {{interface}}") - if result and result.returncode == 0: - # IPv4 주소가 있는지 확인 - if "inet " in result.stdout and not result.stdout.count("inet ") == result.stdout.count("inet 127."): - logger.info(f"인터페이스 {{interface}}에 IP가 할당됨") - return True - time.sleep(1) - - logger.warning(f"인터페이스 {{interface}}의 IP 할당을 {{max_wait}}초간 기다렸지만 할당되지 않음") - return False - -def trigger_pbr_reconfiguration(): - """PBR 재설정 트리거""" - logger.info("PBR 재설정 트리거 중...") - - # 잠시 대기 후 PBR 재설정 실행 - time.sleep(5) # 시스템이 안정화될 시간을 줌 - - pbr_script = Path("{os.path.abspath(__file__)}") - if pbr_script.exists(): - # 기존 설정 제거 후 재설정 - run_command(f"python3 {{pbr_script}} remove", ignore_error=True) - time.sleep(2) - result = run_command(f"echo 'y' | python3 {{pbr_script}} setup") - - if result and result.returncode == 0: - logger.info("PBR 재설정 완료") - else: - logger.error("PBR 재설정 실패") - else: - logger.error(f"PBR 스크립트를 찾을 수 없음: {{pbr_script}}") - -def main(): - if len(sys.argv) != 3: - logger.error("사용법: pbr-udev-handler.py ") - sys.exit(1) - - action = sys.argv[1] - interface = sys.argv[2] - - logger.info(f"udev 이벤트 수신: {{action}} {{interface}}") - - # 루프백과 가상 인터페이스 제외 - if (interface == "lo" or - interface.startswith("docker") or - interface.startswith("veth") or - interface.startswith("br-")): - logger.info(f"인터페이스 {{interface}} 무시됨 (가상 인터페이스)") - return - - if action == "add": - logger.info(f"새 네트워크 인터페이스 감지: {{interface}}") - # IP 할당 대기 후 PBR 재설정 - if wait_for_ip_assignment(interface): - trigger_pbr_reconfiguration() - - elif action == "change": - logger.info(f"네트워크 인터페이스 변경 감지: {{interface}}") - # 인터페이스 상태 확인 후 필요시 재설정 - result = run_command(f"ip link show {{interface}}") - if result and "state UP" in result.stdout: - if wait_for_ip_assignment(interface, max_wait=10): - trigger_pbr_reconfiguration() - - elif action == "ip-change": - logger.info(f"네트워크 인터페이스 IP 변경 감지: {{interface}}") - # IP 변경 시 PBR 재설정 - trigger_pbr_reconfiguration() - -if __name__ == "__main__": - main() -''' - - try: - handler_script_path.write_text(handler_script_content) - handler_script_path.chmod(0o755) - self.logger.info(f"udev 핸들러 스크립트 생성 완료: {handler_script_path}") - - except Exception as e: - self.logger.error(f"udev 핸들러 스크립트 생성 실패: {e}") - - def create_systemd_service(self): - """systemd 서비스 생성 - 부팅 시 PBR 자동 설정""" - self.logger.info("systemd 서비스 생성 중...") - - service_path = Path("/etc/systemd/system/pbr-auto-setup.service") - - service_content = f"""[Unit] -Description=Policy Based Routing Auto Setup -After=network.target -Wants=network.target - -[Service] -Type=oneshot -ExecStart=/bin/bash -c "sleep 10 && echo 'y' | python3 {os.path.abspath(__file__)} setup" -RemainAfterExit=yes -StandardOutput=journal -StandardError=journal - -[Install] -WantedBy=multi-user.target -""" - - try: - service_path.write_text(service_content) - self.logger.info(f"systemd 서비스 생성 완료: {service_path}") - - # 서비스 활성화 - self.run_command("systemctl daemon-reload") - self.run_command("systemctl enable pbr-auto-setup.service") - - except Exception as e: - self.logger.error(f"systemd 서비스 생성 실패: {e}") - - def setup_udev_monitoring(self): - """udev 모니터링 시스템 설정""" - self.logger.info("udev 모니터링 시스템 설정 중...") - - try: - # udev 규칙 생성 - self.create_udev_rules() - - # udev 핸들러 스크립트 생성 - self.create_udev_handler_script() - - # systemd 서비스 생성 - self.create_systemd_service() - - # 로그 디렉토리 확인 - log_dir = Path("/var/log") - if not log_dir.exists(): - log_dir.mkdir(exist_ok=True) - - self.logger.info("udev 모니터링 시스템 설정 완료") - print("\n=== udev 모니터링 설정 완료 ===") - print("1. 새로운 NIC가 추가되면 자동으로 PBR 재설정") - print("2. 기존 NIC의 IP가 변경되면 자동으로 PBR 재설정") - print("3. 시스템 부팅 시 자동으로 PBR 설정") - print("4. 로그 파일: /var/log/pbr-udev.log") - - except Exception as e: - self.logger.error(f"udev 모니터링 시스템 설정 실패: {e}") - - def remove_udev_monitoring(self): - """udev 모니터링 시스템 제거""" - self.logger.info("udev 모니터링 시스템 제거 중...") - - try: - # udev 규칙 제거 - udev_rule_path = Path("/etc/udev/rules.d/99-pbr-network.rules") - if udev_rule_path.exists(): - udev_rule_path.unlink() - self.logger.info("udev 규칙 제거됨") - - # udev 핸들러 스크립트 제거 - handler_script_path = Path("/usr/local/bin/pbr-udev-handler.py") - if handler_script_path.exists(): - handler_script_path.unlink() - self.logger.info("udev 핸들러 스크립트 제거됨") - - # systemd 서비스 제거 - service_path = Path("/etc/systemd/system/pbr-auto-setup.service") - if service_path.exists(): - self.run_command( - "systemctl disable pbr-auto-setup.service", ignore_error=True - ) - self.run_command( - "systemctl stop pbr-auto-setup.service", ignore_error=True - ) - service_path.unlink() - self.run_command("systemctl daemon-reload") - self.logger.info("systemd 서비스 제거됨") - - # udev 규칙 다시 로드 - self.run_command("udevadm control --reload-rules") - - self.logger.info("udev 모니터링 시스템 제거 완료") - - except Exception as e: - self.logger.error(f"udev 모니터링 시스템 제거 실패: {e}") - - def check_interface_changes(self): - """인터페이스 변경 사항 실시간 모니터링 (테스트용)""" - self.logger.info("네트워크 인터페이스 변경 모니터링 시작...") - print("네트워크 인터페이스 변경을 모니터링 중입니다...") - print("Ctrl+C로 중지할 수 있습니다.") - - last_config = self.config.copy() - - try: - while True: - time.sleep(5) # 5초마다 확인 - - # 현재 설정 다시 감지 - current_config = self.auto_detect_network_config() - - # 변경 사항 확인 - if current_config != last_config: - self.logger.info("네트워크 인터페이스 변경 감지됨!") - print("\n=== 네트워크 변경 감지 ===") - - # 새로 추가된 인터페이스 - new_nics = set(current_config["nics"].keys()) - set( - last_config["nics"].keys() - ) - if new_nics: - print(f"새로 추가된 인터페이스: {', '.join(new_nics)}") - - # 제거된 인터페이스 - removed_nics = set(last_config["nics"].keys()) - set( - current_config["nics"].keys() - ) - if removed_nics: - print(f"제거된 인터페이스: {', '.join(removed_nics)}") - - # 설정 업데이트 및 PBR 재설정 - self.config = current_config - print("PBR 자동 재설정 중...") - self.cleanup_existing() - self.setup_routing_tables() - self.configure_nic_routes() - self.setup_policy_rules() - self.setup_main_routing() - print("PBR 재설정 완료!") - - last_config = current_config.copy() - - except KeyboardInterrupt: - print("\n모니터링이 중지되었습니다.") - self.logger.info("네트워크 인터페이스 모니터링 중지됨") - def create_backup(self): """기존 설정 백업""" self.logger.info("기존 설정 백업 중...") @@ -514,95 +335,12 @@ WantedBy=multi-user.target self.logger.info(f"백업 완료: {backup_dir}") - def cleanup_existing(self): - """기존 설정 정리""" - self.logger.info("기존 policy routing 설정 정리 중...") - - for nic_name, nic_config in self.config["nics"].items(): - ip_addr = nic_config["ip"] - - # 기존 policy rules 제거 - self.run_command( - f"ip rule del from {ip_addr}/32 table {nic_name}", ignore_error=True - ) - self.run_command( - f"ip rule del to {ip_addr}/32 table {nic_name}", ignore_error=True - ) - - # 기존 라우팅 테이블 내용 정리 - self.run_command(f"ip route flush table {nic_name}", ignore_error=True) - - def setup_routing_tables(self): - """라우팅 테이블 설정""" - self.logger.info("라우팅 테이블 설정 중...") - - rt_tables_path = Path("/etc/iproute2/rt_tables") - - # 기존 내용 읽기 - existing_content = "" - if rt_tables_path.exists(): - existing_content = rt_tables_path.read_text() - - # 새로운 테이블 추가 - for nic_name, nic_config in self.config["nics"].items(): - table_entry = f"{nic_config['table_id']} {nic_name}" - - if table_entry not in existing_content: - with open(rt_tables_path, "a") as f: - f.write(f"\n{table_entry}\n") - self.logger.info(f"라우팅 테이블 '{nic_name}' 추가됨") - - def configure_nic_routes(self): - """각 NIC별 라우팅 테이블 구성""" - self.logger.info("각 NIC별 라우팅 테이블 구성 중...") - - for nic_name, nic_config in self.config["nics"].items(): - interface = nic_config["interface"] - gateway = nic_config["gateway"] - ip_addr = nic_config["ip"] - network = nic_config["network"] - - self.logger.info(f"NIC {nic_name} ({interface}) 라우팅 설정 중...") - - # 로컬 네트워크 라우트 - self.run_command( - f"ip route add {network} dev {interface} src {ip_addr} table {nic_name}" - ) - - # 기본 게이트웨이 - self.run_command( - f"ip route add default via {gateway} dev {interface} table {nic_name}" - ) - - def setup_policy_rules(self): - """Policy Rules 설정""" - self.logger.info("Policy Rules 설정 중...") - - for nic_name, nic_config in self.config["nics"].items(): - ip_addr = nic_config["ip"] - - # Source IP 기반 정책 - self.run_command( - f"ip rule add from {ip_addr}/32 table {nic_name} priority 100" - ) - - # Destination IP 기반 정책 - self.run_command( - f"ip rule add to {ip_addr}/32 table {nic_name} priority 101" - ) - - self.logger.info(f"NIC {nic_name} (IP: {ip_addr}) Policy Rule 설정 완료") - def setup_main_routing(self): """메인 라우팅 테이블 설정 (metric 기반 우선순위)""" self.logger.info("메인 라우팅 테이블 설정 중...") - # 기존 모든 default 라우트 제거 (더 강력한 방법) - result = self.run_command("ip route show default") - if result and result.stdout.strip(): - for line in result.stdout.strip().split("\n"): - if line.strip() and "default" in line: - self.run_command(f"ip route del {line.strip()}", ignore_error=True) + # 기존 default 라우트 제거 + self.run_command("ip route del default", ignore_error=True) # metric 순으로 정렬하여 default 라우트 추가 sorted_nics = sorted(self.config["nics"].items(), key=lambda x: x[1]["metric"]) @@ -612,21 +350,12 @@ WantedBy=multi-user.target gateway = nic_config["gateway"] metric = nic_config["metric"] - # 라우트 추가 전에 동일한 라우트가 있는지 확인 - check_result = self.run_command( - f"ip route show default via {gateway} dev {interface}" + self.run_command( + f"ip route add default via {gateway} dev {interface} metric {metric}" + ) + self.logger.info( + f"Default 라우트 추가: {gateway} via {interface} (metric: {metric})" ) - if not check_result or not check_result.stdout.strip(): - self.run_command( - f"ip route add default via {gateway} dev {interface} metric {metric}" - ) - self.logger.info( - f"Default 라우트 추가: {gateway} via {interface} (metric: {metric})" - ) - else: - self.logger.info( - f"Default 라우트 이미 존재: {gateway} via {interface} (metric: {metric})" - ) def check_interfaces(self): """네트워크 인터페이스 상태 확인""" @@ -674,6 +403,27 @@ WantedBy=multi-user.target if result: print(result.stdout) + def apply_dynamic_rules(self): + """NIC 변경 사항을 감지하고 동적으로 규칙을 적용/제거""" + self.logger.info("동적 NIC 규칙 적용 중...") + added_nics, removed_nics, current_nics = self.detect_nic_changes() + + # 제거된 NIC 설정 정리 + for nic_name, nic_config in removed_nics.items(): + self._remove_single_nic_config(nic_name, nic_config) + + # 추가된 NIC 설정 적용 + for nic_name, nic_config in added_nics.items(): + self._add_single_nic_config(nic_name, nic_config) + + # 현재 활성 NIC 목록으로 self.config 업데이트 및 저장 + self.config["nics"] = current_nics + self._save_nic_config(self.config) + + # 메인 라우팅 테이블은 전체 NIC 기반으로 재설정 + self.setup_main_routing() + self.logger.info("동적 NIC 규칙 적용 완료.") + def create_startup_script(self): """시스템 시작시 자동 적용을 위한 스크립트 생성""" self.logger.info("시작시 자동 적용 스크립트 생성 중...") @@ -683,20 +433,237 @@ WantedBy=multi-user.target script_content = f"""#!/usr/bin/env python3 import subprocess import json +import sys +import os +import re +import ipaddress +from pathlib import Path +import logging -config = {json.dumps(self.config, indent=2)} +# 로깅 설정 (스크립트 실행 시 로그를 남기기 위함) +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" +) +logger = logging.getLogger(__name__) -def run_cmd(cmd): - try: - subprocess.run(cmd, shell=True, check=False) - except: - pass +class StartupPolicyBasedRoutingManager: + def __init__(self): + self.logger = logger + self.config_file_path = Path("/etc/pbr_nics.json") -# Policy rules 재설정 -for nic_name, nic_config in config['nics'].items(): - ip_addr = nic_config['ip'] - run_cmd(f"ip rule add from {{ip_addr}}/32 table {{nic_name}} priority 100") - run_cmd(f"ip rule add to {{ip_addr}}/32 table {{nic_name}} priority 101") + def run_command(self, cmd, ignore_error=False): + try: + result = subprocess.run(cmd, shell=True, capture_output=True, text=True) + if result.returncode != 0 and not ignore_error: + self.logger.warning(f"명령어 실행 경고: {{cmd}}") + self.logger.warning(f"오류: {{result.stderr}}") + return result + except Exception as e: + self.logger.error(f"명령어 실행 실패: {{cmd}} - {{e}}") + return None + + def get_network_interfaces(self): + interfaces = {{}} + result = self.run_command("ip link show") + if not result or result.returncode != 0: + self.logger.error("네트워크 인터페이스를 가져올 수 없습니다") + return interfaces + for line in result.stdout.split("\\n"): + match = re.match(r"^\\d+:\\s+(\\w+):", line) + if match: + interface = match.group(1) + if ( + interface != "lo" + and not interface.startswith("docker") + and not interface.startswith("veth") + and not interface.startswith("br-") + and "state UP" in line + ): + interfaces[interface] = {{}} + return interfaces + + def get_interface_ip_info(self, interface): + result = self.run_command(f"ip addr show {{interface}}") + if not result or result.returncode != 0: + return None + ip_info = {{}} + for line in result.stdout.split("\\n"): + match = re.search(r"inet (\\d+\\.\\d+\\.\\d+\\.\\d+)/(\\d+)", line) + if match: + ip_addr = match.group(1) + prefix = int(match.group(2)) + network = ipaddress.IPv4Network(f"{{ip_addr}}/{{prefix}}", strict=False) + ip_info = {{ + "ip": ip_addr, + "prefix": prefix, + "network": str(network), + "netmask": str(network.netmask), + }} + break + return ip_info + + def get_default_gateway(self, interface): + result = self.run_command(f"ip route show dev {{interface}}") + if not result or result.returncode != 0: + return None + for line in result.stdout.split("\\n"): + if "default via" in line: + match = re.search(r"default via (\\d+\\.\\d+\\.\\d+\\.\\d+)", line) + if match: + return match.group(1) + ip_info = self.get_interface_ip_info(interface) + if ip_info: + network = ipaddress.IPv4Network(ip_info["network"]) + gateway = str(network.network_address + 1) + return gateway + return None + + def auto_detect_network_config(self): + config = {{"nics": {{}}}} + interfaces = self.get_network_interfaces() + if not interfaces: + self.logger.error("활성화된 네트워크 인터페이스를 찾을 수 없습니다") + return config # Return empty config instead of sys.exit(1) for startup script + table_id = 100 + metric_base = 100 + for i, interface in enumerate(interfaces.keys()): + ip_info = self.get_interface_ip_info(interface) + if not ip_info: + self.logger.warning(f"인터페이스 {{interface}}의 IP 정보를 가져올 수 없습니다") + continue + gateway = self.get_default_gateway(interface) + if not gateway: + self.logger.warning(f"인터페이스 {{interface}}의 게이트웨이를 찾을 수 없습니다") + continue + nic_name = f"nic{{i+1}}" + config["nics"][nic_name] = {{ + "interface": interface, + "ip": ip_info["ip"], + "network": ip_info["network"], + "gateway": gateway, + "metric": metric_base + (i * 100), + "table_id": table_id + i, + }} + return config + + def _save_nic_config(self, config_data): + try: + with open(self.config_file_path, "w") as f: + json.dump(config_data, f, indent=2) + self.logger.info(f"NIC 설정 저장 완료: {{self.config_file_path}}") + except Exception as e: + self.logger.error(f"NIC 설정 저장 실패: {{e}}") + + def _load_nic_config(self): + if self.config_file_path.exists(): + try: + with open(self.config_file_path, "r") as f: + return json.load(f) + except json.JSONDecodeError as e: + self.logger.error(f"NIC 설정 파일 읽기 오류 (JSON): {{e}}") + except Exception as e: + self.logger.error(f"NIC 설정 파일 로드 실패: {{e}}") + return {{"nics": {{}}}} + + def detect_nic_changes(self): + current_nics = self.auto_detect_network_config()["nics"] + previous_config = self._load_nic_config() + previous_nics = previous_config.get("nics", {{}}) + + added_nics = {{}} + removed_nics = {{}} + + for nic_name, nic_config in current_nics.items(): + if nic_name not in previous_nics: + added_nics[nic_name] = nic_config + self.logger.info(f"새로운 NIC 감지됨: {{nic_name}} ({{nic_config['interface']}})") + + for nic_name, nic_config in previous_nics.items(): + if nic_name not in current_nics: + removed_nics[nic_name] = nic_config + self.logger.info(f"NIC 제거 감지됨: {{nic_name}} ({{nic_config['interface']}})") + + return added_nics, removed_nics, current_nics + + def _add_single_nic_config(self, nic_name, nic_config): + self.logger.info(f"NIC {{nic_name}} ({{nic_config['interface']}}) 설정 추가 중...") + rt_tables_path = Path("/etc/iproute2/rt_tables") + table_entry = f"{{nic_config['table_id']}} {{nic_name}}" + + existing_content = "" + if rt_tables_path.exists(): + existing_content = rt_tables_path.read_text() + + if table_entry not in existing_content: + with open(rt_tables_path, "a") as f: + f.write(f"\\n{{table_entry}}\\n") + self.logger.info(f"라우팅 테이블 '{{nic_name}}' 추가됨") + + interface = nic_config["interface"] + gateway = nic_config["gateway"] + ip_addr = nic_config["ip"] + network = nic_config["network"] + + self.run_command(f"ip route add {{network}} dev {{interface}} src {{ip_addr}} table {{nic_name}}") + self.run_command(f"ip route add default via {{gateway}} dev {{interface}} table {{nic_name}}") + self.run_command(f"ip rule add from {{ip_addr}}/32 table {{nic_name}} priority 100") + self.run_command(f"ip rule add to {{ip_addr}}/32 table {{nic_name}} priority 101") + self.logger.info(f"NIC {{nic_name}} 설정 추가 완료") + + def _remove_single_nic_config(self, nic_name, nic_config): + self.logger.info(f"NIC {{nic_name}} ({{nic_config['interface']}}) 설정 제거 중...") + ip_addr = nic_config["ip"] + table_id = nic_config["table_id"] + + self.run_command(f"ip rule del from {{ip_addr}}/32 table {{nic_name}}", ignore_error=True) + self.run_command(f"ip rule del to {{ip_addr}}/32 table {{nic_name}}", ignore_error=True) + self.run_command(f"ip route flush table {{nic_name}}", ignore_error=True) + + rt_tables_path = Path("/etc/iproute2/rt_tables") + if rt_tables_path.exists(): + try: + lines = rt_tables_path.read_text().splitlines() + new_lines = [ + line + for line in lines + if not ( + line.strip().startswith(str(table_id)) + and nic_name in line.strip() + ) + ] + rt_tables_path.write_text("\\n".join(new_lines) + "\\n") + self.logger.info(f"라우팅 테이블 '{{nic_name}}' 항목 제거됨") + except Exception as e: + self.logger.warning(f"rt_tables 파일 수정 중 오류 발생: {{e}}") + self.logger.info(f"NIC {{nic_name}} 설정 제거 완료") + + def setup_main_routing(self, current_nics): + self.logger.info("메인 라우팅 테이블 설정 중...") + self.run_command("ip route del default", ignore_error=True) + sorted_nics = sorted(current_nics.items(), key=lambda x: x[1]["metric"]) + for nic_name, nic_config in sorted_nics: + interface = nic_config["interface"] + gateway = nic_config["gateway"] + metric = nic_config["metric"] + self.run_command(f"ip route add default via {{gateway}} dev {{interface}} metric {{metric}}") + self.logger.info(f"Default 라우트 추가: {{gateway}} via {{interface}} (metric: {{metric}})") + +def main_startup(): + manager = StartupPolicyBasedRoutingManager() + + added_nics, removed_nics, current_nics = manager.detect_nic_changes() + + for nic_name, nic_config in removed_nics.items(): + manager._remove_single_nic_config(nic_name, nic_config) + + for nic_name, nic_config in added_nics.items(): + manager._add_single_nic_config(nic_name, nic_config) + + manager._save_nic_config({{"nics": current_nics}}) + manager.setup_main_routing(current_nics) + +if __name__ == "__main__": + main_startup() """ startup_script.write_text(script_content) @@ -752,17 +719,23 @@ for nic_name, nic_config in config['nics'].items(): self.logger.error("인터페이스 확인 실패") return False - self.cleanup_existing() - self.setup_routing_tables() - self.configure_nic_routes() - self.setup_policy_rules() - self.setup_main_routing() + added_nics, removed_nics, current_nics = self.detect_nic_changes() + + # 제거된 NIC 설정 정리 + for nic_name, nic_config in removed_nics.items(): + self._remove_single_nic_config(nic_name, nic_config) + + # 추가된 NIC 설정 적용 + for nic_name, nic_config in added_nics.items(): + self._add_single_nic_config(nic_name, nic_config) + + # 현재 활성 NIC 목록으로 self.config 업데이트 + self.config["nics"] = current_nics + self._save_nic_config(self.config) # 변경된 설정 저장 + + self.setup_main_routing() # 메인 라우팅 테이블은 전체 NIC 기반으로 재설정 self.verify_configuration() self.create_startup_script() - - # udev 모니터링 시스템 설정 추가 - self.setup_udev_monitoring() - self.run_connectivity_test() print("\n" + "=" * 50) @@ -772,7 +745,6 @@ for nic_name, nic_config in config['nics'].items(): print("1. 외부에서 들어온 패킷은 동일한 NIC로 응답") print("2. 내부 → 외부 패킷은 metric 우선순위에 따라 라우팅") print("3. 시스템 재시작시 자동 적용됨") - print("4. 새로운 NIC 추가/변경시 자동 재설정") return True @@ -784,24 +756,28 @@ for nic_name, nic_config in config['nics'].items(): """설정 제거""" self.logger.info("Policy routing 설정 제거 중...") - # Policy rules 제거 - for nic_name, nic_config in self.config["nics"].items(): - ip_addr = nic_config["ip"] - self.run_command( - f"ip rule del from {ip_addr}/32 table {nic_name}", ignore_error=True - ) - self.run_command( - f"ip rule del to {ip_addr}/32 table {nic_name}", ignore_error=True - ) - self.run_command(f"ip route flush table {nic_name}", ignore_error=True) + # 현재 감지된 NIC와 저장된 NIC를 모두 고려하여 제거 + current_nics = self.auto_detect_network_config()["nics"] + previous_config = self._load_nic_config() + all_nics_to_remove = {**current_nics, **previous_config.get("nics", {})} + + for nic_name, nic_config in all_nics_to_remove.items(): + self._remove_single_nic_config(nic_name, nic_config) # 시작 스크립트 제거 startup_script = Path("/etc/network/if-up.d/policy-routing-python") if startup_script.exists(): startup_script.unlink() - # udev 모니터링 시스템 제거 - self.remove_udev_monitoring() + # 저장된 NIC 설정 파일 제거 + if self.config_file_path.exists(): + try: + self.config_file_path.unlink() + self.logger.info( + f"저장된 NIC 설정 파일 제거 완료: {self.config_file_path}" + ) + except Exception as e: + self.logger.warning(f"저장된 NIC 설정 파일 제거 실패: {e}") self.logger.info("설정 제거 완료") @@ -814,7 +790,7 @@ def main(): ) parser.add_argument( "action", - choices=["setup", "remove", "verify", "detect", "monitor"], + choices=["setup", "remove", "verify", "detect"], help="수행할 작업", ) @@ -830,8 +806,6 @@ def main(): manager.verify_configuration() elif args.action == "detect": manager.print_detected_config() - elif args.action == "monitor": - manager.check_interface_changes() if __name__ == "__main__":