Một Lỗi Phát Sinh Do Vấn Đề Căn Chỉnh Địa Chỉ Bộ Nhớ
Vài ngày nay tôi liên tục rủ rê mọi người tham gia Google+ (G+). Tuy nhiên như ai cũng biết, việc quảng bá dịch vụ này trong “tường lửa” gặp rất nhiều trở ngại. May mắn là người dùng không cần phải vượt tường hoàn toàn - Google có đặt cổng truy cập tại Bắc Kinh, chỉ cần chỉnh sửa tệp hosts trên thiết bị để trỏ các tên miền liên quan đến địa chỉ đó là được. Tôi sẽ không công khai hướng dẫn cụ thể ở đây, mong các bạn cũng đừng chia sẻ trực tiếp trong phần bình luận, trao đổi riêng sẽ tốt hơn. Điều đáng mừng là phiên bản di động của G+ vẫn chưa bị chặn, có lẽ vì m.google.com là cổng chung cho toàn bộ dịch vụ Google trên di động, chặn nó sẽ gây ảnh hưởng quá lớn. Hơn nữa, giao thức https khiến tường lửa không thể phân tích URL để can thiệp từng phần. Nếu một ngày nào đó Google Reader trên điện thoại không thể đọc được, Gmail cũng không nhận được, đó thực sự sẽ là thảm họa.
Trong quá trình hướng dẫn bố tôi sử dụng G+, ông gặp một sự cố: không thể đăng bài trên ứng dụng Android khi dùng mạng gia đình. Tôi phỏng đoán có thể mạng này đã bị chặn. Dù có thể root máy để chỉnh sửa tệp hosts, nhưng đây rõ ràng không phải giải pháp tối ưu. Tôi quyết định remote vào gateway tại nhà - một chiếc máy LinkStation Pro được cài hệ điều hành Debian Linux. Với vi xử lý Arm9, máy này đủ sức chạy dịch vụ DNS giúp toàn bộ thiết bị trong mạng nội bộ truy cập được G+.
Ban đầu tôi nghĩ đến việc cấu hình bind9 - dịch vụ DNS quen thuộc với nhiều người. Tuy nhiên G+ sử dụng hàng loạt tên miền, việc cấu hình thủ công sẽ rất phức tạp. Tôi tin chắc phải có giải pháp đơn giản hơn, nhưng tìm kiếm mãi không thấy. Cuối cùng đành cầu cứu “thánh Twitter”. Quả nhiên chỉ 5 phút sau, hàng loạt “dân lập trình thức đêm” đã đề xuất đủ kiểu giải pháp.
Tôi chọn phương án DNRD của bạn @wuwx vì nó gần nhất với yêu cầu ban đầu của tôi: cần một danh sách kiểu tệp hosts để ưu tiên trỏ các tên miền đến IP chỉ định, còn lại cứ forward query bình thường. Vì apt-get không có sẵn gói cài đặt, tôi đành tải mã nguồn về biên dịch. May mắn là phần mềm viết bằng C chứ không phải C++, giúp chiếc máy yếu ớt của tôi hoàn thành quá trình biên dịch trong vài phút. Tuy nhiên khi cấu hình xong lại không hoạt động như mong muốn - một số tên miền trong danh sách hoạt động tốt, một số lại không.
Là dân lập trình, phản ứng đầu tiên là debug. Tôi nghi ngờ định dạng danh sách có vấn đề, nhưng dnrd không ghi log chi tiết phần này. Tôi đành tự thêm vài dòng log để theo dõi, phát hiện tất cả tên miền đều được tải vào bộ nhớ nhưng không có bất kỳ truy vấn nào khớp. Khi bật mức debug cao nhất (level 4), tôi so sánh kỹ giữa các truy vấn thành công và thất bại: loại truy vấn (query type) thành công là 1 (‘A’ record), thất bại lại là 0 (chưa xác định). Class cũng lần lượt là 1 và 256. Khi phân tích trực tiếp gói tin, 4 byte biểu thị type và class đều là chuỗi 00 01 00 01.
Lập tức tôi nghĩ đến khả năng lệch bit. Vì 256 = 0x100, kiểm tra lại mã nguồn trong src/dns.c thấy đoạn code sau:
y->type = ntohs(*(unsigned short *)(&msg[i])); i += 2; y->class = ntohs( *(unsigned short *)(&msg[i])); i += 2;
Lập tức nhớ lại lời cảnh báo trong cuốn sách học C ngày xưa: “Một số hệ thống yêu cầu địa chỉ bộ nhớ phải căn chỉnh khi đọc dữ liệu kiểu rộng (wide word)”. Thực tế, khi truy vấn DNS, các tên miền có độ dài biến đổi khiến vị trí con trỏ &msg[i] không đảm bảo căn chỉnh đúng 2-byte, dẫn đến việc ép kiểu trực tiếp từ con trỏ này không đáng tin cậy. Điều này hoàn toàn giải thích vì sao một số tên miền hoạt động còn một số khác thì không.
Sau khi xác nhận giả thuyết bằng các dòng log bổ sung, tôi tiến hành sửa lỗi. Cách khắc phục rất đơn giản: thay vì ép kiểu trực tiếp, dùng memcpy để sao chép 2 byte từ &msg[i] vào biến cục bộ kiểu unsigned short trước khi gọi ntohs. Thực tế, phiên bản dnrd cũ cũng từng dùng cách này nhưng不知为何被 thay thế bằng đoạn code hiện tại. Sau khi sửa, mọi thứ hoạt động trơn tru như mong đợi.