[ENC28J60] Bài 9: Giao thức ICMP

icmp

Tiếp tục trong bài học này, chúng ta sẽ tìm hiểu về giao thức ICMP

Xem bài trước : Gói tin ARP

Nếu như các bạn đã biết tới Ping, thì đó chính là ICMP đó.

Giao thức ICMP ( Internet Control Message Protocol ) ICMP được dùng để thông báo các lỗi xảy ra trong quá trình truyền đi của các gói dữ liệu trên mạng. Nó là 1 giao giức của IP ( Internet Protocol ). Do vậy, để làm việc với nó, chúng ta phải tìm hiểu về cấu trúc của các gói IP trước, cụ thể ở đây là IPv4 Protocol

cấu trúc của gói tin IP

IP Protocol có Ether Type là 0x0800 .Các bạn có thể tìm hiểu kĩ hơn các thuộc tính của từng trường, còn trong bài này, chúng ta chỉ cần quan tâm tới:

  • Trường Protocol để xác định xem gói data này có phải là ICMP không !
  • Source IP và Dest IP (đương nhiên phải quan tâm rồi)
  • Data

1. Trường Protocol

Protocol = 0x01 => Đây là gói ICMP

Sử dụng phần mềm WireShark phân tích rất tiện lợi, vị trí byte protocol trong gói tin là 23

2. Trường Source IP và Dest IP

Tương tự, với source ip vị trí bắt đầu là 26
Với dest ip vị trí bắt đầu là 30

3. Trường data

Trường data chính là gói ICMP

Bây giờ chúng ta phân tích kĩ hơn về cấu trúc của gói ICMP

Chúng ta quan tâm tới các trường sau:

  • Type
  • Data

Nếu Type = 0x08 thì đây là 1 gói ping hỏi, còn type = 0x00 thì đây là ping trả lời, vì số lượng kiểu của ICMP quá nhiều nên trong bài này, mình chỉ hướng dẫn các bạn xử lí type = 0x08 (ping hỏi) còn mấy cái type khác chưa hỗ trợ nhé, chứ ngồi viết hết thì thốn lắm

Trường Data chính là nội dung của tin nhắn

Để trả lời 1 gói ping thì chúng ta trả lời lại đúng cái nội dung tin nhắn đã nhắn được là ok

Lí thuyết có vậy thôi, hết rồi đó, bắt tay vào code luôn

Do ICMP thuộc bộ giao thức IP, nên ta phải kiểm tra gói tin gửi tới có phải là gói IP không đã

Trong thư viện net.c ngay dưới phần kiểm tra gói tin ARP, nếu gói tin đó không phải gói ARP, chúng ta tiếp tục check xem có phải là gói IP không

Sau khi đã biết đây là gói tin IP, chúng ta tiếp tục kiểm tra xem nó có phải là gói ICMP không (hay là TCP UDP ….)

Mình sẽ tạo 1 hàm trên là NET_ipRead để kiểm tra

Làm tới đâu test tới đó, ngay bây giờ mình sẽ ping thử tới enc28j60 nhé ! Các bạn chạy mô phỏng và mở cmdping 192.168.1.197

Trong đó 192.168.1.197 là ip tĩnh của enc28j60 mà ta đã cài ở file enc28j60.c

Toẹt vời ! Chúng ta đã bắt được 1 gói ping

Bây giờ viết mã để phản hồi cho gói tin ping này nào !

Tiếp tục tạo file thư viện icmp.hicmp.c và add vào project

Copy nội dung khởi tạo cho file icmp.h

Copy nội dung khởi tạo cho file icmp.c

Đồng thời add thư viện icmp.h vào file net.h

Sử dụng Struct để xử lí các gói tin 1 cách tiện lợi

Hiện tại mình đang sử dụng truy xuất array để xử lí các gói tin, nó ổn, nhưng không tiện lợi bằng struct. Đương nhiên các bạn phải khá thành thạo con trỏ để có thể kết hợp nó với struct 1 cách nhuần nhuyễn. Mình sẽ code sử dụng lẫn lộn giữa array và struct dần rồi sẽ chuyển dần hoàn toàn sang xài struct cho tiện, vì sau này xử lí các gói tin TCP nó phức tạp lắp không dễ như mấy cái ARP với ICMP này đâu 🙁

Mình sẽ khai báo 1 struct vào file icmp.h có cấu trúc tương tự như của 1 ICMP frame

Struct phía trên là cấu trúc của 1 gói ping ( gồm cả request và và reponse)

Sau khi nhận được gói tin, mình sẽ phang thẳng gói tin ấy vào struct bằng cách ép kiểu

Ở bên trên mình có kiểm tra xem có phải gói ping không ở trong file net.c ( hàm NET_ipRead ) mục đích là để test nhanh thôi chứ công việc này sẽ xử lí ở file icmp.c. Do vậy các bạn xóa dòng kiểm tra này đi và gọi hàm ICMP_read để cho hàm này xử lí thì hơn

icmp

Chúng ta sẽ viết hàm ICMP_read trong file icmp.c nhé

Mình sẽ test xem code đến chỗ này đã chạy ok chưa !

Đã bắt được gói ping, các bạn có thấy xài Struct như này chương trình nhìn thân thiện và chuyên nghiệp hơn hẳn không 🙂

Nếu không hiểu dòng ICMP_struct *ICMP_Ping_Packet = (ICMP_struct *)ICMP_Frame; thì đã đến lúc bạn dừng bài viết và đi tìm hiểu về con trỏ và struct rồi đó 🙂

Gửi gói ping trả lời

Giờ chúng ta sẽ viết mã để gửi lại phản hồi

