专栏首页Linux内核那些事ARP协议与邻居子系统剖析

ARP协议与邻居子系统剖析

学习过 TCP/IP 协议的同学都应该了解过 TCP/IP 五层网络模型,如下图:

从上图可以看出,ARP协议 位于 TCP/IP 五层网络模型的 网络层。那么,ARP协议 的用途是什么呢?

ARP协议介绍

在局域网中(同一个路由器内),主机与主机之间需要通过 MAC 地址进行通讯。但由于 MAC 地址过于复杂,不容易被人类记忆。所以,人们更倾向于使用更容易记忆的 IP 地址。

但局域网只能使用 MAC 地址通讯,那有什么办法可以通过主机的 IP 地址来获取到主机的 MAC 地址呢?ARP协议 就应运而生。

ARP(Address Resolution Protocol) 即地址解析协议, 用于实现从 IP 地址到 MAC 地址的映射,即询问目标IP对应的MAC地址。

ARP协议 通过广播消息,向局域网的所有主机广播 ARP请求消息,从而询问主机的 IP 地址对应的 MAC 地址,如下图:

如上图所示,A主机要与B主机通信,但是只知道B主机的 IP 地址,所以这时可以向局域网广播一条 ARP请求消息,用于询问 IP 地址为 192.168.1.2 的主机所对应的 MAC 地址。

由于 ARP请求消息 是广播消息,所以局域网的所有主机都会收到这条消息,但只有对应 IP 地址的主机才会回答这条消息。如上图的B主机会回复一条 ARP应答消息,用于告诉A主机自己的 MAC 地址。

当A主机知道了B主机的 MAC 地址后,就能通过 MAC 地址与B主机进行通信了。

ARP协议头部

每种网络协议都有其协议头部,用于本协议的通信,ARP协议 的头部格式如下:

上图是 ARP协议 头部各个字段的信息,其代码结构定义如下(路径: /src/include/linux/if_arp.h):

struct arphdr
{
    unsigned short  ar_hrd;     /* format of hardware address   */
    unsigned short  ar_pro;     /* format of protocol address   */
    unsigned char   ar_hln;     /* length of hardware address   */
    unsigned char   ar_pln;     /* length of protocol address   */
    unsigned short  ar_op;      /* ARP opcode (command)         */

#if 0
   /* 下面部分没有定义, 因为不同的链路层协议使用的地址格式不一定相同,
    * 所以下面只是以太网和IP协议的示例而已.
    *  Ethernet looks like this : This bit is variable sized however...
    */
    unsigned char       ar_sha[ETH_ALEN];   /* sender hardware address  */
    unsigned char       ar_sip[4];          /* sender IP address        */
    unsigned char       ar_tha[ETH_ALEN];   /* target hardware address  */
    unsigned char       ar_tip[4];          /* target IP address        */
#endif
}

从代码可以看出,arphdr 结构的各个字段与上图一一对应。下面说说各个字段的作用:

  • ar_hrd:硬件类型,如硬件类型是以太网,那么设置为 1
  • ar_pro:协议类型,由于 ARP 协议支持将多种不同协议地址转换成 MAC 地址(如 IP 协议、AX.25 协议等),所以需要通过这个字段指明要转换的协议类型。如 IP 协议设置为 0x0800
  • ar_hln:硬件地址长度,如以太网地址长度是 6。
  • ar_hln:协议地址长度,如 IP 地址长度是 4。
  • ar_op:操作码,如 ARP请求 设置为 1,而 ARP应答 设置为 2。

下面的字段是不定长的,根据硬件类型和协议类型的改变而改变。

譬如:如果是硬件类型是以太网,并且协议类型是 IP 协议,那么 ar_sha 字段和 ar_tha 字段分别为 6 个字节,而 ar_sip 字段和 ar_tip 字段分别为 4 个字节。

邻居子系统

首先说明一下什么是 邻居:在同一局域网中,每一台主机都可以称为其他主机的 邻居。例如 Windows 系统可以在网络中查看到同一局域网的邻居主机,如下图:

如上图所示,每一台主机都可以称为 邻居节点

在 Linux 内核中,也把局域网中的每台主机称为 邻居节点,使用结构 neighbour 来描述,neighbour 结构定义如下:

struct neighbour
{
  struct neighbour    *next;      // 用于连接哈希表中相同哈希值的邻居节点
  struct neigh_table  *tbl;       // 管理邻居节点的邻居表结构
  struct neigh_parms  *parms;     // 参数列表
  struct net_device   *dev;       // 可以与这个邻居节点通信的设备
  unsigned long       used;       // 最后使用时间
  unsigned long       confirmed;  // 最后确认时间
  unsigned long       updated;    // 最后更新时间
  __u8                flags;      // 标志位
  __u8                nud_state;  // 邻居节点所处于的状态
  __u8                type;       // 类型
  __u8                dead;       // 是否已经失效
  atomic_t            probes;
  rwlock_t            lock;       // 锁
                                  // 邻居节点的硬件地址
  unsigned char       ha[(MAX_ADDR_LEN+sizeof(unsigned long)-1)&~(sizeof(unsigned long)-1)];
  struct hh_cache     *hh;
  atomic_t            refcnt;         // 引用计数器
  int                 (*output)(struct sk_buff *skb); // 发送数据给此邻居节点的接口
  struct sk_buff_head arp_queue;      // 等待ARP回复的数据包列表(需要发送的数据包列表)
  struct timer_list   timer;          // 定时器
  struct neigh_ops    *ops;           // 操作方法列表
  u8                  primary_key[0]; // 要转换成MAC地址的上层协议地址(如IP地址)
};

neighbour 结构中,比较重要的字段有:

  • ha:邻居节点的硬件地址,因为与邻居节点通信需要知道其硬件地址(MAC地址)。
  • output:向邻居节点发送数据的接口,当要向邻居节点发送数据时,使用这个接口把数据发送出去。
  • dev:输出设备,如果向当前邻居节点发送数据,需要通过这个设备来发送。
  • primary_key:要转换成 MAC 地址的上层协议地址(如 IP 地址),由于上层协议不确定,所以这里设置 primary_key柔性数组(可变大小数组)(不了解柔性数组可以查阅相关的资料)。

如下图所示:

所以,当本机需要向某一台 邻居节点 主机发送数据时,首先需要通过上层协议地址与输出设备查找对应的 neighbour 对象是否已经存在。如果存在,那么就使用这个 neighbour 对象。否则,就新创建一个 neighbour 对象,并初始化其各个字段。

查找邻居节点信息

要查找一个 邻居节点 的信息,可以通过调用 neigh_lookup() 函数来完成,其实现如下:

struct neighbour *
neigh_lookup(struct neigh_table *tbl, const void *pkey, struct net_device *dev)
{
  struct neighbour *n;
  u32 hash_val;
  int key_len = tbl->key_len;

  hash_val = tbl->hash(pkey, dev); // 通过IP地址与设备计算邻居节点信息的哈希值

  read_lock_bh(&tbl->lock);

  // 通过设备和IP地址从邻居节点哈希表中查找邻居节点信息
  for (n = tbl->hash_buckets[hash_val]; n; n = n->next) {
    if (dev == n->dev && memcmp(n->primary_key, pkey, key_len) == 0) {
      neigh_hold(n);
      break;
    }
  }

  read_unlock_bh(&tbl->lock);

  return n; // 返回邻居节点信息对象
}

neigh_lookup() 函数的参数含义如下:

  • tbl:邻居节点管理表。
  • pkey:上层协议地址(如 IP 地址)。
  • dev:输出设备对象。

neigh_lookup() 函数工作原理如下:

  • 首先通过上层协议地址与设备计算邻居节点信息的哈希值。
  • 然后在邻居节点哈希表中查找对应的邻居节点信息,如果找到即返回邻居节点信息,否则返回NULL。

创建邻居节点信息

邻居节点 信息不存在时,可以通过调用 neigh_create() 函数来创建,其实现如下:

struct neighbour *
neigh_create(struct neigh_table *tbl, const void *pkey, struct net_device *dev)
{
    struct neighbour *n, *n1;
    u32 hash_val;
    int key_len = tbl->key_len;
    int error;

    n = neigh_alloc(tbl); // 创建一个新的邻居节点信息对象
    if (n == NULL)
        return ERR_PTR(-ENOBUFS);

    memcpy(n->primary_key, pkey, key_len); // 复制上层协议地址到 primary_key 字段

    n->dev = dev; // 绑定输出设备
    dev_hold(dev);

    // 对于ARP协议会调用 arp_constructor() 函数
    if (tbl->constructor && (error = tbl->constructor(n)) < 0) {
        neigh_release(n);
        return ERR_PTR(error);
    }
    ...
    hash_val = tbl->hash(pkey, dev);

    write_lock_bh(&tbl->lock);
    ...
    // 把邻居节点信息添加到邻居节点信息管理哈希表中
    n->next = tbl->hash_buckets[hash_val];
    tbl->hash_buckets[hash_val] = n;
    n->dead = 0;
    neigh_hold(n);

    write_unlock_bh(&tbl->lock);

    return n;
}

