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

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

本文转载自微信公众号「 编程杂技」,作者theanarkh 。转载本文请联系 编程杂技公众号。

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

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

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

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 */ 
}; 

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

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 的定义

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) */ 
}; 
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。

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的定义

static struct packet_type rarp_packet_type = 
{ 
    0,  
    0,                /* copy */ 
    rarp_rcv, 
    NULL, 
    NULL 
}; 

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

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函数发送回包。下面列一下该函数的代码。

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的早期实现。

相关推荐