/dev/net/tun 없음, tun은 로드됨: 장치 노드를 고치세요
tun 모듈이 로드되어 있다고 해서 장치 노드가 존재하는 것은 아니다. 드라이버와 노드를 분리해 생각하고, 순서대로 다시 만들고, 인터페이스로 확인하라.
OpenVPN 은 /dev/net/tun 에 대해 errno 2 불만을 남기고 멈추지만, 그 사이 lsmod 은 tun 모듈을 태연히 나열한다. 두 사실은 동시에 참이다. 모듈이 제공하는 것은 드라이버이고, 클라이언트가 여는 경로는 다른 무언가가 만들어야 하는 장치 노드다. 둘을 한 객체로 보기 때문에 모듈을 다시 로드하는 뻔한 수리가 아무것도 바꾸지 못한다.
두 객체, 두 가지 소유자
커널 모듈과 장치 노드는 각자의 수명 주기를 가진 독립된 객체다. 모듈을 로드하면 드라이버가 등록되고, devtmpfs가 있는 시스템에서는 커널이 해당 노드 생성을 요청한다. devtmpfs가 없는 시스템에서는 아무것도 만들어지지 않고, 드라이버는 자신을 가리키는 경로도 없이 그 자리에 남는다. 모듈은 동시에 로드되어 있고 건강하면서도 파일 시스템으로는 전혀 닿을 수 없을 수 있다.
그래서 오류 문구와 모듈 목록은 서로 모순되지 않는다. 클라이언트가 경로를 열고, 경로가 없으며, 커널은 tun 드라이버에 물어보기도 전에 ENOENT를 돌려준다. 모듈을 다시 로드해도 없는 경로는 고쳐지지 않고, 경로를 만들어도 없는 드라이버는 고쳐지지 않는다.
lsmod | grep -w tun
stat -c "%F %t:%T" /dev/net/tun
grep " /dev " /proc/mounts
devtmpfs와 명시적 mknod
보통 두 가지 메커니즘이 그 노드를 만들고, 어느 쪽이 책임져야 하는지 알면 무엇을 고쳐야 하는지 알 수 있다. devtmpfs는 마운트된 인스턴스에서 커널이 장치 노드를 만들게 하고, udev가 그 위에 소유권과 권한을 적용한다. 명시적 mknod 은 둘 다 부팅 시 돌지 않는 이미지와 네임스페이스를 위한 수동 경로다.
의도가 아니라 결과를 비교하라. devtmpfs가 /dev 에 마운트되어 있는데도 노드가 없다면, 드라이버가 생각한 그것이 아니거나 부팅 후 무언가가 노드를 지운 것이다. /dev 이 풀린 rootfs 안의 평범한 디렉터리라면 자동 메커니즘은 없고, 이미지를 만드는 사람이 노드를 만들어야 한다.
stat -c "%F major=%t minor=%T" /dev/net/tun
# expect: character special file major=a minor=c8
두 메커니즘 모두 돌지 않는 곳에서는 노드를 손으로 만들어야 하고, 순서가 중요하다. 먼저 상위 디렉터리를 만든다. mknod 은 그것을 지어낼 수 없기 때문이다. 다음은 문자 장치, 그다음은 root만 쓸 수 있게 하는 모드. 노드를 만들기 전에 모듈을 다시 로드하고, 기존 경로는 ls 이 그것이 장치가 아니라 디렉터리임을 보여준 뒤에만 지운다.
sudo modprobe tun
sudo mkdir -p /dev/net
sudo mknod /dev/net/tun c 10 200
sudo chmod 600 /dev/net/tun
ls -l /dev/net/tun
가장 빠른 판별자는 stat 이 보고하는 유형이며, 그 경로에 디렉터리가 있는 것이 최소 이미지에서 가장 흔한 뜻밖의 일이다. 선의로 쓴 mkdir 이 Dockerfile에 있으면 바로 그것이 남고, 모듈을 아무리 로드해도 디렉터리가 문자 장치로 바뀌지 않는다. Major 10과 minor 200이 tun의 등록된 쌍이며, 십육진수로는 a와 c8로 표현된다.
네 가지 흔적, 네 가지 원인
네 가지 원인이 거의 모든 사례를 설명하고, 각각은 로그에 다른 흔적을 남긴다. 부팅 환경부터 보라. 디스크 없는 이미지, 임베디드 보드, initramfs 단계는 흔히 devtmpfs도 udev가 관리하는 /dev도 마운트하지 않으므로 어떤 프로세스도 노드를 쓰지 않는다. 다음으로 /dev을 호스팅하는 것의 마운트 옵션을 읽어라. nodev 은 경로가 존재하도록 두면서 여는 것은 거부하기 때문이다. 강화된 호스트에서 가져온 bind 마운트는 스스로 알리지 않고 이 플래그를 지닌다. 읽기 전용 마운트는 더 이르고 더 크게 실패한다. mknod 은 거기서 EROFS를 돌려주고, EPERM은 파일 시스템이 자기를 지키는 것이 아니라 프로세스에 능력이 없다는 뜻이다. 마지막으로 자체 /dev tmpfs를 가진 컨테이너 런타임은 노드를 자기 네임스페이스에 주입해야 하고, 그 장치 cgroup이 major와 minor 쌍을 허용해야 한다.
권한을 다시 바꾸기 전에 정책 계층을 확인하라. SELinux와 AppArmor는 파일 속성이 모두 허용하는 열기를 거부할 수 있고, 그 거부는 클라이언트 출력이 아니라 감사 로그에 나타난다. 거부에 한 번도 나타나지 않는 권한 변경은 완전히 엉뚱한 계층에 쓴 시간이다.
sudo journalctl -k -n 20 --no-pager
sudo ausearch -m AVC -ts recent | tail -5
목록이 아니라 인터페이스로 증명하라
성공한 목록은 경로가 존재한다는 것만 증명한다 . 건강한 tun 노드를 읽기용으로 열면 호출은 오류로 돌아온다. 장치에 읽기 의미론이 없고 일반 파일도 아니기 때문이다. 그 오류가 여기서는 성공 신호이며, 그것을 알아보면 고장으로 쫓아다니지 않게 된다.
head -c 1 /dev/net/tun
head: error reading '/dev/net/tun': File descriptor in bad state
결정적인 시험은 인터페이스를 만든다. 드라이버와 노드, 그리고 당신의 권한을 한 단계에서 시험하기 때문이다. 추가가 성공하고 링크가 나타나면 셋 다 동작한다. 실패하면 errno가 원인을 가른다. ENOENT는 쓸 만한 노드가 없다는 뜻, EPERM은 능력이나 마운트 플래그, 정책을 가리키고, EBUSY는 다른 프로세스가 이미 인터페이스를 가지고 있다는 뜻이다.
sudo ip tuntap add dev tun7 mode tun
ip -brief link show tun7
sudo ip tuntap del dev tun7 mode tun
그 시험은 실제로 실패하는 문맥에서 실행하라. 서비스 유닛, 컨테이너, 예약 작업은 로그인 셸과 다른 능력 집합을 가질 수 있다. 잘못된 문맥에서 통과한 시험은 실패하는 문맥에 대해 아무것도 증명하지 못한다.
장치가 문제가 아닐 때
인터페이스 시험이 성공하는데 클라이언트가 여전히 실패한다면 남은 문제는 프로필, 서비스 문맥, 경로이며 장치는 정상이다. 그때가 장치 노드 편집을 멈출 때다. 더 많은 변경은 동작하는 시스템과의 거리만 벌린다.
매번 새로 만들지 말고 같은 다섯 가지를 확인하라. 모듈이 경쟁 모듈 없이 로드되었는가, 경로가 올바른 major와 minor를 가진 문자 장치인가, 마운트 옵션이 깨끗한가, 프로세스 문맥이 능력을 지녔는가, 실패 시각에 정책 거부가 있는가. 다섯을 모두 통과한 고장은 메시지가 무엇을 말하든 장치 문제가 아니다.