neigh_create() 函数的工作原理如下:

  • 调用 neigh_alloc() 函数向内存申请一个新的邻居节点信息对象。
  • 把上层协议地址(如 IP 地址)复制到 primary_key 字段中。
  • 绑定输出设备到 dev 字段。
  • 调用邻居节点信息管理哈希表的 constructor() 方法来初始化邻居节点信息对象,对于 ARP协议 这里调用的是 arp_constructor() 函数。
  • 把新创建的邻居节点信息对象添加到邻居节点信息管理哈希表中。

对于 arp_constructor() 函数,主要是用于初始化邻居节点信息对象的操作方法列表和 output 字段,如下:

static struct neigh_ops arp_generic_ops = {
    AF_INET,                  // family
    NULL,                     // destructor
    arp_solicit,              // solicit
    arp_error_report,         // error_report
    neigh_resolve_output,     // output
    neigh_connected_output,   // connected_output
    dev_queue_xmit,           // hh_output
    dev_queue_xmit            // queue_xmit
};

static int arp_constructor(struct neighbour *neigh)
{
    u32 addr = *(u32*)neigh->primary_key;
    struct net_device *dev = neigh->dev;
    ...
    neigh->type = inet_addr_type(addr);
    ...
    if (dev->hard_header_cache)
        neigh->ops = &arp_hh_ops;
    else
        neigh->ops = &arp_generic_ops;

    if (neigh->nud_state & NUD_VALID)
        neigh->output = neigh->ops->connected_output;
    else
        neigh->output = neigh->ops->output;

    return 0;
}

arp_constructor() 函数的实现可以知道,邻居节点信息对象的ops 字段被设置为 arp_generic_ops,而 output 字段会被设置为 neigh_resolve_output() 函数(当邻居节点信息对象不可用时)或者 neigh_connected_output() 函数(当邻居节点信息对象可用时)。

所以,一个新创建的 邻居节点信息对象 各个字段的值大概如下图所示:

由于此时还不知道邻居节点的 MAC 地址,所以其 ha 字段的值为 0。

向邻居节点发送数据

当向邻居节点发送数据时,需要调用邻居节点信息对象的 output 接口。根据前面的分析,output 接口被设置为 neigh_resolve_output() 函数。我们来分析一下 neigh_resolve_output() 函数的实现:

int neigh_resolve_output(struct sk_buff *skb)
{
    struct dst_entry *dst = skb->dst;
    struct neighbour *neigh;
    ...
    if (neigh_event_send(neigh, skb) == 0) {
        int err;
        struct net_device *dev = neigh->dev;

        if (dev->hard_header_cache && dst->hh == NULL) {
            ...
        } else {
            read_lock_bh(&neigh->lock);
            err = dev->hard_header(skb, dev, ntohs(skb->protocol), 
                                   neigh->ha, NULL, skb->len);
            read_unlock_bh(&neigh->lock);
        }
        if (err >= 0)
            return neigh->ops->queue_xmit(skb);
        kfree_skb(skb);
        return -EINVAL;
    }
    return 0;
    ...
}

neigh_resolve_output() 函数主要完成三件事:

  • 调用 neigh_event_send() 函数发送一个查询邻居节点 MAC 地址的 ARP 请求。
  • 调用设备的 hard_header() 方法设置数据包的目标 MAC 地址(如果邻居节点的 MAC 地址已经获取到)。
  • 如果数据包的目标 MAC 地址设置成功,调用邻居节点信息对象的 queue_xmit() 方法把数据发送出去(对于 ARP 协议,queue_xmit() 方法对应的是 dev_queue_xmit() 函数)。

我们再来看看 neigh_event_send() 函数怎么发送 ARP 请求:

