Như mình đã nói lần trước, để một gói tin được gửi tới chính xác thiết bị trong mạng LAN, nó cần có địa chỉ MAC của thiết bị đó. Giao thức ARP được sử dụng để từ 1 địa chỉ IP có thể tìm ra địa chỉ MAC của thiết bị đích hay nói cách khác là phân giải địa chỉ IP sang địa chỉ máy
Mình sẻ làm thử 1 demo gửi 1 gói tin ARP nhé !
Chúng ta mở project đã làm ở bài 5
Trong file code main, mình khai báo 1 gói tin arp như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
uint8_t arp[62]= { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, //board cat 0x00,0x00,0x20,0x07,0x19,0x98, //source MAC 0x08,0x06, //Ethernet type - ARP 0x00,0x01, //HTPYE 0x08,0x00, 0x06,0x04, 0x00,0x01, 0x00,0x00,0x20,0x07,0x19,0x98, //source MAC 192,168,1,197, 0x00,0x00,0x00,0x00,0x00,0x00, 192,168,1,1, 0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; |
Chú ý: Kiểm tra ip của bạn thuộc dạng 192.168.1.xxx hay 192.168.0.xxx để đổi 2 cái bên trên ip tương ứng nhé
Trong hàm main, vòng lặp while(1) mình sẽ gửi gói tin đi liên tục bằng hàm ENC28J60_send_packet
1 2 |
ENC28J60_send_packet(arp,62); delay_ms(200); |
Chạy thử chương trình và mình thấy led vàng nhấp nháy liên tục, có nghĩa là đã có dữ liệu được gửi đi. Để có thể dubug được gói dữ liệu này mình sẽ sử dụng 1 phần mềm chuyên bắt gói tin trên máy tính
Giới thiệu phần mềm Wireshark
Wireshark là phần mềm chuyên bắt mọi gói dữ liệu ra vào máy tính thông qua Ethernet, wifi, usb. Do chúng ta sử dụng mô phỏng để chạy chíp ENC28J60 nên tất cả gói dữ liệu ra vào con chip này cũng sẽ được phần mềm Wireshark bắt lại. Đây là công cụ mà hacker rất hay dùng
Chúng ta sẽ sử dụng phần mềm này để debug các gói tin trong suốt toàn bộ khóa học
Các bạn tải và cài đặt Wireshark nhé !
Sau khi cài phần mềm xong, mở lên, giao diện nó như vầy
Nếu bạn dùng wifi thì click chọn vào wifi, nếu dùng mạng dây thì chọn mạng dây tương ứng nhé !
Sau khi chọn wifi hoặc mạng dây thì màn hình real time sẽ hiện ra, ở đây sẽ liên tục hiện các gói tin vào ra máy tính theo thời gian thực
Do có rất nhiều gói tin gửi nên mình sẽ dùng chức năng lọc để lọc lấy các gói tin arp
Các bạn thấy đấy, phần mềm đã bắt được gói tin mà mình đã gửi đi, như vậy hàm ENC28J60_send_packet đã gửi thành công gói tin vào môi trường mạng LAN rồi !
Lan màn vậy đủ rồi, chúng ta sẽ vào chủ đề chính ngày hôm nay
Cấu trúc chung của 1 gói tin
Do chung ta đang làm việc với chip enc29j60 sử dụng giao thức mạng ethernet, tất các các gói tin truyền nhận đều phải tuân thủ quy tắc của Ethernet Frame Type II
Chúng ta cần quan tâm 14 byte đầu tiên :
Với 6 byte đầu tiên luôn luôn là địa chỉ MAC của thằng nhận
6 byte tiếp theo là địa chỉ MAC của thằng gửi
2 byte tiếp theo sẽ nói lên giao thức phía sau của nó thuộc loại gì ? IP hay ARP …. ????
Giao thức ARP (Ether Type =0x0806)
Như đã nói bên trên, giao thức ARP dùng để lấy địa chỉ MAC của 1 thiết bị từ địa chỉ IP
Ví dụ ENC28J60 có ip 192.168.197 muốn gửi một tin nhắn cho máy tính có ip 192.168.1.4, trước tiên nó sẽ gửi 1 gói tin ARP request ( boardcast) tới toàn bộ thiết bị trong mạng, gói tin ARP request đó có nội dung như sau:
Tao là 192.168.1.197 đây, MAC của tao là xxx , thằng nào là 192.168.1.4 xưng danh
Toàn bộ thiết bị trong mạng đó sẽ nhận được tin nhắn này, những thằng nào không phải 192.168.1.4 sẽ ngó lơ bản tin này, chỉ riêng máy tính có địa chỉ 192.168.1.4 sẽ gửi 1 gói tin trả lời trực tiếp cho ENC28J60 có ip 192.168.1.197 do nó đã biết MAC của 192.168.1.197 (ARP reponse)
Tao là 192.168.1.4 đây, địa chỉ MAC của tao là AA:BB:CC:DD:EE:FF
ENC28J60 nhận được tin nhắn phải hồi và lưu địa chỉ MAC lại, bây giờ nó có thể gửi tin nhắn tới cho máy tính qua địa chỉ MAC
Bản tin ARP Request
Chúng ta sẽ ngâm cứu kĩ hơn cấu trúc của 1 ARP request
Giải thích cấu trúc :
- 6 byte đầu mặc định: 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
- 6 byte tiếp theo là địa chỉ MAC của nguồn – tức là của chính thằng gửi request
- 2 byte 0x08 0x06 thể hiện đây là bản tin ARP (Ethernet Type)
- 2 byte 0x00 0x01 thể hiện htype
- 2 byte 0x80 0x00 thể hiện Protocol type, ở đây là IPv4
- Tiếp đến là 0x06 và 0x04 là trường length
- 2 byte 0x00 và 0x01 là opcode báo bản tin ARP này là request
- Sender MAC và SenderIP của thiết bị request
- Trường Taget MAC là của đích ( tức thiết bị nhận request) mặc định là 6 byte 0x00 do ta chưa biết MAC của nó
- 4 byte cuối là IP của đích
Nói chung, chúng ta chỉ cần quan tâm tới SourceMAC SenderMAC SenderIP và TargetIP
Bản tin ARP Reponse
Bản tin ARP reponse có cấu trúc tương tự như các Request, chỉ khác nhau là Opcode sẽ là 0x0002 để thể hiện đây là reponse và Destination MAC và Target MAC sẽ không phải là 6 byte mặc định nữa mà là MAC của thiết bị đã gửi request
Dưới đây là ảnh chụp của 1 ARP reponse được bắt bởi WireShark
Các bạn có thể thấy, sau khi mình cho ENC28J60 gửi 1 boardcast (arp request) tới cho IP của máy tính thì máy tính đã trả lời lại MAC của nó ( 192.168.1.15 is at 00:26:5E:3D:74:66)
Thời gian ARP
Khi 1 thiết bị đã lấy được MAC của thiết bị khác từ IP, nó sẽ lưu trữ lại vào bảng để lần gửi sau chỉ việc lấy ra dùng chứ không cần phải hỏi lại nữa. Tuy nhiên trong mạng LAN, IP của các thiết bị là IP động, nó thường hay thay đổi mỗi khi thiết bị khởi động lại. Vì vậy các gói tin ARP vẫn thường xuyên được gửi để cập nhật lại MAC cho chính xác. Tránh việc sử dụng các địa chỉ đã lỗi thời
Thông thường, các bảng lưu ARP được cập nhật từ 30 đến 60s 1 lần
Tạo thư viện ARP
Trước khi viết thư viện ARP, mình sẽ tạo ra thư viện trung gian có tên là NET để làm trung gian giao tiếp giữa thư viện ENC28J60 với thư viện ARP và các thư viện khác nữa !
Chúng ta tạo 2 tệp là net.c và net.h và add vào project tương tự như hướng dẫn ở các bài trước
Các bạn copy nội dung khởi tạo cho file net.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#ifndef NET_H_ #define NET_H_ //-------------------------------------------------- //Include cac thu vien can thiet #include <mega328p.h> #include <delay.h> #include <string.h> #include <stdlib.h> #include <stdint.h> #include <stdio.h> #include <spi.h> #include "uart.h" #include "enc28j60.h" //------------------------------------------------- //-------------------------------------------------- #endif /* ENC28J60_H_ */ |
Tương tự, copy nội dung khởi tạo cho file net.c
1 2 3 4 5 6 7 |
#include "net.h" //-------------------------------------------------------------------------- //End file |
Chú ý: Các này đáng lẽ mình phải nhắc từ đâu, tất cả các file .h .c các bạn phải có 1 dòng trống ở cuối file nhé, không thì trình dịch sẽ báo lỗi
Nhiệm vụ của thư viện NET
- Phân tích gói tin tới xem nó thuộc loại gì, tùy vào loại của gói tin để gọi thư viện xử lí tương ứng. Ví dụ thư viện NET sẽ đọc truòng Ethernet Type của gói tin. Nếu nó là 0x0806 thì đây là gói ARP => gọi thư viện ARP và ném gói này cho nó xử lí. Nếu là gói tin chưa được hỗ trợ thì bỏ qua hoặc thông báo lỗi gì đó tùy ứng dụng …
- Tính toán checksum
- Gửi nhận gói tin
- Kiểm tra liên tục xem có gói tin mới nào không
Để có thể xử lí, phân tích các gói tin, mình sẻ khởi tạo trên ram của chip vi điều khiển 1 bộ đệm dài 512 byte
1 |
uint8_t eth_buffer[BUFFER_LENGTH]; |
Thêm định #define cho BUFFER_LENGTH
1 |
#define BUFFER_LENGTH 512 |
Hàm NET_SendFrame sẽ gọi lại hàm ENC28J60_send_packet
1 2 3 4 5 6 |
void NET_SendFrame(uint8_t *EthFrame,uint16_t len) { sprintf(debug_string,"Gui goi tin ethernet co do dai: %i\r\n",len); UART_putString(debug_string); ENC28J60_send_packet(EthFrame,len); } |
Hàm NET_loop có nhiệm vụ check có gói tin nào không để gửi gói tin cho hàm NET_read xử lí gói tin
1 2 3 4 5 6 7 8 9 10 |
void NET_loop(void) { uint16_t len; while(1) { len=ENC28J60_read_packet(eth_buffer,BUFFER_LENGTH); if(len==0)return; else NET_read(eth_buffer,len); } } |
Các bạn nhớ thêm nguyên mẫu hàm vào file net.h
Hàm NET_loop sẽ được gọi trọng vòng lặp while(1) của hàm main để liên tục thăm dò gói tin
Bây giờ chúng ta sẽ viết nội dung cho hàm NET_read cho nó mở gói tin và xử lí xem bên trong gói tin có gì
1 2 3 4 |
void NET_read(uint8_t *net_buffer,uint16_t len) { } |
Khi 1 gói tin được gửi tới, chúng ta sẽ ngay lập tức biết nó có phải là gói tin ARP không bằng cách check trường Ethernet Type
- Nếu Ethernet Type = 0x0806 -> Đây là gói tin ARP
- Ngược lại nếu Ethernet Type = 0x0800 -> Đây là gói tin IP
- Ngược lại đây là 1 gói tin lạ, chúng ta sẽ bỏ qua
Chúng ta sẽ tạm thời chưa quan tâm gói tin IP vội, mình sẽ xử lí nó sau, trước mắt sẽ viết code để xử lí gói tin ARP
Cước bước kiểm tra
- Một bản tin ARP phải có ít nhất 42 byte, nếu không đủ 42 byte -> loại
- Check Ethernet Type tại byte thứ 12 và 13 xem có phải 0x0806 không, nếu không phải -> loại
- Tới đây, đã có thể chắc chắn đây là 1 gói tin ARP rồi, chúng ta sẽ lại gửi gói tin cho thư viện ARP xử lí
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void NET_read(uint8_t *net_buffer,uint16_t len) { //in ra do dai cua goi tin sprintf(debug_string,"Da doc goi tin ra, do dai goi tin=%i\r\n",len); UART_putString(debug_string); //kiem tra xem co phai goi tin ARP khong if(len>41) { if(net_buffer[12] == 0x08 && net_buffer[13] == 0x06) { //day dung la goi tin ARP UART_putString("Day la goi tin ARP\r\n"); //gui goi tin cho thu vien ARP.h xu li //ARP_read_packet(uint8_t *net_buffer,uint16_t len); } } } |
Đương nhiên hàm ARP_read_packet mình chưua viết nên sẽ tạm comment nó chúng ta sẽ viết nó ngay, nhưng sẽ viết trong thư viện arp.c chứ không viết ở file net.c
Test thử
Code nhiều rồi mà không test thì cũng chán nhỉ ! Chúng ta sẻ test thử bằng cách cho điện thoại gửi thử các bản tin ARP để xem chương trình của chúng ta có nhận được các gói ARP này không nhé !
Để gửi được các boardcast (ARP request) đi, mình sẽ sử dụng 1 phần mềm Scan IP bất kì. Ví dụ ở điện thoại android mình sẽ xài Network IP Scan
Các bạn có thể dùng phần mềm sacn bất kì
Bản chất của phần mềm scan IP là nó sẽ gửi các boardcast ( tức ARP request) đến toàn mạng. Thiết bị nào có IP tương ứng sẽ trả lời lại cho nó
Bây giờ, mình chạy mô phỏng proteus và mở phần mềm scan lên cho nó scan và xem kết quả ( mình cũng mở đồng thời WireShark lên nữa)
Các bạn thấy đó, chúng ta đã bắt được rất nhiều gói tin ARP request của phần mềm scan ( toàn bộ thiết bị trong mạng wifi đều nhận được nha)
Đồng thời thì wireshark nó cũng bắt được rất nhiều gói tin ARP
Như vậy chúng ta đã bắt được các gói tin ARP. Trong phần tiếp theo của bài ARP, chúng ta sẽ tiếp tục hoàn thành thư viện xử lí gói tin ARP và viết hàm trả lời các bản tin ARP request nhé !
Các bạn có thể tải code cho bài học hôm nay tại đây !
chào bạn. Trước hết cảm ơn bạn đã viết tut chia sẽ cũng như hướng dẫn về 28j60.
Mình làm tới tút này thì gặp vấn đề như sau. debug bằng Wireshark thì ko có capture được gói tin từ enc28j60 trong proteus. Bạn giúp mình chỗ này với. . Cảm ơn bạn nhiều
mạng của bạn thuộc dạng 192.168.1.xxx hay 192.168.0.xxx Kiểm tra lại chỗ này nhé
của mình là 192.168.0.6
vậy thì bạn hãy chỉnh lại cái ip trong mảng arp kia cho đúng phân lớp mạng của bạn ! Mình quên mất chỗ này, mình sẽ bổ sung vào bài viết
Chào bạn.
Mình đã thử đổi ip trong mang uint8_t arp[62]= -> 192.168.1.1 và 192.168.1.197 -> 192.168.0.1 và 192.168.0.197
Đồng thời mình cũng đổi ip của 28j60 trong Proteus thành 192.168.0.197
Nhưng vẫn ko bắt được gói tin ARP trên wireshark. Mình dùng wifi, set ip động.
khi gửi led vàng có nháy không
Nháy bình thường bạn à.
Mình thử mở Example code 28j60 với Pic của proteus thì vẫn kết nối được tới server của Microchip.
Nên mình nghĩ ở vấn đề ở đây có thể wireshark cần setting chỗ nào nữa không.
Mình tìm được vấn đề rồi. Con 28j60 trong proteus nó mượn phần cứng máy để hoạt động. Mình nghĩ thế. Sau khi mình set lai ip của 28j60 trong proteus trùng với ip của wifi đang dùng thì đã thấy được các gói tin thông qua Wireshark.
Cảm ơn bạn này nha. Chia sẽ để bạn nào lỡ gặp bước này thì có hướng giải quyết.
ồ ! Mình cũng k biết là phải set ip trong mô phỏng trùng với ip máy ! Nhiều bạn k cần làm thế vẫn ổn mà !
tốt nhất là các bạn nên kết hợp cả mô phỏng lẫn thực tế cho an toàn. vì mô phỏng cũng có giới hạn thôi. Nên từ bài 15 mình bỏ luôn mô phỏng
Mình có một thắc mắc, đó là tại sao MAC addr khi set cho module khác với Source MAC trong bản tin ARP_request mà khi máy tính gửi bản tin ARP_reply vẫn có thể nhận được gói tin đó nhỉ?