作为一个非SDK开发者,对于舞弄C完成硬件寄存器配置,写各种底层SDK的JOB觉得非常的神奇;对于bit,byte级别的操作,觉得C操作起来好吃力,没有verilog来的直接;但对于高层的复杂的数据流,觉得用verilog和SV又好麻烦;
本文介绍一种用C来构造网络数据包的过程;期望给自己打开一个思路,为构造更复杂的bit流打下基础;
1.定义数据类型
//the data stucture defined by user
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long int uint64_t;
不再纠结int,double,float占用几个字节了,自定义这一组数据结构,将其理解为8bit,16bit,32bit和64bit的数据;
C中都是byte对齐的,想定义3bit,7bit,11bit就别想了;把这些想要的数据宽度向上取整为8的倍数,然后选择对应的自定义类型;
2.定义struct
struct就像一个包,将想要的数据类型按顺序放在包中;
注意一个问题,C默认放置方法是申请一个最宽的盒子,然后每个成员都放在这个盒子中,作为硬件工作者,最好不用C默认的规则,使用8bit/1byte作为最小盒子单元,每个成员能用几个就用几个吧,要实现这个功能,就需要如下定义:
//system contorl
#pragma pack(1)
再定义一组控制系统变量的宏:
//packet control
#define TCP 1
#define UDP 0
#define L3_PLD_LEN 16
#define L4_PLD_LEN 16
#define MAX_BUF_DP 380
struct定义如下:
struct l4_tcp {
uint16_t SRC_PORT;
uint16_t DES_PORT;
uint32_t SEQ;
uint32_t ACK;
uint16_t OF_OTHER;
uint16_t WINDOW;
uint16_t CHECKSUM;
uint16_t UPTR;
uint8_t PLD[L4_PLD_LEN];
};
struct l4_udp {
uint16_t SRC_PORT;
uint16_t DES_PORT;
uint16_t UDP_LEN;
uint16_t CHECKSUM;
uint8_t PLD[L4_PLD_LEN];
};
struct l3_ip {
uint8_t VERSION_AND_LEN;
uint8_t TOS;
uint16_t IP_PKT_LEN;
uint16_t ID;
uint16_t FLAGS_FOS;
uint16_t TTL_PROTOCOL;
uint16_t CHECKSUM;
uint32_t SA_IP;
uint32_t DA_IP;
#if TCP
struct l4_tcp TCP_PKT;
#elif UDP
struct l4_udp UDP_PKT;
#else
uint8_t PLD[L3_PLD_LEN];
#endif
};
struct l2_ether2 {
uint16_t DA_H;
uint32_t DA_L;
uint16_t SA_H;
uint32_t SA_L;
uint16_t TL;
struct l3_ip IP_PLD;
uint32_t FCS;
};
如上,
- 定义了4层的TCP和UDP;
- 定义了3层的IP,IP中封装 TCP也可封装UTP,通过宏定义区分;
- 定义了2层的ethernet2包类型,其中包含了IP包;
3.定义各种struct的函数
uint32_t crc32(uint8_t a, uint32_t crc) {
uint32_t poly;
uint32_t crc_new,ct;
uint32_t b;
poly = 0x04c11db7;
crc_new = crc;
b = a;
int i,j;
uint32_t c[32];
for (j = 0; j < 8; j++) {
ct = 0;
for (i = 0; i < 32; i++) {
if (i ==0) {
c[i] = crc_new >> 31;
}
else {
c[i] = (((b >> 7) ^ (crc_new >> 31)) << i) & poly;
}
ct ^= c[i];
}
crc_new = ((crc_new << 1) + (b >> 7)) ^ ct;
b = (b << 1)&0x000000ff;
}
//printf("%.8x,%.2x\n", crc_new,a);
return crc_new;
};
uint32_t swap32(uint32_t a) {
uint32_t b;
b = ((a & 0x000000ff) << 24) | ((a & 0x0000ff00) << 8) | ((a & 0x00ff0000) >> 8) | ((a & 0xff000000) >> 24);
return b;
};
uint16_t swap16(uint16_t a) {
uint16_t b;
b = ((a & 0x00ff) << 8) | ((a & 0xff00) >> 8);
return b;
};
uint8_t bflip8(uint8_t a) {
uint8_t b;
b = (a & (0x01 << 0)) << 7 |
(a & (0x01 << 1)) << 5 |
(a & (0x01 << 2)) << 3 |
(a & (0x01 << 3)) << 1 |
(a & (0x80 >> 0)) >> 7 |
(a & (0x80 >> 1)) >> 5 |
(a & (0x80 >> 2)) >> 3 |
(a & (0x80 >> 3)) >> 1;
return b;
};
uint32_t bflip8_in32(uint32_t a) {
uint32_t b;
uint8_t a_tmp;
uint8_t b_tmp;
int i;
b = 0x0;
for (i = 0; i < 4; i++) {
memcpy(&a_tmp, &a, 1);
b_tmp = bflip8(a_tmp);
b = b | b_tmp << (i * 8);
//printf("%.2x\n", a_tmp);
a >>= 8;
}
return b;
};
struct l4_tcp l4_tcp_sort(struct l4_tcp a) {
struct l4_tcp b;
uint32_t cks_tmp;
uint16_t cks;
int i;
b.SRC_PORT = swap16(a.SRC_PORT);
b.DES_PORT = swap16(a.DES_PORT);
b.SEQ = swap32(a.SEQ);
b.ACK = swap32(a.ACK);
b.OF_OTHER = swap16(a.OF_OTHER);
b.WINDOW = swap16(a.WINDOW);
b.UPTR = swap32(a.UPTR);
for (i = 0; i < L4_PLD_LEN; i++)
b.PLD[i] = a.PLD[i];
cks_tmp = a.SRC_PORT + a.DES_PORT + a.OF_OTHER + a.WINDOW + a.UPTR +
((a.SEQ & 0xffff0000) >> 16) + (a.SEQ & 0x0000ffff) +
((a.ACK & 0xffff0000) >> 16) + (a.ACK & 0x0000ffff);
cks = ((cks_tmp & 0xffff0000) >> 16) + (cks_tmp & 0x0000ffff);
b.CHECKSUM = swap16(cks);
return b;
};
struct l4_udp l4_udp_sort(struct l4_udp a) {
struct l4_udp b;
uint32_t cks_tmp;
uint16_t cks;
int i;
b.SRC_PORT = swap16(a.SRC_PORT);
b.DES_PORT = swap16(a.DES_PORT);
b.UDP_LEN = swap32(a.UDP_LEN);
for (i = 0; i < L4_PLD_LEN; i++)
b.PLD[i] = a.PLD[i];
cks_tmp = a.SRC_PORT + a.DES_PORT + a.UDP_LEN;
cks = ((cks_tmp & 0xffff0000) >> 16) + (cks_tmp & 0x0000ffff);
b.CHECKSUM = swap16(cks);
return b;
};
struct l3_ip l3_sort(struct l3_ip a) {
struct l3_ip b;
uint32_t cks_tmp;
uint16_t cks;
b.VERSION_AND_LEN = a.VERSION_AND_LEN;
b.TOS = a.TOS;
b.ID = swap16(a.ID);
b.FLAGS_FOS = swap16(a.FLAGS_FOS);
b.TTL_PROTOCOL = swap16(a.TTL_PROTOCOL);
b.SA_IP = swap32(a.SA_IP);
b.DA_IP = swap32(a.DA_IP);
#if TCP
b.IP_PKT_LEN = swap16(a.IP_PKT_LEN + sizeof(a.TCP_PKT));
b.TCP_PKT = l4_tcp_sort(a.TCP_PKT);
#elif UDP
b.IP_PKT_LEN = swap16(a.IP_PKT_LEN + sizeof(a.UDP_PKT));
b.UDP_PKT = l4_udp_sort(a.UDP_PKT);
#else
int i;
b.IP_PKT_LEN = swap16(a.IP_PKT_LEN + L4_PLD_LEN + 20); //tcp
for (i = 0; i < L3_PLD_LEN; i++)
b.PLD[i] = a.PLD[i];
#endif
cks_tmp = (a.VERSION_AND_LEN << 8) + a.TOS + a.IP_PKT_LEN + a.ID + a.FLAGS_FOS + a.TTL_PROTOCOL +
((a.SA_IP & 0xffff0000) >> 16) + (a.SA_IP & 0x0000ffff) +
((a.DA_IP & 0xffff0000) >> 16) + (a.DA_IP & 0x0000ffff);
cks = ((cks_tmp & 0xffff0000) >> 16) + (cks_tmp & 0x0000ffff);
b.CHECKSUM = swap16(cks);
return b;
};
struct l2_ether2 l2_sort(struct l2_ether2 a) {
struct l2_ether2 b;
b.DA_H = swap16(a.DA_H);
b.DA_L = swap32(a.DA_L);
b.SA_H = swap16(a.SA_H);
b.SA_L = swap32(a.SA_L);
b.TL = swap16(a.TL);
b.IP_PLD = l3_sort(a.IP_PLD);
b.FCS = swap32(a.FCS);
return b;
};
void l2_pkt_gen(struct l2_ether2 l2_pkt) {
struct l2_ether2 * l2, s_l2_pkt; //Ethernet packet
int i;
int pbyte;
int pkt_len;
l2 = &l2_pkt;
pbyte = sizeof(l2_pkt) - 4; //byte number
pkt_len = (sizeof(l2_pkt) + 4) >> 2; //word number
uint32_t l2_m[MAX_BUF_DP];
uint32_t l2_n[MAX_BUF_DP];
uint8_t l2_f[MAX_BUF_DP];
uint32_t crc ;
//sorted pkt
crc = 0xFFFFFFFF;
s_l2_pkt = l2_sort(l2_pkt);
//caculate the FCS
memcpy(l2_f, &s_l2_pkt, sizeof(s_l2_pkt));
for (i = 0; i < pbyte; i++)
crc = crc32(l2_f[i],crc);
crc = crc^0xffffffff;
crc = ((bflip8(crc >> 24)) << 24) +
((bflip8(crc >> 16)) << 16) +
((bflip8(crc >> 8)) << 8 ) + bflip8(crc);
l2->FCS = crc;
s_l2_pkt = l2_sort(l2_pkt);
//m
memcpy(l2_m, &s_l2_pkt, sizeof(s_l2_pkt));
//n
for (i = 0; i < pkt_len; i++)
l2_n[i] = swap32(l2_m[i]);
//write to the file
FILE* fp = NULL;
fp = fopen("D:\ctest\Project1\l2_packet.txt", "w");
printf("the l2 ethernet packet as following:\n");
for (i = 0; i < pkt_len; i++) {
printf("%.8x\n", l2_n[i]);
fprintf(fp, "%.8x\n", l2_n[i]);
}
printf("\n");
//l4 network sequence
printf("the pkt len is %d\n", sizeof(s_l2_pkt));
fprintf(fp, "ths pkt len is %d\n", sizeof(s_l2_pkt));
fclose(fp);
- crc32: 以太包的CRC计算;这里并不是FCS的计算,FCS计算在l2_pkt_gen函数中实现;
- swap32:32bit数据按字节序反转;
- swap16:16bit数据按字节序反转;
- bflip8 :8bit数据按bit序反转;
- bflip8_in32:32bit数据中每个字节按bit反转;
- l4_tcp_sort:对tcp每个数据高低字节做反转,因为在windows中memcpy函数是按照先copy低字节,再copy高字节的顺序执行的;
- l4_udp_sort,l3_sort,l2_sort:功能和l4_tcp_sort类似;
- l2_pkt_gen:计算FCS,产生以太包,写入文件;
4. main function
int main() {
int i;
struct l2_ether2 l2_pkt, * l2, s_l2_pkt; //Ethernet packet
struct l3_ip l3_pkt, * l3 ; //IP packet
struct l4_tcp l4_tcp, * l4_1; //TCP packet
struct l4_udp l4_udp, * l4_2; //TCP packet
l4_1 = &l4_tcp;
l4_2 = &l4_udp;
l3 = &l3_pkt;
l2 = &l2_pkt;
//build the L4 TCP packet
l4_1->SRC_PORT = 0x4567; //IPV4 and 20byte header
l4_1->DES_PORT = 0x89AB; //COMMON type
l4_1->SEQ = 0x0011; //Total len 24 bytes
l4_1->ACK = 0x0008; //ID
l4_1->OF_OTHER = 0x1010;
l4_1->WINDOW = 0x00ED;
l4_1->CHECKSUM = 0x0000;
l4_1->UPTR = 0x0000;
for (i = 0; i < L4_PLD_LEN; i++)
l4_1->PLD[i] = i;
//l4_1->PLD[i] = rand();
//build the L4 UDP packet
l4_2->SRC_PORT = 0x4567; //IPV4 and 20byte header
l4_2->DES_PORT = 0x89AB; //COMMON type
l4_2->UDP_LEN = L4_PLD_LEN + 8;
l4_2->CHECKSUM = 0x0000;
for (i = 0; i < L4_PLD_LEN; i++)
l4_2->PLD[i] = i;
//l4_2->PLD[i] = rand();
//build the L3 IP packet
l3->VERSION_AND_LEN = 0x45; //IPV4 and 20byte header
l3->TOS = 0x00; //COMMON type
l3->IP_PKT_LEN = 0x14; //Total len 24 bytes
l3->ID = 0xE1C2; //ID
l3->FLAGS_FOS = 0x0000;
l3->TTL_PROTOCOL = 0x0000;
l3->CHECKSUM = 0x0000;
l3->SA_IP = 0xC0A00002;
l3->DA_IP = 0xC0A00003;
#if TCP
l3->TCP_PKT = l4_tcp;
#elif UDP
l3->UDP_PKT = l4_udp;
#else
for (i = 0; i < L3_PLD_LEN; i++)
l3->PLD[i] = i;
#endif
//build the L2 ethernet packet
l2->DA_H = 0x1122;
l2->DA_L = 0x33445566;
l2->SA_H = 0x7788;
l2->SA_L = 0x99AABBCC;
l2->TL = 0x0800;
l2->FCS = 0xCCDDEEFF;
l2->IP_PLD = l3_pkt;
//generate the packet
l2_pkt_gen(l2_pkt);
return 0;
}
指定TCP、UDP,IP,MAC层的参数,调用函数产生数据报文;
5. 小结
C中常用struct,数组,指针来构造数据和引用数据;
对于bit操作,除&^!|等逻辑运算符外,常常需要移位操作来进行bit级的运算;
另外memcpy对bit的copy非常的有效,读者可以慢慢体会;
最后推荐使用VS进行compile和debug,可以watch变量在内存中的值,非常的方便;