Mình sẽ tạo 1 hàm khác để gửi ping phản hồi

Nhiệm vụ trong hàm phản hồi:

  1. Đảo lộn Source Mac và Dest Mac lại
  2. Sửa Type 0x08 (hỏi) thành 0x00 (trả lời)
  3. Sửa lại checksum cho gói icmp
  4. Gửi đi

Viết hàm tính checksum

Checksum là gì ?

Checksum về cơ bản là một giá trị được tính từ gói dữ liệu để kiểm tra tính toàn vẹn của nó. Thông qua checksum , chúng ta có thể kiểm tra xem dữ liệu nhận được có lỗi hay không. Điều này là do trong khi di chuyển trên mạng, một gói dữ liệu có thể bị hỏng và phải có một cách để biết rằng dữ liệu có bị hỏng hay không. Đây là lý do checksum được thêm vào hearder của mỗi gói tin. Ở phía nguồn gửi, checksum được tính toán và đặt trong tiêu đề dưới dạng một trường. Ở phía đích, checksum lại được tính toán và kiểm tra chéo với giá trị tổng kiểm tra hiện có trong tiêu đề để xem liệu gói dữ liệu có ổn hay không.

Cách tính checksum

Công tất cả các byte 16bit lại với nhau (nếu lẻ thì đắp thêm 0x00 cho chẵn),nếu kết quả > 16bit thì lại tiếp tục cộng 16bit lại cho đến khi được 1 byte 16bit hoàn chỉnh, sau đó lấy nghịch đảo

Ví dụ,1 gói ICMP có nội dung như sau:

0x00,0x00,0x49,0x66,0x00,0x01,0x0B,0xF5,0x61,0x62,0x63,0x64,0x65,0x66,
0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,0x70,0x71,0x72,0x73,0x74,0x75,0x76,
0x77,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69

Thì phần mình to đỏ chính là 2 byte check sum. Chúng ta gép các byte8 thành byte16 rồi cộng hết lại với nhau (vì đang tính checksum nên coi checksum=0x0000)

0x0000 + 0x0001 + 0x0BF5 + 0x6162 + 0x6364 + 0x6566 + 0x6768 + 0x696A + 0x6B6C + 0x6D6E + 0x6F70 + 0x7172 + 0x7374 + 0x7576 + 0x7761 + 0x6263 + 0x6465 + 0x6667 + 0x6869 = 0x6B693

Tiếp tục cộng kết quả bằng các bye16

Tức 0x0006 + 0xB693 = 0xB699

Lấy nghịch đảo => checksum = 0x4966

Trong file icmp.c mình sẽ viết hàm tính checksum

Giải thích: Mình giảm len đi 34 vì chúng ta không tính checksum cho các byte thuộc gói ethernet và gói ip ( nó chiếm 34 bye). Con trỏ ptr bắt đầu từ vị trí của trường Type của gói ICMP

Vòng lặp while sẽ gép 2 byte cạnh nhau thành byte lớn 16bit rồi cộng dần lại, sau đó kiểm tra nếu lẻ 1 byte thì nhét thêm 0x00 vào đít bằng lệnh dịch lên cao và cộng lại

Sau khi cộng tổng xong thì kiểm tra tổng này có lớn hơn 16bit không để cộng lại tiếp đến khi con 16bit thì thôi.

Sau đó nghịch đảo byte checksum lại, giá trị checksum trong struct cũng bị lộn thứ tự ( byte thấp đứng trước byte cao) nên mình hoán vị byte lại

Viết hàm ICMP reply

Quá rõ ràng rồi, không cần giải thích nữa nhỉ 🙂

Đừng quên gọi hàm reply trong hàm ICMP_read nhé !

Giờ chạy mô phỏng và ping thử tới enc28j60 của chúng ta thôi

ping enc28j60

Máy tính đã nhận được ping phản hồi không trượt phát nào 🙂

WireShark cũng bắt được cái gói ping request và ping reply

Dowload

Toàn bộ soure cho bài giao thức icmp các bạn có thể tải tại đây

Trong bài tiếp theo, chúng ta sẽ tìm hiểu về giao thức UDP, cũng là 1 giao thức nằm trong IP và làm 1 project đó là điều khiển led từ xa qua mạng lan bằng UDP

 

Từ tác giả:

Nếu có bất kì thắc mắc nào trong bài viết, vui lòng để lại comment dưới mỗi bài ! Mình sẽ không trả lời thắc mắc của các bạn ở facebook hay email !

Nếu trong phần code bạn nhìn thấy nhưng thứ kiểu như &amp; thì đó là lỗi hiển thị, cụ thể 3 kí tự < > & bị biến đổi thành như thế
&amp; là &
&lt;  là <
&gt; là >

Giới thiệu Đào Nguyện 80 bài viết
DIY,chế cháo, viết blog chia sẽ kiến thức về lập trình,điện tử - IoT. Rất mong được giao lưu, kết bạn với các bạn cùng đam mê. Địa chỉ Facebook: https://www.facebook.com/nguyendao207

3 bình luận

  1. Cảm ơn bạn đã chia sẻ bài viết rất hay. Tôi đã thử đến bước ping đến ENC28J60 với tham số -t nhưng chỉ được một thời gian rất ngắn thì bị timeout. Bạn có thể giải thích được không? Cảm ơn nhiều!!!

  2. checksum += (uint16_t) (((uint32_t)*ptr<<8)|*(ptr+1)); taị sao ad lại ép kiểu về uint32_t, trong khi *ptr<<8 thì cùng lắm chỉ là 16 bit

Đã đóng bình luận.