int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{
    write_lock_bh(&neigh->lock);
    if (!(neigh->nud_state & (NUD_CONNECTED|NUD_DELAY|NUD_PROBE))) {
        if (!(neigh->nud_state & (NUD_STALE|NUD_INCOMPLETE))) {
            if (neigh->parms->mcast_probes + neigh->parms->app_probes) {
                ...
                neigh->nud_state = NUD_INCOMPLETE;
                ...
                neigh->ops->solicit(neigh, skb); // 发送查询邻居节点MAC地址的ARP请求
                ...
            } else {
                ...
            }
        }

        if (neigh->nud_state == NUD_INCOMPLETE) {
            if (skb) {
                ...
                __skb_queue_head(&neigh->arp_queue, skb); // 把数据包添加到arp_queue队列中
            }
            write_unlock_bh(&neigh->lock);
            return 1;
        }
        ...
    }
    write_unlock_bh(&neigh->lock);
    return 0;
}

__neigh_event_send() 函数主要完成两个工作:

  • 首先调用邻居节点信息对象的 solicit() 方法发送一个 ARP 请求,从前面的分析可知,solicit() 方法会被设置为 arp_solicit() 函数。
  • 然后把要发送的数据包添加到邻居节点信息对象的 arp_queue 队列中,等待获取到邻居节点 MAC 地址后重新发送这个数据包。

邻居节点信息对象的 arp_queue 队列用于缓存等待发送的数据包,如下图:

发送 ARP 请求

通过前面的分析可知,当向邻居节点发送数据时,如果还不知道邻居节点的 MAC 地址,那么首先会调用 arp_solicit() 函数发送一个 ARP请求 来获取邻居节点的 MAC 地址,其实现如下:

static void arp_solicit(struct neighbour *neigh, struct sk_buff *skb)
{
    u32 saddr;                               // 源IP地址(本地IP地址)
    u8 *dst_ha = NULL;                       // 接收ARP请求目标MAC地址(发广播信息设置为NULL)
    struct net_device *dev = neigh->dev;     // 输出设备
    u32 target = *(u32*)neigh->primary_key;  // 要查询的邻居节点IP地址
    ...

    if (skb && inet_addr_type(skb->nh.iph->saddr) == RTN_LOCAL)
        saddr = skb->nh.iph->saddr;
    else
        saddr = inet_select_addr(dev, target, RT_SCOPE_LINK);
    ...

    arp_send(ARPOP_REQUEST, ETH_P_ARP, target, dev, saddr,
             dst_ha, dev->dev_addr, NULL);
    ...
}

arp_solicit() 函数最终会调用 arp_send() 函数发送一个 ARP 请求,我们来分析一下 arp_send() 函数的实现:

void arp_send(int type, int ptype, u32 dest_ip,
              struct net_device *dev, u32 src_ip,
              unsigned char *dest_hw, unsigned char *src_hw,
              unsigned char *target_hw)
{
    struct sk_buff *skb;
    struct arphdr *arp;
    unsigned char *arp_ptr;
    ...
    // 申请一个数据包对象
    skb = alloc_skb(sizeof(struct arphdr) + 2*(dev->addr_len+4)
                        + dev->hard_header_len + 15, GFP_ATOMIC);
    ...

    // ARP请求头部
    arp = (struct arphdr *)skb_put(skb, sizeof(struct arphdr) + 2*(dev->addr_len+4));

    skb->dev = dev;
    skb->protocol = __constant_htons(ETH_P_ARP);

    if (src_hw == NULL)
        src_hw = dev->dev_addr;
    if (dest_hw == NULL)
        dest_hw = dev->broadcast;

    // 下面设置ARP头部的各个字段信息
    ...
    switch (dev->type) {
    default:
        arp->ar_hrd = htons(dev->type);            // 设置硬件类型
        arp->ar_pro = __constant_htons(ETH_P_IP);  // 设置上层协议类型为IP协议
        break;
    ...
    }

    arp->ar_hln = dev->addr_len;            // 设置硬件地址长度
    arp->ar_pln = 4;                        // 设置上层协议地址长度
    arp->ar_op = htons(type);               // ARP请求操作码类型

    arp_ptr = (unsigned char *)(arp + 1);

    memcpy(arp_ptr, src_hw, dev->addr_len); // 复制源MAC地址(本机MAC地址)

    arp_ptr += dev->addr_len;
    memcpy(arp_ptr, &src_ip,4);             // 复制源IP地址(本机IP地址)

    // 复制目标MAC地址(对于查询请求设置为0)
    arp_ptr += 4;
    if (target_hw != NULL)
        memcpy(arp_ptr, target_hw, dev->addr_len);
    else
        memset(arp_ptr, 0, dev->addr_len);

    // 复制目标IP地址
    arp_ptr += dev->addr_len;
    memcpy(arp_ptr, &dest_ip, 4);
    ...
    dev_queue_xmit(skb); // 把数据发送出去
    return;
}

