Sự kiện hook của OpenVPN: vì sao tên và dấu thời gian không phải là khóa
Hook ngắt kết nối báo cáo thời điểm kết nối, không phải thời điểm ngắt kết nối. Khi ghép với tên người dùng, nó có thể đóng nhầm phiên; hãy sở hữu một danh tính thay thế.
Một bản ghi ngắt kết nối đến sau khi kết nối thứ hai dưới cùng tên đã bắt đầu, và trình xử lý cập nhật hàng mới nhất cho người dùng đó đóng nhầm phiên. Lỗi không phải ở việc so sánh dấu thời gian. Lỗi là ở giả định rằng một tên cộng một giây trọn vẹn xác định một phiên. Các sự kiện hook mô tả các kết nối, và một kết nối cần một khóa chỉ thuộc về nó.
Hãy bắt đầu từ ý nghĩa thực sự của các biến ngắt kết nối. Chỉ khi đó việc chọn định danh mới có ý nghĩa.
Hook ngắt kết nối thực sự mang gì
Trong Community OpenVPN 2.6, tài liệu tham chiếu hook time_unix định nghĩa nó là thời điểm client kết nối; giá trị này được đặt trước khi hook kết nối chạy và không được định nghĩa lại khi hook ngắt kết nối kích hoạt sau đó. time_duration cho biết độ dài của phiên đó tính bằng giây, nên phần cuối xấp xỉ thời điểm kết nối cộng với khoảng thời gian. Cả hai đều là giây nguyên, và không cái nào là thời điểm cơ sở dữ liệu của bạn xử lý sự kiện. Chính việc tách ba thời điểm này ra mới làm cho một tương quan đứng vững, thay vì chỉ trông đúng trong lúc kiểm thử.
Phép tính này đáng được ghi lại, vì đây là thời điểm kết thúc duy nhất mà chính hook cung cấp. Độ trễ xử lý và chênh lệch đồng hồ giữa các máy chủ vẫn nằm ngoài nó.
Vì sao cặp hiển nhiên không duy nhất
Ghép tên với thời điểm kết nối thu hẹp việc tìm kiếm và vẫn không xác định được phiên. Một client có thể kết nối lại trong cùng một giây, nên hai phiên có thể chia sẻ cả hai giá trị. Hai phiên bản daemon, hoặc một lần thay máy chủ vẫn giữ nguyên cấu hình, có thể tạo ra cặp đó từ các tiến trình khác nhau. Địa chỉ đường hầm cũng được tái sử dụng vì lý do đó, khiến chúng trở thành thuộc tính của một phiên chứ không phải định danh cho nó.
Chế độ hỏng hóc là âm thầm. Một lần ngắt kết nối muộn cho phiên đầu tiên khớp với hàng mới nhất và kết thúc một khoản cấp phát mà phiên thứ hai vẫn đang dùng, nên người dùng trải qua một lần mất quyền truy cập ngẫu nhiên mà họ không gây ra. Số hàng trông đúng sau đó khiến vấn đề khó phát hiện hơn.
Danh tính ổn định đến từ đâu
Giao diện quản lý gán cho mỗi client một định danh phiên bản client, và định danh đó chỉ giới hạn trong phiên bản daemon đã phát hành nó. Một khóa ngoài khả dụng kết hợp định danh máy chủ ổn định, một kỷ nguyên thay đổi khi daemon khởi động, và ID client đó. Kỷ nguyên quan trọng vì các định danh khởi động lại cùng tiến trình, và một ID tiến trình trần có thể bị thứ không liên quan dùng lại sau này.
Các biến môi trường được đặt bên trong một script kết nối con không truyền sang script ngắt kết nối sau đó của daemon cha, vì những script đó chạy như các tiến trình riêng với môi trường của chúng. Vì vậy tạo UUID lúc kết nối chỉ là nửa giải pháp. Phía ngắt kết nối phải có khả năng gọi tên cùng UUID đó, nghĩa là danh tính phải sống ở nơi cả hai sự kiện đều có thể chạm tới.
Làm cho việc thu hồi có điều kiện và bất biến
Thu hồi theo chủ sở hữu phiên, không theo người dùng. Một trình xử lý xóa bất kỳ khoản cấp phát nào hiện thuộc về một tên cuối cùng sẽ xóa nhầm, và cách phòng vệ duy nhất là kiểm tra rằng bản ghi bị gỡ là bản ghi mà sự kiện này mô tả.
Giao diện quản lý chỉ chấp nhận một client quản lý được kết nối tại một thời điểm, nên hãy tích hợp với bên đã sở hữu nó thay vì gắn thêm một quan sát viên thứ hai tranh cùng socket.
Xử lý các sự kiện lặp lại mà không thay đổi kết quả hai lần, và giữ các sự kiện không ghép cặp để đối soát thay vì vứt bỏ. Callback ngắt kết nối có các điều kiện vòng đời được ghi lại và có thể không bao giờ đến sau một cú sập hoặc dừng cứng, nên thiết kế cần cách biểu diễn một phiên mà sự kiện kết thúc bị thiếu.
Hãy thử các trường hợp khó xử một cách có chủ đích: hai lần kết nối lại trong một giây, hai phiên bản daemon, một kết nối mà quá trình xử lý thất bại, và một lần khởi động lại daemon giữa phiên. Một khóa sống sót cả bốn trường hợp là khóa đáng tin.