마크 기반 VPN 방화벽에 막힌 libvirt 게스트 DNS 수정
마크 기반 VPN 방화벽은 어떤 필터 체인이 보기도 전에 게스트 DNS를 버립니다. mangle 우선순위 규칙 하나로 쿼리와 그 응답을 마크하여 LAN을 열지 않고 해결합니다.
기본 libvirt 네트워크의 게스트는 WireGuard 인터페이스가 올라오기 전까지 이름을 완벽하게 해석하지만, 올라오면 모든 조회가 같은 지점에서 멈춥니다. dnsmasq 은 여전히 192.168.122.1:53 에서 수신 대기 중이고, 게스트는 여전히 그 주소를 리졸버로 사용하며, 쿼리도 여전히 도착합니다. 달라진 것은 호스트 자체의 업스트림 쿼리 또는 그 응답이 이제 편집하려고 생각할 수 있는 어떤 필터 체인보다 먼저 평가되는 규칙에 의해 버려진다는 점입니다. 해결책은 응답이 상속받는 연결에 충분히 일찍 적용되는 마크 입니다.
터널이 올라오는 순간 게스트는 DNS를 잃습니다
무엇이든 바꾸기 전에 형태를 확인합니다. 터널을 내리고 게스트에서 dig @192.168.122.1 example.com 를 실행해 응답하는지 봅니다. 터널을 올리고 반복해 시간 초과되는지 봅니다. 이 두 결과로 한 번에 잘못된 libvirt 네트워크, 잘못된 리졸버 주소, 죽은 dnsmasq를 배제할 수 있습니다.
다음으로 호스트 쪽을 봅니다. dnsmasq가 쿼리와 그 업스트림 전달을 기록한다면 패킷이 도착한 것이고 장애는 송신 쪽입니다. dnsmasq가 아무것도 기록하지 않는다면 게스트 쿼리는 수신 단계에서 버려지고 있으며 예외는 이 문서가 마크하는 훅과 다른 훅에 속합니다.
나중 체인의 accept 규칙이 결코 발동하지 않는 이유
마크 기반 VPN 방화벽은 패킷 마크나 연결 마크를 보고 결정하지, 소스 주소를 보고 결정하지 않습니다. 예상된 마크 없이 물리 인터페이스에 도착하는 모든 것은 거부되며, 그 거부는 마크 검사를 소유한 규칙 집합에서 일어납니다. 나중 체인에 추가한 ip saddr 192.168.122.0/24 accept 줄은 그 패킷에 대해 결코 참조되지 않습니다. 그래서 명백해 보이는 규칙이 맞아 보이면서도 아무것도 바꾸지 못합니다.
뭔가 될 때까지 넓히는 것이 두 번째 함정입니다. 호스트 입력 경로에서 libvirt 서브넷 전체를 허용하면 게스트에게 호스트의 모든 서비스를 열어 주는 셈인데, 실제 필요한 것은 포트 하나입니다.
좁은 예외를 가능하게 하는 메커니즘은 연결 추적입니다. 패킷에 ct mark 를 설정하면 그 값이 패킷에만이 아니라 conntrack 항목에 저장되므로, 그 연결의 모든 패킷이 이후 그 값을 지니며 호스트에서 게스트로 돌아가는 응답도 포함됩니다. 이게 전부입니다. 요청을 한 번 마크하면 돌아오는 경로는 이미 마크되어 있습니다.
VPN 테이블이 보기 전에 쿼리를 마크합니다
예외를 자체 테이블에 두어 나중에 읽는 사람이 VPN 규칙을 해석하지 않고도 찾을 수 있게 합니다:
table inet allowedLocal {
chain guest_dns {
type filter hook input priority mangle; policy accept;
ip saddr 192.168.122.0/24 meta l4proto { tcp, udp } \
th dport 53 ct mark set 0x00000f41
}
}
priority mangle 은 음수이므로 이 체인은 VPN 패키지가 제로 우선순위에 설치하는 일반 필터 체인보다 앞에 놓입니다. 규칙을 유효하게 만드는 것은 훅 순서입니다. VPN 체인 뒤에 정렬되는 체인의 같은 규칙은 장식일 뿐입니다.
마크 번호 자체는 이식할 수 없습니다. 설치된 VPN 규칙이 자체 트래픽에 허용하는 값을 가져와 실행 중인 규칙 집합에서 읽고 사용하십시오. 잘못된 마크는 깨끗하게 로드되고 모든 쿼리에 일치하며 아무것도 통과시키지 않는 규칙을 만듭니다.
구멍은 포트 53 너비로 유지합니다
적힌 그대로 이 규칙은 libvirt 서브넷에서 호스트로 TCP 또는 UDP 포트 53을 통한 DNS만 허용하고 그 외에는 아무것도 허용하지 않습니다. 게스트가 터널 밖 인터넷에 도달하게 하지 않고, 호스트가 게스트 쪽으로 연결을 열게 하지도 않습니다. 나중에 게스트가 다른 호스트 서비스를 필요로 하면 이 규칙을 완화하는 대신 그 대상 포트를 지정한 별도 규칙을 추가하십시오.
무엇을 맞바꾸는지 솔직해지십시오. 리졸버에 도달할 수 있는 게스트는 DNS 쿼리로 임의의 데이터를 터널링할 수도 있습니다. 이 예외는 VPN 경계에 의도적으로 낸 틈이며 로컬 네트워크를 신뢰한다는 선언이 아닙니다. 소스를 192.168.122.0/24 로 제한하면 그 틈이 다른 모든 로컬 서브넷에서 떨어지며, 위협이 중요하다면 그 소스에 대한 쿼리를 세는 것은 저렴합니다.
재부팅 후에도 살아남게 하고 두 절반을 모두 증명합니다
셸 프롬프트에서 입력한 규칙은 다음 부팅 때 사라지고, VPN 인터페이스가 이미 트래픽을 통과시킨 뒤에 로드되는 규칙은 첫 연결에서 경쟁에서 집니다. 테이블을 호스트의 영구 nftables 구성에 두거나 VPN 서비스보다 앞서 정렬된 유닛에 두고, 순서를 가정하지 말고 확인하십시오.
검증에는 세 가지 별개의 답이 필요합니다. 체인이 로드되고 먼저 정렬되었는지 확인하고, conntrack 항목이 양방향으로 마크를 보여 주는지 확인하고, 게스트 조회가 주소를 반환하는지 확인합니다. 그다음 나머지 절반을 확인합니다. 공용 호스트로 가는 게스트의 일반 웹 트래픽은 터널이 내려가 있거나 차단 중일 때 여전히 실패해야 합니다. DNS가 되고 나머지가 모두 새면 당신의 규칙은 위의 것보다 넓습니다.
마크를 넣은 뒤에도 조회가 실패하면 소스를 넓히려는 충동을 참으십시오. dnsmasq가 어디로 전달하는지, 게스트 리졸버가 192.168.122.1 를 가리키는지 아니면 누군가 입력한 공용 주소를 가리키는지, 쿼리가 호스트에 도달하기는 하는지 확인하십시오. 마크는 패킷을 통과시킬 뿐 그 외에는 아무것도 하지 않으므로, 업스트림이 나쁘거나 가상 네트워크가 깨졌다면 규칙을 아무리 추가해도 그대로입니다.