从1写TCPIP协议栈4:ARP协议实现
引言
上节提到ARP协议中协议类型字段需要注意大小端的问题。实际数据传输时,只拿到主机的IP地址是不行的,这样数据只能传递至链路层,因此必须还要拿到的MAC地址,ARP就是解决48位的MAC地址和32位的IPV4地址之间的对照和映射问题。ARP的映射是一种动态映射,因为实际网络中的网卡数量不定,部分机器下线后同一IP可能DHCP分配给其他网卡。ARP采用广播包,只适用于IPV4,IPV6使用邻居发现协议。除此之外,反向ARP(RARP)几乎已经被弃用(RFC0903)。
ARP实现:地址-IP映射表的可增删改
ARP报文结构体定义
“引言”中已经提到,ARP映射IP地址和MAC地址时并不是固定的,因此协议栈开发时需要提供ARP映射表的可增/删改-机制,实现可增删改的前提就是需要检查表中无效和错误的表项。本项目中只考虑一个表,基础代码实现为:
typedef union _arp_table_type
{
uint8_t array[IPV4_ADDR_SIZE];
uint32_t addr;
}_arp_table_type;
//arp1表项
typedef struct _arp_table
{
_arp_table_type ipaddr;//两种表示方式
uint8_t macaddr[LLC_MAC_SIZE];
uint8_t tablestate;//表状态,这里只有一个表(结构体)
uint16_t ttl;//生存时间/超时时间
uint8_t trycount;//错误后重试的次数
}_arp_table;
static _arp_table _arp_table1;//只有一个表项
//地址映射表更新
_type_drv_err _arp_update_maptable(uint8_t* srcip,uint8_t *srcmac)
{
memcpy(_arp_tableOne.ipaddr.array,srcip,SIZE_IPV4_ADDR);
memcpy(_arp_tableOne.macaddr, srcmac,SIZE_ETHII_MAC);
_arp_tableOne.tablestate = XARP_TABLE_OK;
_arp_tableOne.trycount = XARP_CFG_MAX_RETRIES;//超时时间
_arp_tableOne.ttl = XARP_CFG_ENTRY_OK_TTL;//生存时间
printf("[ARP Table Update] MAC: %d IP:%d ", srcmac,srcip);
}
MAC-IP映射表更新
//地址映射表更新
_type_drv_err _arp_update_maptable(uint8_t* srcip,uint8_t *srcmac)
{
memcpy(_arp_tableOne.ipaddr.array,srcip,SIZE_IPV4_ADDR);
memcpy(_arp_tableOne.macaddr, srcmac,SIZE_ETHII_MAC);
_arp_tableOne.tablestate = XARP_TABLE_OK;
_arp_tableOne.trycount = XARP_CFG_MAX_RETRIES;//超时时间
_arp_tableOne.ttl = XARP_CFG_ENTRY_OK_TTL;//生存时间
}
我们也可以arp -a
查看本机的ARP地址表:
ARP地址表中有动态和静态,动态ARP映射就是可以动态变化的。如前文所述:当动态ARP映射关系产生冲突时,一般都是停止使用此地址或者不理会继续使用;对于静态ARP地址映射,一般我们希望某些端口和IP是固定在某个VLAN下面,此类地址冲突时会发送ARP防御通告,如果冲突继续,继续使用此映射关系,因此静态的ARP映射优先级最高,可以理解为主机初始化阶段自行写入的~
ARP表超时机制
//arp表查询函数
void _arp_poll(void)
{
if (_eth_check_ttl(&_arp_time, XARP_TABLE_CHECK_TIME))//_eth_check_ttl:运行工程1s后启动ARP表项扫描
{
switch (_arp_tableOne.tablestate)//查看表状态
{
case XARP_TABLE_STATE_OK://表状态ok
if ((--_arp_tableOne.ttl) == 0)//ttl-1后跳出继续下一秒的操作
{
_arp_make_request(&_arp_tableOne.ipaddr);//查询arp表中的地址是否还存在,存在时在update函数会更新表项,ttl重新回到最大值
_arp_tableOne.tablestate = XARP_TABLE_STATE_PENDING;//设置当前表状态为查询中,跳转进XARP_TABLE_STATE_PENDING
_arp_tableOne.ttl = XARP_TABLE_TTL_PENDING;//为跳入case XARP_TABLE_STATE_PENDING 准备
}
break;
case XARP_TABLE_STATE_PENDING:
if ((--_arp_tableOne.ttl) == 0)//一般配置为1s,也就是紧接case XARP_TABLE_STATE_OK
{
if ((_arp_tableOne.trycount--) == 0)//在update函数中如果没有收到arp响应,并且超时次数超时,-释放掉
{
_arp_tableOne.trycount = XARP_TABLE_MAX_RETRIES;
_arp_tableOne.ttl = XARP_TABLE_TTL;//5
_arp_tableOne.tablestate = XARP_TABLE_Free;//0
}
else//继续尝试
{
_arp_make_request(&_arp_tableOne.ipaddr);//查询arp表中的地址是否还存在
_arp_tableOne.tablestate = XARP_TABLE_STATE_PENDING;//设置当前表状态为查询中,跳转进XARP_TABLE_STATE_PENDING
_arp_tableOne.ttl = XARP_TABLE_TTL_PENDING;//最大查询时间1s
}
}
break;
}
}
可能这里大家有点乱,没关系,其实ARP表项的超时机制核心就是:协议栈启动一定时间后启动ARP表项检查,检查时根据表状态分类,表ok状态
时,ttl开始定期自减,为0后进入表状态pending
发送ARP请求;表状态pending后
开始连续数次发送ARP请求等待ARP响应。整个过程中ARP的输入检查一直while(1),这意味着一旦接收到ARP响应,表的各项参数全部恢复默认值:
//地址映射表更新--下文的数据输入中就有
_type_drv_err _arp_update_maptable(uint8_t* srcip,uint8_t *srcmac)
{
memcpy(_arp_tableOne.ipaddr.array,srcip,SIZE_IPV4_ADDR);
memcpy(_arp_tableOne.macaddr, srcmac,SIZE_ETHII_MAC);
_arp_tableOne.tablestate = XARP_TABLE_STATE_OK;
_arp_tableOne.trycount = XARP_TABLE_MAX_RETRIES;//超时时间
printf("[ARP Table Update] MAC: %d IP:%d ", srcmac,srcip);
}
ARP实现:数据输入与输出
数据输出:免费ARP实现
免费ARP指在主机启动时向网络其他主机通告的自己主机地址的行为,报文中发送方和接收方的MAC与IP都是发送方信息,其他主机在收到此ARP广播包后更新自己地址映射表中的IP-MAC映射,理想状态下主机不会收到任何回复。若网络中存在与自己IP重复的主机,此时就会报错。基于此问题RFC5227
提出了ACD地址冲突检测机制,地址冲突机制要求ARP报文的目的MAC/IP地址都为0,以免准备使用的正确的IPV4地址在其他主机被更新/污染。因此ACD机制下的ARP也是免费ARP。
如果没有查到冲突,此时主机会间隔2s向网络发送两个完全体的免费ARP报文(目标和源mac/IP都是发送方)来告知其他主机更新自己地址映射表,代码实现如下:
//arp包结构
typedef struct _arp_packet
{
uint16_t hardtype, prtcltype;
uint8_t hardsize, prtclsize;
uint16_t option;
uint8_t sormac[LLC_MAC_SIZE];
uint8_t destmac[LLC_MAC_SIZE];
uint8_t sorIP[IPV4_ADDR_SIZE];
uint8_t destIP[IPV4_ADDR_SIZE];
}_arp_packet;
int _arp_make_request(const _arp_ip_type ipaddr)
{
_eth_packet* packet = _eth_packet_tx(sizeof(packet));
_arp_packet* arppacket = (_arp_packet*)packet->dataptr;
arppacket->hardtype = swap_order16(XARP_HW_TYPE);//硬件类型
arppacket->prtcltype = swap_order16(TCPIP_PROTOCOL);//协议类型
arppacket->hardsize = SIZE_ETHII_MAC;//硬件长度
arppacket->prtclsize = SIZE_IPV4_ADDR;//协议长度
arppacket->option = swap_order16(XARP_OP_REAUEST);//请求
memcpy(arppacket->sormac, _mac_cfg);//源mac
memcpy(arppacket->sorIP, _ip_cfg.array,SIZE_IPV4_ADDR);//源ip
memcpy(arppacket->destmac, 0,SIZE_ETHII_MAC);//目标mac,接收方为全0,避免被污染
memcpy(arppacket->destIP,ipaddr.array,SIZE_IPV4_ADDR);//目标IP,也应该为0
return _eth_drive_send(ARP_PROTOCOL,_mac_boardcast,packet);
}
本次练习我们不进行完整的冲突检测开发,那实际ACD机制具体如何处理冲突呢?RFC5227
表明,如果发送方在其他主机的免费ARP报文中发现了自己的主机IP,视为冲突。地址冲突目前有三种解决方式:停止使用、继续使用、发送防御ARP报文后继续使用此地址。
数据输入:报文检查与响应
除了免费ARP和地址映射表之外,就是对ARP报文做接收和响应,其中接收的实现思想代码体现为:
驱动层收包
_eth_packet* packet;
if (_eth_drive_read(&packet) == DRIVE_ERR_OK)
{
_ethernet_input(packet);
}
包检查
static _type_drv_err _ethernet_input(_eth_packet* packet/*发送的包*/)
{
_ethII_packet* eth_header = NULL;//用于解析mac地址
if (packet->size <= sizeof(_ethII_packet))//不能比header小
{
return;
}
eth_header = (_ethII_packet*)packet->dataptr;
switch (swap_order16(eth_header->Type))
{
case ARP_PROTOCOL://ARP协议的话
_eth_packet_del_header(packet,sizeof(_ethII_packet));//删除链路层包头,
_arp_check_in(packet);//检查ARP
break;
case TCPIP_PROTOCOL:break;//预留接口给TCPIP协议
}
return DRIVE_ERR_OK;
}
具体的ARP包检查实现思想为:
void _arp_check_in(_eth_packet* packet)
{
if (packet->size < sizeof(_arp_packet))
return;//不做响应
_arp_packet* arp_packet = (_arp_packet*)packet->dataptr;
uint16_t opcode = swap_order16(arp_packet->option);
printf("arp opcode = 0x%llx \n", opcode);
//判断包字段
if (swap_order16(arp_packet->hardtype) != XARP_HW_TYPE || arp_packet->hardsize != SIZE_ETHII_MAC ||
swap_order16(arp_packet->prtcltype) != TCPIP_PROTOCOL || arp_packet->prtclsize != SIZE_IPV4_ADDR ||
(opcode != (XARP_OP_REPLY || XARP_OP_REAUEST))
)
{
//打印错误的信息
printf("hardtype = 0x%llx \n", swap_order16(arp_packet->hardtype));
printf("hardsize = %d \n", arp_packet->hardsize);
printf("prtcltype = 0x%llx \n", swap_order16(arp_packet->prtcltype));
printf("prtclsize = %d \n", arp_packet->prtclsize);
printf("option = %d", opcode);
return;//不做响应
}
//检查包的目标IP地址是不是本机地址
if (!IP_equal(&_ip_cfg,arp_packet->destIP))
{
return;
}
//OP:1-ARP请求,2-ARP响应,3-RARP请求,4-Rarp响应
switch (opcode)
{
case XARP_OP_REAUEST:
_arp_make_response(arp_packet);//封装响应包
_arp_update_maptable(arp_packet->sorIP,arp_packet->sormac);//更新地址映射表,对应上文ARP超时机制
break;
case XARP_OP_REPLY:
_arp_update_maptable(arp_packet->sorIP, arp_packet->sormac);//更新地址映射表
break;
}
}
包响应
包响应接口封装一个格式合格的ARP报文并进行发送:
//响应发送
_type_drv_err _arp_make_response(_arp_packet * arp_packet)//传入接收到的ARP报文
{
_eth_packet* packet = _eth_packet_tx(sizeof(_arp_packet));
_arp_packet* response_pocket = (_arp_packet*)packet->dataptr;
response_pocket->hardtype = swap_order16(XARP_HW_TYPE);
response_pocket->prtcltype = swap_order16(TCPIP_PROTOCOL);
response_pocket->hardsize = SIZE_ETHII_MAC;
response_pocket->prtclsize = SIZE_IPV4_ADDR;
response_pocket->option = swap_order16(XARP_OP_REPLY);//响应
memcpy(response_pocket->sormac, _mac_cfg, SIZE_ETHII_MAC);
memcpy(response_pocket->sorIP, _ip_cfg.array, SIZE_IPV4_ADDR);
memcpy(response_pocket->destmac, arp_packet->sormac, SIZE_ETHII_MAC);//目的MAC为接收到的ARP报文源MAC
memcpy(response_pocket->destIP, arp_packet->sorIP, SIZE_IPV4_ADDR);//目的IP为接收到的ARP报文源IP
return _ethernet_send(ARP_PROTOCOL, arp_packet->sormac, packet);//利用驱动层函数发送ARP包,驱动层函数会进行MAC的增加,请参考本白其他文章
}
调试
当你已经实现了上述的几个接口后,便可以试运行,只看ARP协议:
后面的两条ARP是因为ICMP-PING产生的:
当ARP完成之后,我们查看虚拟机的IP-MAC地址映射,可以看出已经更新:
我们再正常进行主机和虚拟机的互PING操作:
可以看出每工程进行一次PING操作,发送方都会向接收方发送自己的ARP报文以求更新和同步MAC地址。(这里可能不对,本白自己抓包了几次看现象都是这样的~)
关于ARP协议的简单思考
经过ARP协议的基础开发,已然能够发觉ARP协议存在的漏洞,那就是容易被欺骗,我只是用C++虚拟了一个主机就可以骗过本地主机和虚拟机参与其中的通讯,因此ARP机制的安全性是非常值得思考的。根据小白之前的所见所闻,可能的的手段有:
1、增添协议的复杂性,比如添加一些签名和校验形成新的ARP协议。
2、在物理层对ARP的各主机进行专网划分并校验VLANID。
3、可以修改ARP报文的传播方式,将广播修改为单播。
4、与DHCP进行集合形成带有某种复杂规律的动态MAC-IP映射关系。
5、将静态IP和实际网络传输的IP进行某种掩码的换算,比如异或算法,又比如使用E2E的DataID,再指定一个中间转换没有规律的映射表。
... ...
在车联网程度越来越高的今天,其实这些安全手段的本质就是为了保护MAC-IP的映射表关系不被外界轻易嗅探,这对以太网的安全来说是极大的挑战,因为通信速率越高,网内交互越复杂,需要辅助的协议就越多,协议越多,能被攻击的点越多,各位刺客的“妙手”可就太多了,哈哈哈。这样看来Flexray还真是挺好用的,除了速率不太行/成本较高,其他的优点真是诀绝子。可能有朝一日,大家就会发现对以太网的维护成本上升到接近甚至超过Flexray的使用成本,可能以太网也会引入TDMA机制,届时车载以太网可能又会提出什么Flex-ETH之类的协议,将车载网络推向新的高度~当然,就目前来说个人觉得采用2+5的方式基本可以实现ARP的保护。
附录
ARP参考文档:RFC826
ADC参考文档:RFC5227
RFC官方文档:网址
本文来自博客园,作者:{张一默},转载请注明原文链接:https://www.cnblogs.com/YiMo9929/p/17100008.html