mirror of
https://github.com/jung-geun/policy-routing.git
synced 2025-12-20 02:34:39 +09:00
Netlink 상수 추가 및 인터페이스 라우팅에 메트릭 지원 추가
This commit is contained in:
@@ -33,13 +33,25 @@ RTM_DELLINK = 17
|
|||||||
RTM_NEWADDR = 20
|
RTM_NEWADDR = 20
|
||||||
RTM_DELADDR = 21
|
RTM_DELADDR = 21
|
||||||
|
|
||||||
|
# Pylance 오류 해결을 위한 추가 Netlink 상수 정의
|
||||||
|
# 실제 시스템의 socket 모듈에서 이 값들이 노출되지 않을 경우를 대비
|
||||||
|
try:
|
||||||
|
_AF_NETLINK = socket.AF_NETLINK
|
||||||
|
_SOL_NETLINK = socket.SOL_NETLINK
|
||||||
|
_NETLINK_ADD_MEMBERSHIP = socket.NETLINK_ADD_MEMBERSHIP
|
||||||
|
except AttributeError:
|
||||||
|
# Fallback for environments where these are not directly exposed
|
||||||
|
_AF_NETLINK = 16 # Common value for AF_NETLINK
|
||||||
|
_SOL_NETLINK = 270 # Common value for SOL_NETLINK
|
||||||
|
_NETLINK_ADD_MEMBERSHIP = 1 # Common value for NETLINK_ADD_MEMBERSHIP
|
||||||
|
|
||||||
# 기본 설정
|
# 기본 설정
|
||||||
DEFAULT_CONFIG = {
|
DEFAULT_CONFIG = {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"log_level": "INFO", # DEBUG, INFO, WARNING, ERROR
|
"log_level": "INFO", # DEBUG, INFO, WARNING, ERROR
|
||||||
"check_interval": 5, # 더 빠른 체크
|
"check_interval": 5, # 더 빠른 체크
|
||||||
"interfaces": {},
|
"interfaces": {},
|
||||||
"global_settings": {"base_table_id": 100, "base_priority": 30000},
|
"global_settings": {"base_table_id": 100, "base_priority": 30000, "default_metric": 1000},
|
||||||
"monitoring": {"use_netlink": True, "use_udev": True, "use_polling": True},
|
"monitoring": {"use_netlink": True, "use_udev": True, "use_polling": True},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +68,7 @@ class NetlinkMonitor:
|
|||||||
def start(self):
|
def start(self):
|
||||||
"""Netlink 모니터링 시작"""
|
"""Netlink 모니터링 시작"""
|
||||||
try:
|
try:
|
||||||
self.sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, NETLINK_ROUTE)
|
self.sock = socket.socket(_AF_NETLINK, socket.SOCK_RAW, NETLINK_ROUTE)
|
||||||
self.sock.bind((os.getpid(), 0))
|
self.sock.bind((os.getpid(), 0))
|
||||||
|
|
||||||
# 관심 있는 그룹에 가입
|
# 관심 있는 그룹에 가입
|
||||||
@@ -65,10 +77,10 @@ class NetlinkMonitor:
|
|||||||
) # RTNLGRP_LINK, RTNLGRP_IPV4_IFADDR
|
) # RTNLGRP_LINK, RTNLGRP_IPV4_IFADDR
|
||||||
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 65536)
|
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 65536)
|
||||||
self.sock.setsockopt(
|
self.sock.setsockopt(
|
||||||
socket.SOL_NETLINK, socket.NETLINK_ADD_MEMBERSHIP, 1
|
_SOL_NETLINK, _NETLINK_ADD_MEMBERSHIP, 1
|
||||||
) # RTNLGRP_LINK
|
) # RTNLGRP_LINK
|
||||||
self.sock.setsockopt(
|
self.sock.setsockopt(
|
||||||
socket.SOL_NETLINK, socket.NETLINK_ADD_MEMBERSHIP, 5
|
_SOL_NETLINK, _NETLINK_ADD_MEMBERSHIP, 5
|
||||||
) # RTNLGRP_IPV4_IFADDR
|
) # RTNLGRP_IPV4_IFADDR
|
||||||
|
|
||||||
self.running = True
|
self.running = True
|
||||||
@@ -368,7 +380,7 @@ class PolicyRoutingManager:
|
|||||||
self.logger.error(f"라우팅 테이블 설정 실패: {e}")
|
self.logger.error(f"라우팅 테이블 설정 실패: {e}")
|
||||||
|
|
||||||
def apply_interface_routing(
|
def apply_interface_routing(
|
||||||
self, interface_info: Dict, table_id: int, priority: int
|
self, interface_info: Dict, table_id: int, priority: int, metric: int
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""인터페이스별 라우팅 규칙 적용 (네트워크 계산 수정)"""
|
"""인터페이스별 라우팅 규칙 적용 (네트워크 계산 수정)"""
|
||||||
name = interface_info["name"]
|
name = interface_info["name"]
|
||||||
@@ -378,7 +390,7 @@ class PolicyRoutingManager:
|
|||||||
|
|
||||||
self.logger.info(f"=== {name} 인터페이스 라우팅 설정 시작 ===")
|
self.logger.info(f"=== {name} 인터페이스 라우팅 설정 시작 ===")
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
f"IP: {ip}, Gateway: {gateway}, Table: {table_id}, Priority: {priority}"
|
f"IP: {ip}, Gateway: {gateway}, Table: {table_id}, Priority: {priority}, Metric: {metric}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not all([name, ip, gateway]):
|
if not all([name, ip, gateway]):
|
||||||
@@ -447,6 +459,8 @@ class PolicyRoutingManager:
|
|||||||
name,
|
name,
|
||||||
"table",
|
"table",
|
||||||
str(table_id),
|
str(table_id),
|
||||||
|
"metric",
|
||||||
|
str(metric),
|
||||||
],
|
],
|
||||||
ignore_errors=["File exists"],
|
ignore_errors=["File exists"],
|
||||||
)
|
)
|
||||||
@@ -572,16 +586,17 @@ class PolicyRoutingManager:
|
|||||||
config_changed = True
|
config_changed = True
|
||||||
self.logger.info(f"새 인터페이스 {name} 자동 추가됨")
|
self.logger.info(f"새 인터페이스 {name} 자동 추가됨")
|
||||||
|
|
||||||
if self.config["interfaces"][name]["enabled"]:
|
if self.config["interfaces"][name]["enabled"]:
|
||||||
iface_config = self.config["interfaces"][name]
|
iface_config = self.config["interfaces"][name]
|
||||||
table_id = iface_config["table_id"]
|
table_id = iface_config["table_id"]
|
||||||
priority = (
|
priority = (
|
||||||
self.config["global_settings"]["base_priority"]
|
self.config["global_settings"]["base_priority"]
|
||||||
+ iface_config["priority"]
|
+ iface_config["priority"]
|
||||||
)
|
)
|
||||||
|
metric = iface_config.get("metric", self.config["global_settings"]["default_metric"])
|
||||||
|
|
||||||
self.setup_routing_table(name, table_id)
|
self.setup_routing_table(name, table_id)
|
||||||
self.apply_interface_routing(info, table_id, priority)
|
self.apply_interface_routing(info, table_id, priority, metric)
|
||||||
|
|
||||||
if config_changed:
|
if config_changed:
|
||||||
self.save_config(self.config)
|
self.save_config(self.config)
|
||||||
@@ -661,6 +676,7 @@ class PolicyRoutingManager:
|
|||||||
"table_id": table_id,
|
"table_id": table_id,
|
||||||
"priority": 100,
|
"priority": 100,
|
||||||
"health_check_target": "8.8.8.8",
|
"health_check_target": "8.8.8.8",
|
||||||
|
"metric": self.config["global_settings"]["default_metric"],
|
||||||
}
|
}
|
||||||
self.save_config(self.config)
|
self.save_config(self.config)
|
||||||
|
|
||||||
@@ -669,9 +685,10 @@ class PolicyRoutingManager:
|
|||||||
priority = (
|
priority = (
|
||||||
self.config["global_settings"]["base_priority"] + iface_config["priority"]
|
self.config["global_settings"]["base_priority"] + iface_config["priority"]
|
||||||
)
|
)
|
||||||
|
metric = iface_config.get("metric", self.config["global_settings"]["default_metric"])
|
||||||
|
|
||||||
self.setup_routing_table(interface_name, table_id)
|
self.setup_routing_table(interface_name, table_id)
|
||||||
success = self.apply_interface_routing(target_interface, table_id, priority)
|
success = self.apply_interface_routing(target_interface, table_id, priority, metric)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
print(f"✅ {interface_name} 인터페이스 규칙 적용 완료")
|
print(f"✅ {interface_name} 인터페이스 규칙 적용 완료")
|
||||||
@@ -711,6 +728,7 @@ class PolicyRoutingManager:
|
|||||||
"table_id": table_id,
|
"table_id": table_id,
|
||||||
"priority": 100,
|
"priority": 100,
|
||||||
"health_check_target": "8.8.8.8",
|
"health_check_target": "8.8.8.8",
|
||||||
|
"metric": self.config["global_settings"]["default_metric"],
|
||||||
}
|
}
|
||||||
config_changed = True
|
config_changed = True
|
||||||
self.logger.info(f"새 인터페이스 {name} 자동 추가됨")
|
self.logger.info(f"새 인터페이스 {name} 자동 추가됨")
|
||||||
@@ -722,9 +740,10 @@ class PolicyRoutingManager:
|
|||||||
self.config["global_settings"]["base_priority"]
|
self.config["global_settings"]["base_priority"]
|
||||||
+ iface_config["priority"]
|
+ iface_config["priority"]
|
||||||
)
|
)
|
||||||
|
metric = iface_config.get("metric", self.config["global_settings"]["default_metric"])
|
||||||
|
|
||||||
self.setup_routing_table(name, table_id)
|
self.setup_routing_table(name, table_id)
|
||||||
self.apply_interface_routing(info, table_id, priority)
|
self.apply_interface_routing(info, table_id, priority, metric)
|
||||||
|
|
||||||
if config_changed:
|
if config_changed:
|
||||||
self.save_config(self.config)
|
self.save_config(self.config)
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ class TestPolicyRoutingManager(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
table_id = 101
|
table_id = 101
|
||||||
priority = 30100
|
priority = 30100
|
||||||
|
metric = 1000 # Add metric for testing
|
||||||
|
|
||||||
# run_command의 side_effect를 설정하여 각 호출에 대한 응답을 시뮬레이션
|
# run_command의 side_effect를 설정하여 각 호출에 대한 응답을 시뮬레이션
|
||||||
# 순서대로 호출될 명령에 대한 응답을 정의
|
# 순서대로 호출될 명령에 대한 응답을 정의
|
||||||
@@ -213,14 +214,14 @@ class TestPolicyRoutingManager(unittest.TestCase):
|
|||||||
(True, ""), # 1. ip rule del from 192.168.1.10/32
|
(True, ""), # 1. ip rule del from 192.168.1.10/32
|
||||||
(True, ""), # 2. ip route show table 101 (empty, so flush is skipped)
|
(True, ""), # 2. ip route show table 101 (empty, so flush is skipped)
|
||||||
(True, ""), # 3. ip route add network
|
(True, ""), # 3. ip route add network
|
||||||
(True, ""), # 4. ip route add default
|
(True, ""), # 4. ip route add default (with metric)
|
||||||
(True, ""), # 5. ip rule add from
|
(True, ""), # 5. ip rule add from
|
||||||
(True, ""), # 6. ip rule add iif
|
(True, ""), # 6. ip rule add iif
|
||||||
(True, "30100: from 192.168.1.10 lookup 101"), # 7. ip rule show (verification)
|
(True, "30100: from 192.168.1.10 lookup 101"), # 7. ip rule show (verification)
|
||||||
(True, "default via 192.168.1.1 dev eth0 table 101") # 8. ip route show table 101 (verification)
|
(True, "default via 192.168.1.1 dev eth0 table 101 metric 1000") # 8. ip route show table 101 (verification)
|
||||||
]
|
]
|
||||||
|
|
||||||
success = self.manager.apply_interface_routing(interface_info, table_id, priority)
|
success = self.manager.apply_interface_routing(interface_info, table_id, priority, metric)
|
||||||
|
|
||||||
self.assertTrue(success)
|
self.assertTrue(success)
|
||||||
self.manager.logger.error.assert_not_called()
|
self.manager.logger.error.assert_not_called()
|
||||||
@@ -231,7 +232,7 @@ class TestPolicyRoutingManager(unittest.TestCase):
|
|||||||
mock.call(['ip', 'rule', 'del', 'from', '192.168.1.10/32'], ignore_errors=['No such file or directory']), # This is the second call
|
mock.call(['ip', 'rule', 'del', 'from', '192.168.1.10/32'], ignore_errors=['No such file or directory']), # This is the second call
|
||||||
# mock.call(['ip', 'route', 'flush', 'table', '101'], ignore_errors=['No such file or directory']), # Removed, as table is empty
|
# mock.call(['ip', 'route', 'flush', 'table', '101'], ignore_errors=['No such file or directory']), # Removed, as table is empty
|
||||||
mock.call(['ip', 'route', 'add', '192.168.1.0/24', 'dev', 'eth0', 'src', '192.168.1.10', 'table', '101'], ignore_errors=['File exists']),
|
mock.call(['ip', 'route', 'add', '192.168.1.0/24', 'dev', 'eth0', 'src', '192.168.1.10', 'table', '101'], ignore_errors=['File exists']),
|
||||||
mock.call(['ip', 'route', 'add', 'default', 'via', '192.168.1.1', 'dev', 'eth0', 'table', '101'], ignore_errors=['File exists']),
|
mock.call(['ip', 'route', 'add', 'default', 'via', '192.168.1.1', 'dev', 'eth0', 'table', '101', 'metric', '1000'], ignore_errors=['File exists']),
|
||||||
mock.call(['ip', 'rule', 'add', 'from', '192.168.1.10/32', 'table', '101', 'pref', '30100'], ignore_errors=['File exists']),
|
mock.call(['ip', 'rule', 'add', 'from', '192.168.1.10/32', 'table', '101', 'pref', '30100'], ignore_errors=['File exists']),
|
||||||
mock.call(['ip', 'rule', 'add', 'iif', 'eth0', 'table', '101', 'pref', '30101'], ignore_errors=['File exists']),
|
mock.call(['ip', 'rule', 'add', 'iif', 'eth0', 'table', '101', 'pref', '30101'], ignore_errors=['File exists']),
|
||||||
mock.call(['ip', 'rule', 'show']),
|
mock.call(['ip', 'rule', 'show']),
|
||||||
|
|||||||
Reference in New Issue
Block a user