计算机网络:数据链路层——以太网协议

数据链路层——以太网协议

第1关:以太网帧的解析

任务描述
补充代码,解析收到的以太网帧,根据出帧类型,调用对应的处理函数。
本任务中的给出的以太网帧字节流不包含前面的7字节同步码、1字节帧开始定界符和后面的4字节校验码。
任务1:完成函数
unsigned char* get_eth_dst_addr(unsigned char eth);
输入为帧的第一个字节的指针,要求返回目的MAC的首地址。
任务2:完成函数
unsigned char get_eth_src_addr(unsigned char eth);
输入为帧的第一个字节的指针,要求返回源MAC的首地址。
任务3:完成函数
void eth_dispatch(unsigned char eth);
输入为帧的第一个字节的指针,要求返回源MAC的首地址。要求解析帧中的类型字段,根据类型编码确定该帧封装是IP,ARP或其他协议包。注意网络字节顺序可能与本地字节训练不同。已经IP协议对应的帧类型代码是0x0800,ARP协议对应的帧类型是0x0806。
以太网是最简单的网络协议,掌握以太网帧解析的方法对后续解析其他协议包大有帮助。
为了完成本关任务,你需要掌握:以太网协议语法和语义。
在这里插入图片描述
  帧字节流的前6个字节是目的结点的物理地址(目的MAC地址),接着的6字节是发送端的物理地址(源MAC地址)。接下来的类型字段是对应帧封装的上层协议类型,如IP协议对应的帧类型代码是0x0800,ARP协议对应的帧类型是0x0806。
  数据部分是上层交付的分组,对于数据链路层来说,上层的协议字段以及上层封装的数据一起看作是自己的数据部分。
由于帧要求上层数据包长度最小46字节,如果长度不够,以太网驱动在发送时会自动补零,以便整个以太网帧达到64字节。
在用wireshark抓取以太网帧时,由于它是在链路层旁路,因此不会抓取到填充数据,也不会抓取到校验码。这就是为什么有时抓取发送帧最小只有42字节,接收到的帧最小只有60字节的原因。
  以太网帧的最大长度是1500+18字节,其中1500是以太网能封装的最大协议数据长度,18是以太网的协议字段。实际wireshark能抓取的帧由于缺少校验码,最长为1500+14字节。
代码的部分函数供测试代码调用,主要功能是从测试的数据集中恢复帧字节序列。
测试的数据以字符串形式存储,因此需要将字节串转换为原始的字节串。
存于文档的帧,形式如下:
0000 00 f1 f3 05 39 4b 30 99 35 5a f2 93 08 06 00 01
0010 08 00 06 04 00 01 30 99 35 5a f2 93 c0 a8 01 01
0020 00 00 00 00 00 00 c0 a8 01 02 00 00 00 00 00 00
0030 00 00 00 00 00 00 00 00 00 00 00 00
由于一个字节需要两个ascii码字符表示,因此我们先将字符转换为对应的值,如字符’1’转为数值1;字符’e’转换为数值14(记为十六进制就0xe)再将两个值分别拼接到无符号字符的高4位和低4位,即"1e"字符串就转换成一个无符号的值0x1e.
  首先从标准输入流接收测试数据的文件名,再从以字符串形式存储的帧中恢复字节序列,再调用任务中实现的获取目的MAC,源MAC函数返回的地址指针并打印相应的地址。
  接着任务3实现的分发函数,根据帧类型分别调用对应的函数。协议包的处理函数只是简单的打印一句提示语句。

AC代码

//analysis_eth.c
#include"datalink_eth.h"
/*输入为帧字节串,返回帧目的MAC的起始位置*/
byte*  get_eth_dst_addr(byte *eth) {
	byte*  p;
    /******* Begin ************/
    p = eth;
    /******* End *************/
    return p;
}
/*输入为帧字节串,返回帧源MAC的起始位置*/
byte*  get_eth_src_addr(byte *eth) {
	byte*  p;
    /******* Begin ************/
    p = eth + 6;
    /******* End *************/
    return p;
}
/*输入为帧字节串,根据帧类型判断,将帧的数据部分交给相应处理函数*/
void eth_dispatch(byte *eth) {
    uint16 eth_type;
    byte *data;
    /************ Begin ************/
    byte *p;
    p = str2hex(eth + 12);
    eth_type = htons(*p) + *(p+1);
    /************ End *************/
    switch (eth_type) {
        case 0x0800:
            ip(data);
            break;
        case 0x0806:
            arp(data);
            break;
        default:
            other(data);
            break;
    }
}