arp_send() 函数的工作也比较清晰:

  • 首先调用 alloc_skb() 函数申请一个数据包对象。
  • 然后设置 ARP 头部各个字段的信息。
  • 调用 dev_queue_xmit() 函数把数据发送出去。

处理 ARP 回复

当邻居节点回复 MAC 地址查询 ARP 请求时,本机需要处理此 ARP 回复。本机通过 arp_rcv() 函数来处理 ARP 回复,代码如下:

int arp_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
    struct arphdr *arp = skb->nh.arph;
    unsigned char *arp_ptr = (unsigned char *)(arp+1);
    struct rtable *rt;
    unsigned char *sha, *tha;
    u32 sip, tip;
    u16 dev_type = dev->type;
    int addr_type;
    struct in_device *in_dev = in_dev_get(dev);
    struct neighbour *n;
    ...
    // 从ARP回复中获取数据
    sha = arp_ptr;             // 源MAC地址(邻居节点的MAC地址)
    arp_ptr += dev->addr_len;
    memcpy(&sip, arp_ptr, 4);  // 源IP地址(邻居节点的IP地址)

    arp_ptr += 4;
    tha = arp_ptr;             // 目标MAC地址(本机的MAC地址)

    arp_ptr += dev->addr_len;
    memcpy(&tip, arp_ptr, 4);  // 目标IP地址(本机的IP地址)
    ...

    n = __neigh_lookup(&arp_tbl, &sip, dev, 0); // 查找邻居节点信息对象
    if (n) {
        int state = NUD_REACHABLE;
        int override = 0;

        if (jiffies - n->updated >= n->parms->locktime)
            override = 1;

        if (arp->ar_op != __constant_htons(ARPOP_REPLY) 
            || skb->pkt_type != PACKET_HOST)
            state = NUD_STALE;

        neigh_update(n, sha, state, override, 1); // 更新邻居节点信息对象
        neigh_release(n);
    }
    ...
    return 0;
}

arp_rcv() 函数主要完成以下工作:

  • 通过从 ARP 回复中获取到邻居节点的 MAC 地址和 IP 地址。
  • 通过邻居节点的 IP 地址和输入设备来查找对应的邻居节点信息对象。
  • 如果邻居节点信息对象已经存在,那么调用 neigh_update() 函数更新邻居节点信息对象的数据。

我们来看看 neigh_update() 函数怎么更新邻居节点信息对象的数据:

int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, int override, int arp)
{
    u8 old;
    int err;
    int notify = 0;
    struct net_device *dev = neigh->dev;
    ...
    old = neigh->nud_state;    // 更新前邻居节点信息对象的状态
    ...
    neigh->nud_state = new;    // 更新邻居节点信息对象的状态
    if (lladdr != neigh->ha) { // 更新邻居节点信息对象的MAC地址
        memcpy(&neigh->ha, lladdr, dev->addr_len);
        ...
    }
    ...
    if (!(old&NUD_VALID)) {
        struct sk_buff *skb;

        // 如果 arp_queue 队列中有等待发送的数据包, 现在可以把这些数据包发送出去
        while (neigh->nud_state & NUD_VALID &&
               (skb = __skb_dequeue(&neigh->arp_queue)) != NULL) 
        {
            struct neighbour *n1 = neigh;
            write_unlock_bh(&neigh->lock);
            ...
            n1->output(skb);
            write_lock_bh(&neigh->lock);
        }
        skb_queue_purge(&neigh->arp_queue);
    }
    ...
    return err;
}

neigh_update() 函数主要完成以下工作:

  • 更新邻居节点信息对象的 ha 字段(也就是 MAC 地址)为 ARP 回复中获得的邻居节点 MAC 地址。
  • 如果邻居节点对象的 arp_queue 队列不为空,说明有等待发送的数据包,此时调用邻居节点信息的 output() 接口把这些数据发送出去。从上下文可知,此时的 output() 还是为 neigh_resolve_output() 函数。但由于邻居节点的 MAC 地址已经获得,所以不会再发送 ARP 请求。而是调用设备的 hard_header() 接口设置数据包的目标 MAC 地址,然后 调用 dev_queue_xmit() 函数把数据包发送出去。

