arp报文定义与基本介绍

This commit is contained in:
impact-eintr
2022-11-26 11:38:03 +08:00
parent 331b8bd3dd
commit 20b5b3415a
5 changed files with 135 additions and 1 deletions

99
tcpip/header/arp.go Normal file
View File

@@ -0,0 +1,99 @@
package header
import "netstack/tcpip"
const (
// ARPProtocolNumber是ARP协议号为0x0806
ARPProtocolNumber tcpip.NetworkProtocolNumber = 0x0806
// ARPSize是ARP报文在IPV4网络下的长度
ARPSize = 2 + 2 + 1 + 1 + 2 + 2*6 + 2*4 // 28 Bytes
)
// ARPOP 代表ARP的操作码
type ARPOp uint16
// RFC 826 定义的操作码
const (
// arp 请求
ARPRequest ARPOp = 1
// arp应答
ARPReply ARPOp = 2
)
/*
ARP报文的封装
1. 2B 硬件类型(hard type) 硬件类型用来指代需要什么样的物理地址,如果硬件类型为 1表示以太网地址
2. 2B 协议类型 协议类型则是需要映射的协议地址类型,如果协议类型是 0x0800表示 ipv4 协议。
3. 1B 硬件地址长度 表示硬件地址的长度,单位字节,一般都是以太网地址的长度为 6 字节。
4. 1B 协议地址长度: 表示协议地址的长度,单位字节,一般都是 ipv4 地址的长度为 4 字节。
5. 2B 操作码 这些值用于区分具体操作类型,因为字段都相同,所以必须指明操作码,不然连请求还是应答都分不清。
1=>ARP 请求, 2=>ARP 应答3=>RARP 请求4=>RARP 应答。
6. 6B 源硬件地址 源物理地址如02:f2:02:f2:02:f2
7. 4B 源协议地址 源协议地址如192.168.0.1
8. 6B 目标硬件地址 目标物理地址如03:f2:03:f2:03:f2
9. 4B 目标协议地址 目标协议地址,如 192.168.0.2
*/
type ARP []byte
// 从报文中得到硬件类型
func (a ARP) hardwareAddressSpace() uint16 { return uint16(a[0])<<8 | uint16(a[1]) }
// 从报文中得到协议类型
func (a ARP) protocolAddressSpace() uint16 { return uint16(a[2])<<8 | uint16(a[3]) }
// 从报文中得到硬件地址的长度
func (a ARP) hardwareAddressSize() int { return int(a[4]) }
// 从报文中得到协议的地址长度
func (a ARP) protocolAddressSize() int { return int(a[5]) }
// Op从报文中得到arp操作码.
func (a ARP) Op() ARPOp { return ARPOp(a[6])<<8 | ARPOp(a[7]) }
// SetOp设置arp操作码.
func (a ARP) SetOp(op ARPOp) {
a[6] = uint8(op >> 8)
a[7] = uint8(op)
}
// SetIPv4OverEthernet设置IPV4网络在以太网中arp报文的硬件和协议信息.
func (a ARP) SetIPv4OverEthernet() {
a[0], a[1] = 0, 1 // htypeEthernet
a[2], a[3] = 0x08, 0x00 // IPv4ProtocolNumber
a[4] = 6 // macSize
a[5] = uint8(IPv4AddressSize)
}
// ProtocolAddressSender从报文中得到arp发送方的协议地址为ipv4地址
func (a ARP) ProtocolAddressSender() []byte {
const s = 8 + 6 // 8 是arp的协议头部 6是本机MAC
return a[s : s+4] // 本机IP
}
// HardwareAddressTarget从报文中得到arp目的方的硬件地址
func (a ARP) HardwareAddressTarget() []byte {
const s = 8 + 6 + 4 // 8是arp协议头部 6 是本机MAC 4是本机ip
return a[s : s+6] // 目标MAC
}
// ProtocolAddressTarget从报文中得到arp目的方的协议地址为ipv4地址
func (a ARP) ProtocolAddressTarget() []byte {
const s = 8 + 6 + 4 + 6 // 8是arp协议头部 6 是本机MAC 4是本机ip 6是目标MAC
return a[s : s+4] // 目标IP
}
// IsValid检查arp报文是否有效
func (a ARP) IsValid() bool {
// 比arp报文的长度小返回无效
if len(a) < ARPSize {
return false
}
const htypeEthernet = 1
const macSize = 6
// 是否以太网、ipv4、硬件和协议长度都对
return a.hardwareAddressSpace() == htypeEthernet &&
a.protocolAddressSpace() == uint16(IPv4ProtocolNumber) &&
a.hardwareAddressSize() == macSize &&
a.protocolAddressSize() == IPv4AddressSize
}

View File

