arp报文及使用go实现

一、ARP协议报文格式及ARP表

ARP(Address Resolution Protocal,地址解析协议)是将IP地址解析为以太网的MAC地址(或者称为物理地址)的协议。在局域网中,当主机或其他网络设备有数据要发送给另一个主机或设备时,它必须知道对方的网络层地址(即IP地址)。但是仅仅有IP地址还是不够的,因为IP数据报文必须封装成帧才能通过物理网络发送,因为发送站还必须有接收站的物理地址,所以需要一个从IP地址到物理地址的映射。ARP就是实现这个功能的协议。

ARP报文

ARP是一个独立的三层协议,所以ARP报文在向数据链路层传输时不需要经过IP协议的封装,而是直接生成自己的报文,其中包括ARP报头,到数据链路层后再由对应的数据链路层协议(如以太网协议)进行封装。ARP报文分为ARP请求和ARP应答报文两种,它们的报文格式可以统一为下图所示。

  • 硬件类型:占两字节,表示ARP报文可以在哪种类型的网络上传输,值为1时表示为以太网地址。
  • 上层协议类型:占两字节,表示硬件地址要映射的协议地址类型,映射IP地址时的值为0x0800
  • MAC地址长度:占一字节,标识mac地址长度,以字节为单位,此处为6.
  • IP协议地址长度:占一字节,标识IP地址长度,以字节为单位,此处为4
  • 操作类型:占两字节,指定本次ARP报文类型。1表示arp请求报文,2表示arp应答报文,3表示RARP请求,4表示RARP应答
  • 源MAC地址:占六字节,标识发送设备的硬件地址
  • 源IP地址:占4字节,标识发送方设备的IP地址
  • 目的MAC地址:占六字节,标识接收方设备的硬件地址,在请求报文中该字段全为0,即00-00-00-00-00-00,表示任意地址,因为现在不知道这个地址
  • 目的IP地址:占四字节,表示接收方的IP地址

ARP报文不是直接在网络层上发送的,它还是需要向下传输到数据链路层,所以当ARP报文传输到数据链路层之后,需要再次进行封装。以以太网为例,ARP报文传输到以太网数据链路层后会形成ARP帧。ARP帧如下图所示,他就是在ARP报文前面加了一个以太网帧头

以太网帧头的三个字段说明:

  • 目的MAC地址:占6字节,如果是ARP请求帧,因为它是一个广播帧,所以要填上广播MAC地址(FF-FF-FF-FF-FF-FF),其目标主机是网络上所有主机
  • 源MAC地址:占6字节,这是发送ARP帧的节点MAC地址
  • 帧类型:占两字节,这里用来标识 帧封装的上层协议,因为本帧的数据部分是ARP部分,所以直接用ARP的协议号0x0806表示就可以了。

ARP映射表

无论是主机,还是交换机都会有一个用来缓存同一网段设备IP地址和MAC地址的ARP映射表,用于数据帧的转发。设备通过ARP解析到目的MAC之后,将会在自己的ARP映射表中增加IP地址到MAC地址的映射表,以用于后续到同一目的地数据帧的转发。ARP表项分为动态ARP表项和静态ARP表项。

动态ARP表项

动态ARP表项由ARP协议通过ARP报文自动生成和维护,可以被老化,可以被新的ARP报文更新,也可以被静态ARP表项所覆盖。当到达老化时间或接口关闭时会删除相应的动态ARP表项

静态ARP表项

静态ARP表项通过手工配置(通过对应设备的IP地址与MAC地址绑定命定进行)和维护。不会被老化,也不会被动态ARP表项覆盖。配置静态ARP表项可以增加通信的安全性,因为静态ARP可以限定和指定IP地址的设备通信时只使用指定的MAC地址(也就是我们通常所说的IP地址和MAC地址的绑定),此时攻击报文无法修改此表项的IP地址和MAC地址的映射关系,从而保护了本设备和指定设备间正常通信。静态ARP表项又分为短静态ARP表项和长静态ARP表项

短静态ARP表项

在配置短静态ARP表项时,只需要配置IP地址和MAC地址项。如果出接口是三层以太网接口,短静态ARP表项可以直接用于报文转发;如果出接口是VLAN虚接口,短静态ARP表项不能直接用于报文转发,当要发送IP数据包时,先发送ARP请求报文,如果收到的相应报文中的源IP地址和源MAC地址与所配置的IP地址和MAC地址相同,则将接受ARP响应报文的接口加入该静态表项中,之后就可以用于IP数据包的转发了。