本文分享自微信公众号 - Linux内核那些事(like_linux),作者:songsong001

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-02-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 漫画说 “虚拟IP”

    由于静态绑定比较麻烦,所以一般使用 ARP协议 来获取(关于ARP协议的原理可以参考网上的资料或者书籍)。

    用户7686797
  • 4.ARP_RIP_OSPF_BGP

    见贤思齊
  • 路由协议

    那个口也不绑定,它所提供的对应关系是 IP 地址和MAC地址的对应关系。如果硬是要说他是哪个口,比如PC 上的网卡,那他就是绑定在这个口上。当然,如果有多网卡...

    见贤思齊
  • Linux内核网络UDP数据包发送(三)——IP协议层分析

    Linux内核网络 UDP 协议层通过调用 ip_send_skb 将 skb 交给 IP 协议层,本文通过分析内核 IP 协议层的关键函数来分享内核数据包发送...

    Linux阅码场
  • 彻底搞懂系列之:ARP协议

    地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP...

    用户8611941
  • 浏览器输入域名网址访问后的过程详解

    1、客户端浏览器通过DNS解析到https://www.fgba.net/,IP地址是202.108.22.5,通过这个IP地址找到客户端到服务器的路径。客户端...

    曾高飞
  • 闲谈IPv6-Loopback网口上的IPv6地址

    早年,我也写过关于Loopback的两篇文章: 用IP地址的用途理解Loopback接口: https://blog.csdn.net/dog250/arti...

    望天
  • IPv6排障工具之ping6完整过程细节剖析

    导语 | 关于ping的原理详解,网上搜索一下可以搜索出很多相关内容,而ping6的详解,我暂时还没有看见高质量的文章。希望本文能够让更多朋友了解ping6的原...

    binwenli
  • openstack网络设计-(二)underlay网络

    惠伟:openstack网络设计-(一)试探​zhuanlan.zhihu.com

    惠伟
  • H3C ARP概述

        ARP( Address Resolution Protocol,地址解析协议)是将 IP 地址解析为以太网 MAC 地址(或称物理地址)的协议。

    py3study
  • IPv6系列-初学者的10个常见困扰

    本文是《IPv6系列》文章的第二篇《常见困扰》,紧接《入门指南》,用于解答IPv6的10个常见困扰。

    小慢哥Linux运维
  • 透过MH370看网络自动化监控

    引子 MH370已经消失4个月了,这个严重的灾难也被蒙上了神秘的面纱,找不到消失的任何记录。也意味着,马航无法确保这种事情不会再次发生,人类生活在恐惧...

    鹅厂网事
  • 干货 | 思科网络工程师的网络排错笔记 —— 收藏必备!

    一般网络包括路由、拨号、交换、视频、WAN(ISDN、帧中继、ATM、…)、LAN、VLAN、…

    网络技术联盟站
  • OSPF基本工作原理(上)

    Link State(链路状态)指的是路由器的接口状态,在ospf中路由器的某一接口的链路状态包含了

    冬夜先生
  • linux之arp和arping命令

    一般会使用arp协议获取局域网内的主机MAC,所以局域网主机之间也互称为网络邻居。

    陈不成i
  • 网工知识大扫盲——三层交换技术

    今天给大家带来的是交换技术,主要是三层方向的,文中提到的示例都以锐捷设备为例,很适合大家查漏补缺,以下是目录:

    网络技术联盟站
  • 计算机网络之网络层1 IP地址与MAC地址的关系2 ARP协议的作用,地址解析的过程3 IP地址的分类,每类地址的范围,给定IP地址能够判断属于哪一类4 IP数据报分片IP编址有类IP地址5 IP子网

    JavaEdge
  • 计算机网络·感觉Java研发计算机网络只要会者几道就够了其他很少考

    【运维方向优先】a. 请描述TCP协议3次握手建立连接的过程。b. 为什么协议设计是3次握手连接建立而不是2次或4次,如果2次有什么问题,如果4次有什么问题?

    陈黎栋
  • 云数据中心网络虚拟化——大二层技术巡礼之NVo3技术DC间隧道

    NVo3体系框架只是要求隧道构建在IP网络上,并没有要求一定是要端到端的,因此DC间跨越Internet进行互联的一些技术也属于NVo3框架中。这类技术往往部署...

    SDNLAB

扫码关注云+社区

领取腾讯云代金券