@@ -0,0 +1,24 @@
# arp协议介绍
在以太网协议中规定,同一局域网中的一台主机要和另一台主机进行直接通信,必须要知道目标主机的 MAC 地址。而在 TCP/IP 协议中,网络层和传输层只关心目标主机的 IP 地址。这就导致在以太网中使用 IP 协议时,数据链路层的以太网协议接到上层 IP 协议提供的数据中,只包含目的主机的 IP 地址。于是需要一种方法,根据目的主机的 IP 地址,获得其 MAC 地址。这就是 ARP 协议要做的事情。所谓地址解析address resolution就是主机在发送帧前将目标 IP 地址转换成目标 MAC 地址的过程。
当发送主机和目的主机不在同一个局域网中时,即便知道目的主机的 MAC 地址,两者也不能直接通信,必须经过路由转发才可以。所以此时,发送主机通过 ARP 协议获得的将不是目的主机的真实 MAC 地址,而是一台可以通往局域网外的路由器的 MAC 地址。于是此后发送主机发往目的主机的所有帧,都将发往该路由器,通过它向外发送。这种情况称为委托 ARP 或 ARP 代理ARP Proxy
还有一种免费 ARPgratuitous ARP它是指主机发送 ARP 查询(广播)自己的 IP 地址,当 ARP 功能被开启或者是端口初始配置完成,主机向网络发送免费 ARP 来查询自己的 IP 地址确认地址唯一可用。用来确定网络中是否有其他主机使用了 IP 地址,如果有应答则产生错误消息。免费 ARP 也可以做更新 ARP 缓存用,网络中的其他主机收到该广播则在缓存中更新条目,收到该广播的主机无论是否存在与 IP 地址相关的条目都会强制更新,如果存在旧条目则会将 MAC 更新为广播包中的 MAC。
## arp报文组成
1. 硬件类型(hard type) 硬件类型用来指代需要什么样的物理地址,如果硬件类型为 1表示以太网地址
2. 协议类型 协议类型则是需要映射的协议地址类型,如果协议类型是 0x0800表示 ipv4 协议。
3. 硬件地址长度 表示硬件地址的长度,单位字节,一般都是以太网地址的长度为 6 字节。
4. 协议地址长度: 表示协议地址的长度,单位字节,一般都是 ipv4 地址的长度为 4 字节。
5. 操作码 这些值用于区分具体操作类型,因为字段都相同,所以必须指明操作码,不然连请求还是应答都分不清。 1=>ARP 请求, 2=>ARP 应答3=>RARP 请求4=>RARP 应答。
6. 源硬件地址 源物理地址如02:f2:02:f2:02:f2
7. 源协议地址 源协议地址如192.168.0.1
8. 目标硬件地址 目标物理地址如03:f2:03:f2:03:f2
9. 目标协议地址。 目标协议地址,如 192.168.0.2
## ARP 高速缓存
知道了 ARP 发送的原理后,我们不禁疑惑,如果每次发之前都要发送 ARP 请求硬件地址会不会太慢,但是实际上 ARP 的运行是非常高效的。那是因为每一个主机上都有一个 ARP 高速缓存,我们可以在命令行键入 arp -a 获取本机 ARP 高速缓存的所有内容。

7
tcpip/network/arp/arp.go Normal file
View File

@@ -0,0 +1,7 @@
// 主机的链路层寻址是通过 arp 表来实现的
package arp
const (
ProtocolName = "arp"
ProtocolNumber = "arp"
)

View File

@@ -0,0 +1 @@
package arp_test

View File

@@ -345,9 +345,12 @@ func (n *NIC) getRef(protocol tcpip.NetworkProtocolNumber, dst tcpip.Address) *r
return nil return nil
} }
// 当 NIC 从物理接口接收数据包时,将调用函数 DeliverNetworkPacket用来分发网络层数据包。
// 比如 protocol 是 arp 协议号那么会找到arp.HandlePacket来处理数据报。
// 简单来说就是根据网络层协议和目的地址来找到相应的网络层端,将网络层数据发给它,
// 当前实现的网络层协议有 arp、ipv4 和 ipv6。
func (n *NIC) DeliverNetworkPacket(linkEP LinkEndpoint, remoteLinkAddr, localLinkAddr tcpip.LinkAddress, func (n *NIC) DeliverNetworkPacket(linkEP LinkEndpoint, remoteLinkAddr, localLinkAddr tcpip.LinkAddress,
protocol tcpip.NetworkProtocolNumber, vv buffer.VectorisedView) { protocol tcpip.NetworkProtocolNumber, vv buffer.VectorisedView) {
// TODO 需要完成逻辑
netProto, ok := n.stack.networkProtocols[protocol] netProto, ok := n.stack.networkProtocols[protocol]
if !ok { if !ok {
n.stack.stats.UnknownProtocolRcvdPackets.Increment() n.stack.stats.UnknownProtocolRcvdPackets.Increment()