长静态ARP表项

在配置长静态ARP表项时,除了配置IP地址和MAC地址项外,还必须配置该ARP表所对应的VLAN(虚拟局域网)和出接口。也就是长静态ARP表项同事绑定了IP地址、MAC地址、VLAN和端口,可以直接用于报文转发。

二、arp解析过程

  1. 当PC1想发送数据给PC2,首先在自己的本地ARP缓存表中检查主机PC2匹配的MAC地址
  2. 如果PC1缓存中没有找到响应的条目,它将询问主机PC2的MAC地址,从而将ARP请求帧广播到本地网络的所有主机。该帧中包括源主机PC1的IP、MAC地址,本地网络中的所有主机都接收到ARP请求,并且检查是否与自己的ip地址相匹配。如果发现请求中的IP地址与自己的IP不匹配,则丢弃ARP请求
  3. 主机PC2确定ARP请求中的IP地址与自己的IP地址匹配,则将主机pc1的地址和mac地址添加到本地缓存表中。
  4. 主机PC2将包含其mac地址的ARP回复消息直接发送回主机pc1(数据帧为单播)
  5. 主机pc1收到PC2返回的ARP回复消息,将PC2的IP和MAC地址添加至自己的ARP缓存表中,本机缓存是有生存期的,默认ARP缓存表有效期120s。当超过该有效期后,则将重复上面过程。主机pc2的MAC地址一旦确定,主机PC1就能向主机PC2发送IP信息

rp中,如果内网IP10.10.10.10,访问外网IP 100.100.100.100, 网关为10.10.10.1, 网关对外映射IP 100.100.100.1 ,这是arp的过程是什么样子的?

  1. 主机发送数据包:

    • 主机在发送数据包时,发现目标 IP 地址(100.100.100.100)不在同一子网内。
    • 主机会查找其本地 ARP 缓存表,如果没有找到目标 IP 对应的 MAC 地址,则会尝试发送 ARP 请求以获取目标 MAC 地址。
  2. ARP 请求发送:

    • 主机将发送一个 ARP 请求,目标 IP 地址为网关的 IP 地址(10.10.10.1)。
    • ARP 请求中包含主机自身的 IP 地址(10.10.10.10)、MAC 地址(主机的物理地址)以及目标 IP 地址(10.10.10.1)。
  3. 网关收到 ARP 请求:

    • 网关接收到 ARP 请求,检查请求中的目标 IP 地址。
    • 网关发现自己的 IP 地址与 ARP 请求中的目标 IP 地址匹配(即10.10.10.1),因此它会作出响应。
  4. 网关发送 ARP 响应:

    • 网关将会发送一个 ARP 响应给主机,包含网关自身的 MAC 地址。
    • 这个 ARP 响应中会包含主机之前请求的目标 IP 地址(10.10.10.1)和网关的 MAC 地址。
  5. 主机收到 ARP 响应:

    • 主机接收到网关的 ARP 响应后,将该映射关系存储到 ARP 缓存表中,以备将来使用。
    • 接着,主机会更新自己要发送到目标 IP 地址(100.100.100.100)的数据包,使用网关的 MAC 地址作为目标 MAC 地址。
  6. 数据包转发:

    • 网关接收到主机发来的数据包,因为它知道如何到达外部的 100.100.100.100,所以它会根据自身的路由表进行转发。
    • 网关将该数据包重新封装,将目标 IP 地址更改为 100.100.100.100,并将数据包发送到下一个目标,即外部网络。

arp报文抓包分析

arp请求报文

arp返回报文

三、用go实现发送ARP包

package main

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"net"
	"time"

	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcap"
	"github.com/google/gopacket/routing"
	"github.com/jackpal/gateway"
	"github.com/libp2p/go-netroute"
)

