通过源码理解rarp协议(基于linux1.2.13)

2020-09-21 15:40:56 浏览数 (1)

rarp是通过mac地址查询ip的协议,主要用于有mac的主机,但是没有ip的情况。我们先看看rarp协议的协议定义(来自网上的图[1])。

rarp协议的格式和arp协议是一样的,他们都是通过一种地址查询另外一种地址。操作系统内维护了一个转换表。定义如下。

代码语言:javascript复制
struct rarp_table
{
    struct rarp_table  *next;             /* Linked entry list           */
    unsigned long      ip;                /* ip address of entry         */
    unsigned char      ha[MAX_ADDR_LEN];  /* Hardware address            */
    unsigned char      hlen;              /* Length of hardware address  */
    unsigned char      htype;             /* Type of hardware in use     */
    struct device      *dev;              /* Device the entry is tied to */
};

初始化的时候是空的,这个表格的数据来源于,用户通过操作系统提供的接口设置。我们看如何操作这个表。

代码语言:javascript复制
int rarp_ioctl(unsigned int cmd, void *arg)
{
    struct arpreq r;
    struct sockaddr_in *si;
    int err;

    switch(cmd)
    {
        case SIOCDRARP:
            if (!suser())
                return -EPERM;
            err = verify_area(VERIFY_READ, arg, sizeof(struct arpreq));
            if(err)
                return err;
            memcpy_fromfs(&r, arg, sizeof(r));
            if (r.arp_pa.sa_family != AF_INET)
                return -EPFNOSUPPORT;
            si = (struct sockaddr_in *) &r.arp_pa;
            rarp_destroy(si->sin_addr.s_addr);
            return 0;

        case SIOCGRARP:
            err = verify_area(VERIFY_WRITE, arg, sizeof(struct arpreq));
            if(err)
                return err;
            return rarp_req_get((struct arpreq *)arg);
        case SIOCSRARP:
            if (!suser())
                return -EPERM;
            err = verify_area(VERIFY_READ, arg, sizeof(struct arpreq));
            if(err)
                return err;
            return rarp_req_set((struct arpreq *)arg);
        default:
            return -EINVAL;
    }

    /*NOTREACHED*/
    return 0;
}

通过ioctl函数,我们可以对表格进行增删改查。我们只关注新增的逻辑。因为其他的是类似的。下面是arpreq 的定义

代码语言:javascript复制
struct arpreq {
  struct sockaddr    arp_pa;        /* protocol address        */
  struct sockaddr    arp_ha;        /* hardware address        */
  int            arp_flags;    /* flags            */
  struct sockaddr       arp_netmask;    /* netmask (only for proxy arps) */
};
代码语言:javascript复制
static int rarp_req_set(struct arpreq *req)
{
    struct arpreq r;
    struct rarp_table *entry;
    struct sockaddr_in *si;
    int htype, hlen;
    unsigned long ip;
    struct rtable *rt;

    memcpy_fromfs(&r, req, sizeof(r));

    /*
     *    We only understand about IP addresses... 
     */

    if (r.arp_pa.sa_family != AF_INET)
        return -EPFNOSUPPORT;

    switch (r.arp_ha.sa_family) 
    {
        case ARPHRD_ETHER:
            htype = ARPHRD_ETHER;
            hlen = ETH_ALEN;
            break;
        default:
            return -EPFNOSUPPORT;
    }

    si = (struct sockaddr_in *) &r.arp_pa;
    ip = si->sin_addr.s_addr;
    if (ip == 0)
    {
        printk("RARP: SETRARP: requested PA is 0.0.0.0 !n");
        return -EINVAL;
    }
    // 
    rt = ip_rt_route(ip, NULL, NULL);
    if (rt == NULL)
        return -ENETUNREACH;

/*
 *    Is there an existing entry for this address?  Find out...
 */

    cli();
    // 判断之前是不是已经存在
    for (entry = rarp_tables; entry != NULL; entry = entry->next)
        if (entry->ip == ip)
            break;

/*
 *    If no entry was found, create a new one.
 */
    // 不存在则创建一个表项
    if (entry == NULL)
    {
        entry = (struct rarp_table *) kmalloc(sizeof(struct rarp_table),
                    GFP_ATOMIC);
        // 还没初始化则初始化
        if(initflag)
        {
            rarp_init();
            initflag=0;
        }

        entry->next = rarp_tables;
        rarp_tables = entry;
    }

    entry->ip = ip;
    entry->hlen = hlen;
    entry->htype = htype;
    memcpy(&entry->ha, &r.arp_ha.sa_data, hlen);
    entry->dev = rt->rt_dev;

    sti();

    return 0;
}

我们看到这里会往表里插入一个表项(如果不存在的话),还有另外一个逻辑是rarp_init。

代码语言:javascript复制
static void rarp_init (void)
{
    /* Register the packet type */
    rarp_packet_type.type=htons(ETH_P_RARP);
    dev_add_pack(&rarp_packet_type);
}

这个函数是往底层注册一个节点,当mac底层收到一个ETH_P_RARP类型的数据包的时候(在mac协议头里定义),就会执行rarp_packet_type中定义的函数。下面是该rarp_packet_type的定义

