/dev/net/tun thiếu dù tun đã nạp: sửa nút thiết bị
Mô-đun tun đã nạp không chứng minh nút thiết bị tồn tại. Tách trình điều khiển khỏi nút, dựng lại theo thứ tự và xác minh bằng giao diện.
OpenVPN dừng với lỗi errno 2 về /dev/net/tun trong khi lsmod vẫn liệt kê mô-đun tun một cách thoải mái. Cả hai sự thật đều đúng cùng lúc. Mô-đun cung cấp trình điều khiển, còn đường dẫn mà ứng dụng mở là một nút thiết bị mà thứ khác phải tạo ra. Việc coi hai thứ đó là một đối tượng chính là lý do cách sửa hiển nhiên là nạp lại mô-đun không thay đổi được gì.
Hai đối tượng, hai chủ sở hữu khác nhau
Mô-đun hạt nhân và nút thiết bị là hai đối tượng độc lập với vòng đời độc lập. Việc nạp mô-đun đăng ký một trình điều khiển, và trên hệ thống có devtmpfs hạt nhân yêu cầu tạo nút tương ứng. Trên hệ thống không có devtmpfs, không gì tạo ra gì cả, và trình điều khiển nằm đó mà không có đường dẫn nào trỏ tới. Mô-đun có thể vừa được nạp, vừa khỏe mạnh, vừa hoàn toàn không thể tiếp cận qua hệ thống tệp trong cùng một thời điểm.
Vì vậy nội dung lỗi và danh sách mô-đun không hề mâu thuẫn. Ứng dụng mở một đường dẫn, đường dẫn không tồn tại, và hạt nhân trả về ENOENT trước khi trình điều khiển tun được hỏi tới. Nạp lại mô-đun không sửa được đường dẫn thiếu, và tạo đường dẫn không sửa được trình điều khiển thiếu.
lsmod | grep -w tun
stat -c "%F %t:%T" /dev/net/tun
grep " /dev " /proc/mounts
devtmpfs so với mknod tường minh
Thông thường hai cơ chế tạo ra nút đó, và biết cơ chế nào đáng lẽ phải chịu trách nhiệm cho bạn biết cần sửa gì. devtmpfs để hạt nhân tạo nút thiết bị trong một phiên bản đã gắn kết, còn udev áp quyền sở hữu và quyền truy cập lên chúng. Một lệnh mknod tường minh là con đường thủ công cho ảnh và không gian tên nơi cả hai đều không chạy khi khởi động.
Hãy so sánh kết quả chứ không phải ý định. Nếu devtmpfs được gắn tại /dev mà nút vẫn thiếu, thì trình điều khiển không phải thứ bạn giả định, hoặc có gì đó đã xóa nút sau khi khởi động. Nếu /dev là một thư mục thường trong rootfs đã giải nén, thì không có cơ chế tự động nào và người dựng ảnh phải tạo nút.
stat -c "%F major=%t minor=%T" /dev/net/tun
# expect: character special file major=a minor=c8
Ở nơi không cơ chế nào chạy, phải tạo nút bằng tay, và thứ tự rất quan trọng. Trước hết tạo thư mục cha, vì mknod không thể tự nghĩ ra nó, sau đó thiết bị ký tự, rồi một chế độ chỉ cho root ghi. Nạp lại mô-đun trước khi tạo nút, và chỉ xóa một đường dẫn đang có sau khi ls đã cho thấy đó là thư mục chứ không phải thiết bị.
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
Dấu phân biệt nhanh nhất là kiểu mà stat báo cáo, và một thư mục tại đường dẫn đó là bất ngờ phổ biến nhất trong các ảnh tối giản. Một lệnh mkdir thiện chí trong Dockerfile để lại đúng như vậy, và không lượng nạp mô-đun nào thay được một thư mục bằng thiết bị ký tự. Major 10 với minor 200 là cặp đã đăng ký cho tun, biểu diễn dạng thập lục phân là a và c8.
Bốn dấu vết, bốn nguyên nhân
Bốn nguyên nhân giải thích gần như mọi trường hợp, và mỗi nguyên nhân để lại một dấu vết khác nhau trong nhật ký. Hãy bắt đầu từ môi trường khởi động. Ảnh không ổ đĩa, bo mạch nhúng và các giai đoạn initramfs thường không gắn cả devtmpfs lẫn /devdo udev quản lý, nên không tiến trình nào ghi nút. Sau đó đọc các tùy chọn gắn kết của thứ chứa /dev, vì nodev cho phép đường dẫn tồn tại nhưng từ chối mở nó, và một lần gắn bind lấy từ máy chủ đã tăng cường mang cờ đó mà không tự thông báo. Gắn chỉ đọc thất bại sớm hơn và to hơn: mknod trả về EROFS ở đó, còn EPERM nghĩa là tiến trình thiếu năng lực chứ không phải hệ thống tệp tự bảo vệ. Cuối cùng, một runtime container có /dev tmpfs riêng cần nút được tiêm vào không gian tên của chính nó, và cgroup thiết bị của nó phải cho phép cặp major và minor.
Kiểm tra tầng chính sách trước khi đổi quyền lần nữa. SELinux và AppArmor có thể từ chối một lần mở mà mọi thuộc tính tệp đều cho phép, và sự từ chối đó xuất hiện trong nhật ký kiểm toán chứ không phải đầu ra của ứng dụng. Một thay đổi quyền không bao giờ xuất hiện trong từ chối là thời gian dành cho tầng hoàn toàn sai.
sudo journalctl -k -n 20 --no-pager
sudo ausearch -m AVC -ts recent | tail -5
Hãy chứng minh bằng giao diện, không phải danh sách
Một danh sách thành công chỉ chứng minh rằng có một đường dẫn và không hơn. Mở một nút tun khỏe mạnh để đọc và lời gọi trả về lỗi, vì thiết bị không có ngữ nghĩa đọc và không phải tệp thường. Lỗi đó ở đây chính là tín hiệu thành công, và nhận ra nó giúp bạn không đuổi theo nó như một sự cố.
head -c 1 /dev/net/tun
head: error reading '/dev/net/tun': File descriptor in bad state
Phép thử quyết định tạo một giao diện, vì nó kiểm tra trình điều khiển, nút và quyền của bạn trong một bước. Nếu việc thêm thành công và liên kết xuất hiện, cả ba đều hoạt động. Khi nó thất bại, errno phân tách nguyên nhân: ENOENT nghĩa là không có nút dùng được, EPERM chỉ vào năng lực, cờ gắn kết hoặc chính sách, còn EBUSY nghĩa là tiến trình khác đã sở hữu giao diện.
sudo ip tuntap add dev tun7 mode tun
ip -brief link show tun7
sudo ip tuntap del dev tun7 mode tun
Hãy chạy phép thử đó trong ngữ cảnh thực sự thất bại, vì một đơn vị dịch vụ, một container hay một tác vụ theo lịch có thể mang bộ năng lực khác với shell đăng nhập của bạn. Một phép thử đạt trong ngữ cảnh sai không chứng minh gì về ngữ cảnh đang thất bại.
Khi thiết bị không phải là vấn đề
Nếu phép thử giao diện thành công mà ứng dụng vẫn thất bại, vấn đề còn lại là hồ sơ, ngữ cảnh dịch vụ hoặc tuyến đường, và thiết bị vẫn ổn. Đó là lúc dừng sửa nút thiết bị, vì thay đổi thêm chỉ làm tăng khoảng cách với một hệ thống đang chạy.
Mỗi lần hãy chạy đúng năm kiểm tra đó thay vì nghĩ ra cái mới: mô-đun có được nạp mà không có mô-đun đối thủ không, đường dẫn có phải thiết bị ký tự với major và minor đúng không, các tùy chọn gắn kết có sạch không, ngữ cảnh tiến trình có mang năng lực không, và có sự từ chối chính sách tại thời điểm thất bại không. Một sự cố sống sót qua cả năm không phải vấn đề thiết bị, bất kể thông báo nói gì.