func main() {
	//targetIP := net.IP{100, 100, 100, 100} //设置目标地址
	//device, _ := GetDevByIp(targetIP)
	device, _ := GetDevByIp(net.IP{10, 100, 100, 100})
	srcIP, srcMac, gw, device2, _ := GetIpFromRouter(net.IP{100, 100, 200, 100})
	fmt.Println(srcIP, srcMac, gw)
	fmt.Println(device, device2)

	//构建ARP请求包
	arpLayer := &layers.ARP{
		AddrType:          layers.LinkTypeEthernet,  //硬件类型 1
		Protocol:          layers.EthernetTypeIPv4,  //上层协议类型0x0800
		HwAddressSize:     6,                        //mac地址长度
		ProtAddressSize:   4,                        // IP地址长度
		Operation:         layers.ARPRequest,        //操作类型,1为arp请求
		SourceHwAddress:   []byte(srcMac),           //本机的mac地址
		SourceProtAddress: []byte(srcIP),            //本机的IP地址
		DstHwAddress:      []byte{0, 0, 0, 0, 0, 0}, //目标mac地址,使用任意地址,未知
		DstProtAddress:    []byte{10, 122, 131, 225},
	}

	ethLayer := &layers.Ethernet{
		EthernetType: layers.EthernetTypeARP,                               //帧类型 0x0806
		SrcMAC:       srcMac,                                               //本机mac地址
		DstMAC:       net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, //广播mac地址
	}

	buffer := gopacket.NewSerializeBuffer()
	opts := gopacket.SerializeOptions{FixLengths: true}
	err := gopacket.SerializeLayers(buffer, opts, ethLayer, arpLayer)
	if err != nil {
		log.Fatal(err)
	}

	outgoingPacket := buffer.Bytes()

	handle, err := pcap.OpenLive(device, 1024, true, pcap.BlockForever)
	if err != nil {
		log.Fatal(err)
	}
	defer handle.Close()

	//发送 ARP请求包
	err = handle.WritePacketData(outgoingPacket)
	fmt.Printf("Outgoing Packet: %x\n", outgoingPacket)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("arp request sent")
	time.Sleep(3 * time.Second)

	// 接收并处理响应包
	packetSource := gopacket.NewPacketSource(handle, handle.LinkType())

	timeout := time.After(5 * time.Second) // 设置超时时间为5秒

	// 接收并处理响应包
	for {
		select {
		case packet := <-packetSource.Packets():
			arpLayer := packet.Layer(layers.LayerTypeARP)
			if arpLayer != nil {
				arpPacket, _ := arpLayer.(*layers.ARP)
				if arpPacket.Operation == layers.ARPReply && bytes.Equal(arpPacket.SourceProtAddress, gw) {
					fmt.Printf("MAC address of %s is %s\n", gw.String(), arpPacket.SourceHwAddress)
					return
				}
			}
		case <-timeout:
			fmt.Println("Timeout: No response received")
			return
		}
	}
}

// 获取设备名称
func GetDevByIp(ip net.IP) (devName string, err error) {
	devices, err := pcap.FindAllDevs()
	if err != nil {
		return
	}
	for _, d := range devices {
		for _, address := range d.Addresses {
			_ip := address.IP.To4()
			if _ip != nil && _ip.IsGlobalUnicast() && _ip.Equal(ip) {
				fmt.Println()
				fmt.Println("找到网络设备:", d.Name)
				//return d.Name, nil
			}
		}
		return d.Name, nil
	}
	return "", errors.New("can not find dev")
}

// 通过扫描的目标IP获取发包的网卡信息,返回源IP、源mac、网关IP、设备名称
func GetIpFromRouter(dstIp net.IP) (srcIp net.IP, srcMac net.HardwareAddr, gw net.IP, devName string, err error) {
	//先验证扫描IP是否同网段
	srcIp, srcMac = GetIfaceMac(dstIp)
	if srcIp == nil {
		//如果不是同网段,则查询路由
		var r routing.Router    //创建一个 routing.Router 类型的变量,用于查询路由信息
		r, err = netroute.New() //初始化routing.Router
		if err == nil {
			var iface *net.Interface               //创建变量iface用于保存与路由相关的网络接口信息
			iface, gw, srcIp, err = r.Route(dstIp) //通过路由查询路由信息,包括目标IP地址 dstIp 对应的路由信息。iface 保存了与该路由信息关联的网络接口,gw 保存了网关IP地址,srcIp 保存了与该路由信息关联的本地IP地址
			if err == nil {
				if iface != nil {
					srcMac = iface.HardwareAddr //如果找到了与目标IP地址匹配的路由信息,即 iface 不为 nil,则设置 srcMac 为该网络接口的MAC地址。否则,继续下一步
				} else {
					_, srcMac = GetIfaceMac(srcIp)
				}
			}
		}
		//如果在之前的步骤中出现错误或者 srcMac 为 nil,它尝试取得第一个默认网关的信息。
		if err != nil || srcMac == nil {
			//取第一个默认路由
			gw, err = gateway.DiscoverGateway() //获取第一个默认网关的IP地址
			if err == nil {
				srcIp, srcMac = GetIfaceMac(gw)
			}
		}
	}
	gw = gw.To4()
	srcIp = srcIp.To4()
	devName, err = GetDevByIp(srcIp)
	if srcIp == nil || err != nil || srcMac == nil {
		if err == nil {
			err = fmt.Errorf("err")

		}
		return nil, nil, nil, "", fmt.Errorf("no router,%s", err)
	}
	return

}