代码语言:javascript复制
static struct packet_type rarp_packet_type =
{
    0, 
    0,                /* copy */
    rarp_rcv,
    NULL,
    NULL
};

rarp_rcv函数就是收到一个rarp请求的时候(来自其他主机),执行的函数。

代码语言:javascript复制
int rarp_rcv(struct sk_buff *skb, struct device *dev, struct packet_type *pt)
{
/*
 *    We shouldn't use this type conversion. Check later.
 */
    // rarp协议报文
    struct arphdr *rarp = (struct arphdr *)skb->h.raw;
    // rarp协议数据部分
    unsigned char *rarp_ptr = (unsigned char *)(rarp 1);
    struct rarp_table *entry;
    long sip,tip;
    unsigned char *sha,*tha;            /* s for "source", t for "target" */

    // 硬件地址长度或类型不一致则忽略
    if (rarp->ar_hln != dev->addr_len || dev->type != ntohs(rarp->ar_hrd) 
        || dev->flags&IFF_NOARP)
    {
        kfree_skb(skb, FREE_READ);
        return 0;
    }

    /*
     *    If it's not a RARP request, delete it.
     */
    // 不是请求报文则忽略
    if (rarp->ar_op != htons(ARPOP_RREQUEST))
    {
        kfree_skb(skb, FREE_READ);
        return 0;
    }
    /*
     *    Extract variable width fields
     */
    // rarp协议首地址
    sha=rarp_ptr;
    // 发送端mac地址长度
    rarp_ptr =dev->addr_len;
    // 拿到发送端ip,存到sip
    memcpy(&sip,rarp_ptr,4);
    // 跳过4字节
    rarp_ptr =4;
    // 目的mac地址
    tha=rarp_ptr;
    // 跳过mac地址长度
    rarp_ptr =dev->addr_len;
    // 目的ip地址
    memcpy(&tip,rarp_ptr,4);

    /*
     *    Process entry. Use tha for table lookup according to RFC903.
     */

    cli();
    for (entry = rarp_tables; entry != NULL; entry = entry->next)
        // 判断mac地址是否相等
        if (!memcmp(entry->ha, tha, rarp->ar_hln))
            break;
    // 非空则说明找到
    if (entry != NULL)
    {    // 拿到对应的ip
        sip=entry->ip;
        sti();
        // 回复,类似是响应ARPOP_RREPLY
        arp_send(ARPOP_RREPLY, ETH_P_RARP, sip, dev, dev->pa_addr, sha, 
            dev->dev_addr);
    }
    else
        sti();

    kfree_skb(skb, FREE_READ);
    return 0;
}

我们看到这个函数很长,不过逻辑比较简单,就是解析收到的rarp请求中的数据,然后根据其他主机请求的mac地址,从维护的表格中找到对应的ip(如果有的话),然后调用arp_send函数发送回包。下面列一下该函数的代码。

代码语言:javascript复制
void arp_send(int type, int ptype, unsigned long dest_ip, 
          struct device *dev, unsigned long src_ip, 
          unsigned char *dest_hw, unsigned char *src_hw)
{
    struct sk_buff *skb;
    struct arphdr *arp;
    unsigned char *arp_ptr;

    /*
     *    No arp on this interface.
     */

    if(dev->flags&IFF_NOARP)
        return;

    /*
     *    Allocate a buffer
     */
    // 分配一个skb存储数据包
    skb = alloc_skb(sizeof(struct arphdr)  2*(dev->addr_len 4)
                  dev->hard_header_len, GFP_ATOMIC);
    // 构造arp协议数据包
    skb->len = sizeof(struct arphdr)   dev->hard_header_len   2*(dev->addr_len 4);
    skb->arp = 1;
    skb->dev = dev;
    // 不存在缓存,发完可以销毁
    skb->free = 1;
    // 构造mac头
    dev->hard_header(skb->data,dev,ptype,dest_hw?dest_hw:dev->broadcast,src_hw?src_hw:NULL,skb->len,skb);

    /* Fill out the arp protocol part. */
    arp = (struct arphdr *) (skb->data   dev->hard_header_len);
    arp->ar_hrd = htons(dev->type);
    arp->ar_pro = htons(ETH_P_IP);
    arp->ar_hln = dev->addr_len;
    arp->ar_pln = 4;
    arp->ar_op = htons(type);
    arp_ptr=(unsigned char *)(arp 1);
    memcpy(arp_ptr, src_hw, dev->addr_len);
    arp_ptr =dev->addr_len;
    memcpy(arp_ptr, &src_ip,4);
    arp_ptr =4;
    if (dest_hw != NULL)
        memcpy(arp_ptr, dest_hw, dev->addr_len);
    else
        memset(arp_ptr, 0, dev->addr_len);
    arp_ptr =dev->addr_len;
    memcpy(arp_ptr, &dest_ip, 4);
    // 调用mac头发送函数发送出去
    dev_queue_xmit(skb, dev, 0);
}

这就是rarp的早期实现。

References

[1] 网上的图: https://wenku.baidu.com/view/8fbb89a7f524ccbff12184a0.html#

0 人点赞