datalink_eth.h

#ifndef  _DATALINK_ETH_H_
#define  _DATALINK_ETH_H_
#include<stdio.h>
#include<string.h>
#pragma  pack(1) 
typedef unsigned char byte;
typedef unsigned short uint16;
typedef unsigned int uint32;

/* 网络字节顺序与本地顺序的转换,对于本地是大端模式的则不需要转换 */
#define	 htons(x)  ((0xff & ((x)>>8)) | ((0xff & (x)) << 8))
#define	 htonl(x)  ((((x)>>24) & 0x000000ff) | (((x)>> 8) & 0x0000ff00) | \
		   (((x)<<8) & 0x00ff0000) | (((x)<<24) & 0xff000000))
#define	 ntohs(x)  ((0xff & ((x)>>8)) | ( (0xff & (x)) << 8))
#define	 ntohl(x)  ((((x)>>24) & 0x000000ff) | (((x)>> 8) & 0x0000ff00) | \
		   (((x)<<8) & 0x00ff0000) | (((x)<<24) & 0xff000000))
/* 将两个字节表示的十进制拼接为单字节的值,如0x01 0e ->0x1e
本宏由测试程序调用中,用于准备帧字节流*/
#define a2hex(p)   (p[0] << 4)| (p[1] & 0x0f)

#define ETH_ADDR_LEN 6
#define ETH_HDR_LEN 14
#define MAX_ETH_LENTH  (1500+18)


struct etherPkt {
    byte dst[ETH_ADDR_LEN];
    byte src[ETH_ADDR_LEN];
    uint16 type;
    byte data[1];
};

/*在tools.c中定义的全局变量*/
extern byte eth[MAX_ETH_LENTH];

/*将以十六进文本形式存储的包文件,转为字节流形式,并存于全局变量eth当中。
此函数在测试函数中调用,仅供大家参考*/
extern int generate_raw_pkt(char* pke_file);
/*以十六进制形式打印包的字节*/
extern void print_pkt(byte *pkt, int len);
/*打印帧的地址*/
extern void print_eth_addr(byte *p) ;
/*模拟的arp协议处理函数,测试函数调用*/
extern void arp(byte *p) ;
/*模拟的ip协议处理函数,测试函数调用*/
extern void ip(byte *p) ;
/*模拟的其他协议处理函数,测试函数调用*/
extern void other(byte *p) ;
/*输入为帧字节串,返回帧目的MAC的起始位置*/
extern byte*  get_eth_dst_addr(byte *eth);
/*输入为帧字节串,返回帧源MAC的起始位置*/
extern byte*  get_eth_src_addr(byte *eth);
/*输入为帧字节串,根据帧类型判断,将帧的数据部分交给相应处理函数*/
extern void eth_dispatch(byte *eth);
#endif

tools.c

//etherframe.cpp
#include"datalink_eth.h"

byte eth[MAX_ETH_LENTH] = { 0 };

/*将字符串表示的十六进制,转为对应的值,如二个字节符"1e"转为对应的两个值0x01 0e
本函数由测试程序调用中,用于准备帧字节流*/
char * str2hex(char *str) {
    int i = 0;
    while (str[i] != '\0') {
        if (str[i] >= '0' && str[i] <= '9')
            str[i] = str[i] - '0';
        if (str[i] >= 'a' && str[i] <= 'f')
            str[i] = str[i] - 'a' + 10;
        i++;
    }
    return str;
}
/*以十六进制形式打印包的字节*/
void print_pkt(byte pkt[], int len) {
    int row = 0;
    printf("\n%04d ", row);
    for (int i = 0; i < len; i++) {
        printf("%02x ", pkt[i]);
        if ((i + 1) % 8 == 0)
            printf("  ");
        if ((i + 1) % 16 == 0) {
            row++;
            printf("\n%04d ", row);
        }
    }
    printf("\n");
}
/*将以十六进文本形式存储的包文件,转为字节流形式,并存于全局变量eth当中。
此函数在测试函数中调用,仅供大家参考*/
int generate_raw_pkt( char *pke_file) {
    FILE *f   = fopen(pke_file, "r");
    if (f==NULL)
        return -1;
    char str[2000];
    int len = 0;
    while (fgets(str, 2000, f) != NULL) {//返回的串末尾有换行符'\n'
        char *p = strtok(str, " ");
        p = strtok(NULL, " ");
        while (p) {
           char *next= strtok(NULL, " ");
           char *t = str2hex(p);
           eth[len] = a2hex(t);
           len++;
           p = next;
        }
    }
    fclose(f);
    return len;
}