// 查找与指定IP地址相匹配的本地IP地址和MAC地址,其实就是检测是否同网段
func GetIfaceMac(ifaceAddr net.IP) (src net.IP, mac net.HardwareAddr) {
	interfaces, _ := net.Interfaces()  //获取本地计算机上的网络接口信息
	for _, iface := range interfaces { //遍历所有网络接口
		if addrs, err := iface.Addrs(); err == nil { //获取接口的IP地址列表
			for _, addr := range addrs { //遍历该接口的IP地址
				if addr.(*net.IPNet).Contains(ifaceAddr) { //检查每个IP地址是否包含了给定的ifaceAddr(即传参),这里用的contains方法,检查给定IP和接口IP是否同一子网
					return addr.(*net.IPNet).IP, iface.HardwareAddr //匹配就返回接口IP和mac地址
				}
			}

		}

	}
	return nil, nil

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/188659.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

03:2440--UART

目录 一:UART 1:概念 2:工作模式 3:逻辑电平 4:串口结构图 5:时间的计算 二:寄存器 1:简单的UART传输数据 A:GPHCON--配置引脚 B:GPHUP----使能内部上拉​编辑 C: UCON0---设置频率115200 D: ULCON0----数据格式8n1 E:发送数据 A:UTRSTAT0 B:UTXHO--发送数据输…

二维数值型数组例题2

1、内部和 题目描述 给定一个m行n列的二维矩阵&#xff0c;求其内部元素和 输入要求 第一行为两个整数&#xff1a;m和n&#xff08;0<m,n<10&#xff09;&#xff0c;接下来输入m*n的二维矩阵 输出要求 二维矩阵内部元素和 输入样例 3 3 1 2 3 4 5 6 7 8 9 …

基于python+Django+SVM算法模型的文本情感识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介1. 简介2. 技术栈3. 系统架构4. 关键模块介绍5. 如何运行 二、功能三、系统四. 总结 一项目简介 # 基于 Python Django SVM 算法模型的文本情感识别系统介…

芯片安全和无线电安全底层渗透技术

和传统网络安全不同&#xff0c;硬件安全、芯片安全、无线电安全属于网络底层安全的重要细分领域&#xff0c;是网络安全的真正基石&#xff0c;更是国家安全的重要组成部分&#xff0c;“夯实网络底层安全基础&#xff0c;筑牢网络强国安全底座”&#xff0c;是底网安全重要性…

基于51单片机的百叶窗控制系统设计

**单片机设计介绍&#xff0c; 基于51单片机的百叶窗控制系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于51单片机的百叶窗控制系统设计可以分为硬件设计和软件设计两个方面。下面是一个简要的设计介绍&#xff1a; …

阿里云服务器安装mysql数据库之后无法远程连接

目录 一、mysql安装完成后直接远程远程连接阿里云服务器上的MySQL会报下述错误&#xff1a; 1、修改root用户的host 为% 登录MySQL 后 执行 2、修改完成后执行 3、退出mysql 重启mysql服务 exit; 4、修改完成后需要设置阿里云的安全规则。 二、dbaver测试链…

docker devicemapper: Error running DeleteDevice dm_task_run failed

docker 删除容器&#xff0c;遇到&#xff1a; devicemapper: Error running DeleteDevice dm_task_run failed 异常 [hadoophadoop02 ~]$ sudo docker rm 5ede1280f0bf Error response from daemon: container 5ede1280f0bf791e91d40038b15decd42e8923546ae578abd96e08114c76…

JVM——垃圾回收(方法区中的垃圾回收和(堆回收)自动垃圾回收)

目录 1.自动垃圾回收介绍1.C/C的内存管理2.Java的内存管理3.垃圾回收的对比 2.方法区的回收方法区的回收 – 手动触发回收 3.堆回收1.引用计数法2.可达性分析算法 1.自动垃圾回收介绍 1.C/C的内存管理 ⚫ 在C/C这类没有自动垃圾回收机制的语言中&#xff0c;一个对象如果不再…

【差旅游记】走进新疆哈密博物馆

哈喽&#xff0c;大家好&#xff0c;我是雷工&#xff01; 前些天在新疆哈密时&#xff0c;有天下午有点时间&#xff0c;看离住的宾馆不远就是哈密博物馆&#xff0c;便去逛了逛博物馆&#xff0c;由于接下来的一段时间没顾上记录&#xff0c;趁今天有些时间简单记录下那短暂的…

蓝桥杯每日一题2023.11.25

题目描述 “蓝桥杯”练习系统 (lanqiao.cn) 题目分析 对于此题目最开始是递归想法&#xff0c;但发现题意中的匹配次数实际上是指在这8个字母中这8个字母每个字母对应的个数是否相同&#xff0c;如果相同则匹配。 此处我们可以使用subsrtr函数&#xff0c;每次循环截取8个字…

值得看的书--《全宋词》节选

(https://img-blog.csdnimg.cn/5d5fe2844f6646b5b7b415f0a9e80f6c.jpg)

基于uniapp的 电子书小程序——需求整理

前言 想开发一个很简单的 电子书阅读小程序&#xff0c;要怎么做的。下面从功能、数据库设计这一块来说一下。说不一定能从某个角度提供一些思路 开发语言 springcloud uniapp 小程序&#xff08;vue2&#xff09;mysql 说明 电子书的主题是电子书&#xff0c;我们在日常…

Elasticsearch集群部署 head监控插件 Kibana部署 Nginx反向代理 Logstash部署

一、组件介绍1、Elasticsearch&#xff1a;2 、Logstash3、Kibana4、Kafka&#xff1a;5、Filebeat: 二、 Elasticsearch集群部署服务器创建用户安装ES修改配置文件创建数据和日志目录设置JVM堆大小 #7.0默认为4G修改安装目录及存储目录权限系统优化&#xff08;1&#xff09;增…

2024年天津天狮学院专升本护理学专业《内外科护理学》考试大纲

天津天狮学院2024年护理学专业高职升本入学考试《内外科护理学》考试大纲 一、考试性质 《内外科护理学》专业课程考试是天津天狮学院护理专业高职升本入学考试的必考科目之一&#xff0c;其性质是考核学生是否达到了升入本科继续学习的要求而进行的选拔性考试。《内外科护理学…

完蛋!我被ConcurrentHashMap源码包围了!(一)

文章目录 1. 引言2. 使用3. 初始化4. 存储流程5. 取值流程6. 扩容流程 1. 引言 ConcurrentHashMap是一个线程安全的HashMap&#xff0c;在JDK1.7与JDK1.8&#xff0c;无论是实现还是数据结构都会有所不一样。这促使了ConcurrentHashMap有着HashMap一样的面试高频考点。 接下来…

【Linux】进程间通信——system V共享内存、共享内存的概念、共享内存函数、system V消息队列、信号量

文章目录 进程间通信1.system V共享内存1.1共享内存原理1.2共享内存数据结构1.3共享内存函数 2.system V消息队列2.1消息队列原理 3.system V信号量3.1信号量原理3.2进程互斥 4.共享内存的使用示例 进程间通信 1.system V共享内存 1.1共享内存原理 共享内存区是最快的IPC形式…

递归剪枝题

期中考终于考完了&#xff0c;整道题奖励下自己 我一北大同学问我的&#xff0c;说他递归超时了&#xff0c;叫我想一个办法 后面他说他加了个剪枝就过了&#xff0c;然后我自己尝试了一个方法&#xff1a; 就是先把城市按1到n排列&#xff0c;然后考虑互换&#xff0c;如果互…

02 _ 架构分层:我们为什么一定要这么做?

在系统从0到1的阶段&#xff0c;为了让系统快速上线&#xff0c;我们通常是不考虑分层的。但是随着业务越来越复杂&#xff0c;大量的代码纠缠在一起&#xff0c;会出现逻辑不清晰、各模块相互依赖、代码扩展性差、改动一处就牵一发而动全身等问题。 这时&#xff0c;对系统进…

基于OGG实现Oracle实时同步MySQL

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

Python---函数定义时缺省参数(参数默认值)---放最右边

缺省参数也叫默认参数&#xff0c;用于定义函数&#xff0c;为参数提供默认值&#xff0c;调用函数时 可 不传该默认参数的值&#xff08;注意&#xff1a;所有位置参数必须出现在默认参数前&#xff0c;包括函数定义和调用&#xff09;。 比如&#xff1a;原先的代码&#…