Lập trình TCP/IP stack 1: Ethernet và ARP (2016)
(saminiir.com)- Đây là bước đầu tiên trong loạt bài tự xây dựng TCP/IP stack ở không gian người dùng trên Linux để tìm hiểu các tầng mạng, bắt đầu từ xử lý khung Ethernet ở layer 2 và phản hồi ARP
- Lưu lượng mạng mức thấp của kernel được nhận qua thiết bị TAP, và thông qua file descriptor được trả về có thể
read/writebộ đệm Ethernet của thiết bị ảo - Header Ethernet được xử lý với
dmac,smac,ethertype,payload; khi giá trịethertypetừ 1536 trở lên thì là loại payload, còn nhỏ hơn thì biểu thị độ dài payload - ARP ánh xạ động địa chỉ giao thức như địa chỉ IPv4 sang địa chỉ MAC 48-bit, và được triển khai theo luồng phản hồi request đồng thời cập nhật bảng ánh xạ
- Trong bài kiểm tra
arping, phản hồi ARP từ stack tùy biến được kernel Linux nhận ra và thêm mục10.0.0.4vào ARP cache của giao diệntap0
Điểm khởi đầu của TCP/IP stack ở không gian người dùng
- Mục tiêu là hiện thực một TCP/IP stack tối giản ở không gian người dùng trên Linux để hiểu sâu hơn về mạng và lập trình hệ thống
- TCP đã trở nên phức tạp do hơn 30 năm tích lũy nhiều đặc tả, nhưng các thành phần cốt lõi có thể thu gọn thành phân tích header TCP, state machine, điều khiển tắc nghẽn và tính toán thời gian chờ truyền lại
- Ethernet và IP có độ phức tạp thấp hơn TCP, vì vậy loạt bài bắt đầu hiện thực từ layer 2
Nhận lưu lượng Ethernet bằng thiết bị TAP
- Để chặn lưu lượng mạng mức thấp của kernel Linux, bài viết sử dụng thiết bị Linux TAP
- Thiết bị TUN/TAP thường được dùng khi các ứng dụng mạng ở không gian người dùng thao tác với lưu lượng L3/L2 tương ứng
- Tunneling là cách bọc một gói tin vào trong payload của một gói tin khác
- Các chương trình như OpenVPN cũng dùng thiết bị TUN/TAP
- Vì xây dựng network stack từ layer 2 nên cần thiết bị TAP chứ không phải TUN
- Thiết bị TAP được tạo bằng cách mở
/dev/net/tapvà gọiioctl(fd, TUNSETIFF, ...)IFF_TAPchọn thiết bị TAPIFF_NO_PIngăn việc gắn thêm thông tin gói tin không cần thiết trước khung Ethernet
- Sau khi tạo, có thể dùng file descriptor
fdđược trả về để đọc và ghi bộ đệm Ethernet của thiết bị ảo
Định dạng khung Ethernet
- Ethernet là công nghệ nền tảng kết nối các máy tính trong LAN; tiêu chuẩn Ethernet đầu tiên được Digital Equipment Corporation, Intel và Xerox công bố vào năm 1980
- Phiên bản đầu tiên dùng tốc độ khoảng 10Mb/s và truyền thông bán song công, nên cần một giao thức MAC để điều phối luồng dữ liệu
- Trong giao diện Ethernet bán song công, CSMA/CD là cơ chế MAC cần thiết
- 100BASE-T sử dụng cáp twisted-pair để hỗ trợ truyền thông song công toàn phần và thông lượng cao hơn
- Khi switch Ethernet trở nên phổ biến, nhu cầu đối với CSMA/CD phần lớn đã giảm đi
- Tiêu chuẩn Ethernet do nhóm công tác IEEE 802.3 quản lý
Header Ethernet dùng trong phần hiện thực
- Phần hiện thực bao gồm
if_ether.hcủa Linux để dùng ánh xạ giữa ethertype và các giá trị thập lục phân - Header Ethernet được biểu diễn trong struct C với các trường sau
dmac: địa chỉ MAC đíchsmac: địa chỉ MAC nguồnethertype: độ dài hoặc loại payloadpayload: con trỏ payload chứa gói ARP hoặc IPv4
ethertypelà trường 2 octet và ý nghĩa thay đổi theo giá trị- Nếu giá trị từ 1536 trở lên thì biểu thị loại payload như IPv4, ARP
- Nếu nhỏ hơn thì biểu thị độ dài payload
- Khung Ethernet có thể kèm thẻ biểu thị VLAN hoặc QoS, nhưng phần hiện thực này loại trừ frame tag
- Nếu độ dài payload nhỏ hơn kích thước tối thiểu yêu cầu 48 byte khi không có tag, các byte đệm sẽ được thêm vào cuối
- Cuối Ethernet Frame Format có trường Frame Check Sequence dùng CRC để kiểm tra tính toàn vẹn, nhưng phần hiện thực này không xử lý
Cách phân tích khung Ethernet
- Thuộc tính
packedtrong khai báo struct ngăn trình biên dịch GNU C tối ưu layout bộ nhớ của struct bằng cách chèn các byte đệm để căn chỉnh dữ liệu - Việc phân tích trong phần hiện thực dùng cách ép kiểu bộ đệm sang struct giao thức phù hợp
- Ví dụ:
struct eth_hdr *hdr = (struct eth_hdr *) buf;
- Ví dụ:
- Cách có tính di động cao hơn là tuần tự hóa dữ liệu giao thức một cách thủ công
- Trong trường hợp đó, trình biên dịch có thể thêm byte đệm để đáp ứng yêu cầu căn chỉnh dữ liệu theo từng bộ xử lý
- Xử lý khung Ethernet nhận được đi theo một luồng đơn giản
- Đọc bộ đệm từ thiết bị TAP
- Khởi tạo header Ethernet bằng
init_eth_hdr(buf) handle_frame(&netdev, hdr)quyết định bước tiếp theo dựa trên giá trịethertype
Cấu trúc và vai trò của gói ARP
- ARP (Address Resolution Protocol) ánh xạ động địa chỉ giao thức như địa chỉ IPv4 sang địa chỉ MAC là địa chỉ Ethernet 48-bit
- ARP không chỉ giới hạn ở IPv4 mà còn có thể dùng với nhiều giao thức L3 khác
- Ví dụ, CHAOS khai báo địa chỉ giao thức 16-bit
- Trong giao tiếp LAN thông thường, dù biết địa chỉ IP của dịch vụ thì vẫn cần địa chỉ MAC để truyền thực tế
- ARP gửi truy vấn broadcast lên mạng để chủ sở hữu của địa chỉ IP tương ứng tự thông báo địa chỉ phần cứng của mình
Header ARP và payload cho IPv4
- Header ARP gồm các trường sau
hwtype: trường 2 octet biểu thị loại liên kết, với Ethernet có giá trị0x0001protype: trường 2 octet biểu thị loại giao thức, với IPv4 có giá trị0x0800hwsize: trường 1 octet biểu thị kích thước địa chỉ phần cứng, địa chỉ MAC là 6 byteprosize: trường 1 octet biểu thị kích thước địa chỉ giao thức, địa chỉ IPv4 là 4 byteopcode: trường 2 octet biểu thị loại bản tin ARP
- Giá trị
opcodeđược chia thành bốn loại- ARP request:
1 - ARP reply:
2 - RARP request:
3 - RARP reply:
4
- ARP request:
- Dữ liệu ARP cho IPv4 được xử lý bằng struct
arp_ipv4smac: địa chỉ MAC của bên gửisip: địa chỉ IP của bên gửidmac: địa chỉ MAC của bên nhậndip: địa chỉ IP của bên nhận
Thuật toán phân giải địa chỉ và cache
- Thuật toán phân giải địa chỉ trong RFC 826 hoạt động theo luồng kiểm tra loại phần cứng và loại giao thức, sau đó cập nhật bảng ánh xạ, rồi nếu địa chỉ đích là chính mình thì tạo phản hồi
- translation table lưu kết quả ARP để host có thể tra cứu các mục đã có trong cache
- Cache này giúp giảm việc mạng bị lấp đầy không cần thiết bởi các ARP request trùng lặp
- Mã hiện thực nằm trong
arp.c
Kiểm tra phản hồi ARP và bước tiếp theo
- Bài kiểm tra cuối cùng của phần hiện thực ARP là xác nhận nó phản hồi đúng với request
- Kết quả chạy
arping -I tap0 10.0.0.4cho thấy phản hồi unicast quay về từ10.0.0.4với địa chỉ MAC00:0C:29:6D:50:25 - Sau đó, trong đầu ra
arp, xuất hiện mục10.0.0.4 ether 00:0c:29:6d:50:25 tap0trong ARP cache của kernel Linux - Chỉ với xử lý khung Ethernet tối thiểu và hiện thực ARP, có thể xác nhận thiết bị Ethernet tùy biến đã điền ARP cache của host Linux
- Mã nguồn dự án có trên GitHub, và bước tiếp theo là hiện thực ICMP echo/reply cho ping cùng phân tích gói IPv4
1 bình luận
Ý kiến trên Hacker News
Vài năm trước, tôi từng tự làm một network stack ở user space bằng C, xử lý packet thô qua giao diện TUN và khiến nó chạy được ở mức nào đó
Hiện giờ nó có một shell đơn giản để thiết lập địa chỉ IP, route, v.v., còn các packet mạng được chứa trong một cấu trúc lai trông như pha trộn giữa mbuf và sk_buf
Tuy nhiên sau khi hoàn tất phần triển khai UDP, tôi không còn thời gian hay động lực để triển khai TCP nữa; mã nằm ở đây: https://github.com/cakturk/unet
Dĩ nhiên nó gần như là thứ chậm nhất và dễ vỡ nhất trong lịch sử, nhưng nó thực sự chạy được và cũng khá vui. Hy vọng đoạn mã đó vẫn còn sót lại đâu đó
“POSIX port” của lwip cũng lấy các byte Ethernet thô từ thiết bị TUN/TAP theo cùng cách
https://github.com/lwip-tcpip/lwip/blob/master/contrib/ports...
Nếu biên dịch một Linux kernel tối thiểu không có TCP/IP stack thì nó là 400KB, còn thêm TCP/IP stack vào thì thành 800KB
Trong một dự án chỉ cần gửi nhiệt độ, chúng tôi đưa giá trị vào các thông điệp UDP tự làm bằng một chương trình C nhỏ ở user space, nhờ đó giảm được nhiều dung lượng và độ phức tạp
Nếu tắt ARP, bạn có thể đặt cùng một IP cho nhiều server trong cùng mạng
Nếu một server đóng vai trò frontend định tuyến có thể chuyển packet tới giao diện mạng của backend server dựa trên địa chỉ MAC, backend đó sẽ nhận ra mình là đích, đổi IP nguồn/đích rồi trả lời trực tiếp cho client. Khi đó nó không đi qua frontend định tuyến lần nữa
Hoặc có thể không tắt ARP mà thêm địa chỉ IP chung làm alias trên giao diện loopback để đạt hiệu quả tương tự; backend vẫn nhận ra mình là đích đồng thời tránh xung đột ARP. Đây là mẹo mà bộ cân bằng tải phần mềm IBM WebSphere dùng trong thập niên 90–00
Điểm tốt hơn so với cân bằng tải L3 phổ biến hơn là không cần viết lại header của IP packet
Vì vậy nếu dùng cách này thì nên tạo một VLAN riêng
Tôi từng làm một thứ tương tự bằng Python: https://github.com/georgek/notebooks/blob/master/internet.ip...
Có lẽ chất lượng mã kém hơn, và nói thật là thuật toán phân giải địa chỉ cũng chỉ do tôi tự bịa ra. Tôi đã gửi ping tới host trên Internet bằng ICMP thành công
Tôi thích điểm là toàn bộ nằm gọn trong một notebook ngắn. Bài gốc đã lược bỏ trong phần nội dung nhiều chi tiết nằm ở phần mã nguồn lớn hơn được tham chiếu
Tôi không đọc bài này mà chỉ xem Wikipedia để làm. Tuy nhiên từ TCP trở đi độ phức tạp tăng vọt nên tôi hơi mất hứng. Nghe nói phần 3 sẽ nói về đoạn đó, nên có thể một ngày nào đó tôi sẽ đọc và hoàn tất. Nếu quan tâm đến networking, tôi nghĩ đây là việc đáng thử và đáng công với lập trình viên ở bất kỳ trình độ nào
Vài năm trước tôi làm về đo lường trong nhà máy điện hạt nhân. Phần phát triển phía client được làm trên workstation Sun, và thực ra tôi được tuyển nhờ kinh nghiệm TCP/IP; kinh nghiệm đó tôi có được từ môn “Hệ điều hành” ở CMU
Trong khi đó, máy tính của nhà máy điện là minicomputer không có TCP/IP stack, nên nhóm đó phải tự làm stack
Khoảng 1 phút đầu bài có câu “dmac và smac là các trường khá hiển nhiên”, nhưng độc giả không biết chúng là gì có thể bỏ cuộc ngay
Họ sẽ nghĩ: “Bài này dành cho những người thấy các trường này là hiển nhiên. Nó không dành cho mình, nên thôi không đọc nữa”
Hơn nữa, nếu là bài viết về xây dựng network stack thì có thể an toàn giả định rằng độc giả biết về networking ở mức nào đó
Các bài liên quan:
Let’s code a TCP/IP stack (2016) - https://news.ycombinator.com/item?id=27654182 - tháng 6/2021, 49 bình luận
Let’s code a TCP/IP stack, 1: Ethernet & ARP (2016) - https://news.ycombinator.com/item?id=17316487 - tháng 6/2018, 47 bình luận
Let’s Code a TCP/IP Stack: TCP Retransmission - https://news.ycombinator.com/item?id=14701199 - tháng 7/2017, 30 bình luận
Let’s code a TCP/IP stack, 1: Ethernet and ARP - https://news.ycombinator.com/item?id=11234229 - tháng 3/2016, 49 bình luận
Không rõ tác giả lấy địa chỉ IP 10.0.0.4 dùng trong bài kiểm thử phân giải ARP từ đâu
Đó là địa chỉ của cái gì? Là một thiết bị giả có thể truy cập từ thiết bị Ethernet giả tạo ở đây, hay là một thiết bị thực sự có trong mạng của tác giả?
Thiết bị TAP giống như một liên kết Ethernet được mô phỏng bằng phần mềm. Khi gửi packet vào đó, chúng được chuyển thẳng tới chương trình ở user space, và chương trình đó quyết định nó sẽ có địa chỉ IP nào cũng như phản hồi ARP ra sao
Thông thường hệ điều hành xử lý những việc này, và cần quyền root để thêm địa chỉ IP cho interface. Việc mở thiết bị TAP cũng vậy. Networking nhìn chung vận hành theo kiểu hợp tác; một tác nhân độc hại có quyền root trong mạng có thể làm những việc xấu
Theo tôi nhớ thì ARP chỉ hoạt động trong segment cục bộ. Router sẽ điền địa chỉ của chính nó rồi chuyển tiếp packet
Còn có cả rarp nữa, là một trong những cách để hỏi “mạng” địa chỉ IP của chính mình. Không rõ rarp hiện nay còn hoạt động trong môi trường thực tế hay không