/*打印帧的地址*/
void print_eth_addr(byte *p) {
    int i = 0;
    for (i = 0; i < ETH_ADDR_LEN -1; i++) {
        printf("%02x-", p[i]);
    }
    printf("%02x", p[i]);
}
/*模拟的arp协议处理函数,测试函数调用*/
void arp(byte *p) {
    printf("这是arp分组\n");
}
/*模拟的ip协议处理函数,测试函数调用*/
void ip(byte *p) {
    printf("这是ip包\n");
}
/*模拟的其他协议处理函数,测试函数调用*/
void other(byte *p) {
    printf("这是其他的协议类型\n");
}

test_main.c

#include"datalink_eth.h"

int  main(int argc, char **argv) {
    char file[256];
    fgets(file, 256, stdin);
    int len = generate_raw_pkt(file);
    //print_pkt(eth, len);
    printf("dst MAC:");
    print_eth_addr(get_eth_dst_addr(eth));
    printf("\n");
    printf("src MAC:");
    print_eth_addr(get_eth_src_addr(eth));
    printf("\n");
    eth_dispatch(eth);
    return 0;
}

第2关:以太网帧的创建

任务描述
根据给定的参数创建以太网帧。不需要计算帧的校验码。
实现函数:

int generate_eth_frame(unsigned char *input_eth,   
                        unsigned char *output_eth,   
                        unsigned short type,   
                        char *data, int d  
                        ata_len);  

参数
input_eth,输入的帧,从中获取该帧的源/目的地址
output_eth,创建的帧,其源地址为input_eth的目的地址;其目的地址为input_eth的源地址。注意输入和输出帧的地址位置互换。
type,为创建帧指定的帧类型,注意字节顺序。
data,为创建帧封装的数据
data_len,为输入字符串的长度,注意如果封装的数据长度太短,还需要填充,以满足以太网冲突检测的需要。
返回:
创建的帧长度,即以太网协议的首部及数据,不包括帧校验码。

AC代码

//generate_eth_frame.c
#include"datalink_eth.h"
/*根据输入参数创建帧,不需要计算帧校验码,创建的帧长也不包括校验码字节,
将输入帧input_eth的MAC地址,源/目的地址交换顺序后赋值给创建的帧output_eth,
type指定创建的帧类型,data为创建的帧封装的数据,data_len为输入的数据长度,
返回值为创建的帧长度,注意不包括帧的校验字节*/
int generate_eth_frame(byte *input_eth, byte *output_eth, uint16 type, char *data, int data_len){
    int frame_len = 0;
    /************ Begin ***********/
    int j = 0;
    for(int i = 0; i < 60; i++) {
        if(i < 6)
            output_eth[i] = input_eth[i+6];
        else if(i < 12)
            output_eth[i] = input_eth[i-6];
        else if(i == 12)
            output_eth[i] = htons(type);
        else if(i == 13)
            output_eth[i] = type;
        else if(i < data_len + 14)
            output_eth[i] = data[j++];
        else 
            output_eth[i] = 0;
    } 
    frame_len = 60;
    /************ End ************/
    return frame_len;
}

data_link_eth.h

#ifndef  _DATALINK_ETH_H_
#define  _DATALINK_ETH_H_
#include<stdio.h>
#include<string.h>
#pragma  pack(1) 
typedef unsigned char byte;
typedef unsigned short uint16;
typedef unsigned int uint32;
/* 网络字节顺序与本地顺序的转换,对于本地是大端模式的则不需要转换 */
#define	 htons(x)  ((0xff & ((x)>>8)) | ((0xff & (x)) << 8))
#define	 htonl(x)  ((((x)>>24) & 0x000000ff) | (((x)>> 8) & 0x0000ff00) | \
		   (((x)<<8) & 0x00ff0000) | (((x)<<24) & 0xff000000))
#define	 ntohs(x)  ((0xff & ((x)>>8)) | ( (0xff & (x)) << 8))
#define	 ntohl(x)  ((((x)>>24) & 0x000000ff) | (((x)>> 8) & 0x0000ff00) | \
		   (((x)<<8) & 0x00ff0000) | (((x)<<24) & 0xff000000))
/* 将两个字节表示的十进制拼接为单字节的值,如0x01 0e ->0x1e
本宏由测试程序调用中,用于准备帧字节流*/
#define a2hex(p)   (p[0] << 4)| (p[1] & 0x0f)

#define ETH_ADDR_LEN 6
#define ETH_HDR_LEN 14
#define MAX_ETH_LENTH  (1500+18)
typedef byte Eaddr[ETH_ADDR_LEN];

struct etherPkt {
    byte dst[ETH_ADDR_LEN];
    byte src[ETH_ADDR_LEN];
    uint16 type;
    byte data[1];
};

/*在tools.c中定义的全局变量*/
extern byte eth[MAX_ETH_LENTH];

/*将以十六进文本形式存储的包文件,转为字节流形式,并存于全局变量eth当中。
此函数在测试函数中调用,仅供大家参考*/
extern int generate_raw_pkt(char* pke_file);
/*以十六进制形式打印包的字节*/
extern void print_pkt(byte *pkt, int len);

/*根据输入参数创建帧,不需要计算帧校验码,创建的帧长也不包括校验码字节,
将输入帧input_eth的MAC地址,源/目的地址交换顺序后赋值给创建的帧output_eth,
type指定创建的帧类型,data为创建的帧封装的数据,data_len为输入的数据长度,
返回值为创建的帧长度,注意不包括帧的校验字节*/
extern int generate_eth_frame( byte *input_eth, byte *output_eth, uint16 type, char *data, int data_len);
#endif

tools.c

#include"datalink_eth.h"
unsigned char eth[MAX_ETH_LENTH] = { 0 };

/*将字符串表示的十六进制,转为对应的值,如二个字节符"1e"转为对应的两个值0x01 0e
本函数由测试程序调用中,用于准备帧字节流*/
char * str2hex(char *str) {
    int i = 0;
    while (str[i] != '\0') {
        if (str[i] >= '0' && str[i] <= '9')
            str[i] = str[i] - '0';
        if (str[i] >= 'a' && str[i] <= 'f')
            str[i] = str[i] - 'a' + 10;
        i++;
    }
    return str;
}
/*以十进进制形式打印包的字节*/
void print_pkt(byte *pkt, int len) {
    int row = 0;
    printf("\n%04d ", row);
    for (int i = 0; i < len; i++) {
        printf("%02x ", pkt[i]);
        if ((i + 1) % 8 == 0)
            printf("  ");
        if ((i + 1) % 16 == 0) {
            row++;
            printf("\n%04d ", row);
        }
    }
    printf("\n");
}
/*将以十六进文本形式存储的包文件,转为字节流形式,并存于全局变量eth当中。
此函数在测试函数中调用,仅供大家参考*/
int generate_raw_pkt(char *pke_file) {
    FILE *f   = fopen(pke_file, "r");
    if (f==NULL)
        return -1;
    char str[2000];
    int len = 0;
    while (fgets(str, 2000, f) != NULL) {//返回的串末尾有换行符'\n'
        char *p = strtok(str, " ");
        p = strtok(NULL, " ");
        while (p) {
            char *next = strtok(NULL, " ");
            char *t = str2hex(p);
            eth[len] = a2hex(t);
            len++;
            p = next;
        }
    }
    fclose(f);
    return len;
}

/*打印帧的地址*/
void print_eth_addr(byte *p) {
    int i = 0;
    for (i = 0; i < ETH_ADDR_LEN - 1; i++) {
        printf("%02x-", p[i]);
    }
    printf("%02x", p[i]);
}

test_main.c

#include "datalink_eth.h"

int  main(int argc, char **argv) {
    char file[256];
    fgets(file,255,stdin);
    int len = generate_raw_pkt(file);
    print_pkt(eth, len);
    unsigned char newEth[1500+14];
    const char str[] = "Hello World!";
    len = generate_eth_frame(eth, (unsigned char*)&newEth, 0x1314,(char*) str,strlen(str));
    print_pkt(newEth, len);

    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值