Skip to content
Snippets Groups Projects
arp.c 34.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    	if (r->arp_flags & ATF_PERM)
    		r->arp_flags |= ATF_COM;
    	if (dev == NULL) {
    		struct flowi fl = { .nl_u = { .ip4_u = { .daddr = ip,
    							 .tos = RTO_ONLINK } } };
    		struct rtable * rt;
    		if ((err = ip_route_output_key(&rt, &fl)) != 0)
    			return err;
    		dev = rt->u.dst.dev;
    		ip_rt_put(rt);
    		if (!dev)
    			return -EINVAL;
    	}
    	switch (dev->type) {
    #ifdef CONFIG_FDDI
    	case ARPHRD_FDDI:
    		/*
    		 * According to RFC 1390, FDDI devices should accept ARP
    		 * hardware types of 1 (Ethernet).  However, to be more
    		 * robust, we'll accept hardware types of either 1 (Ethernet)
    		 * or 6 (IEEE 802.2).
    		 */
    		if (r->arp_ha.sa_family != ARPHRD_FDDI &&
    		    r->arp_ha.sa_family != ARPHRD_ETHER &&
    		    r->arp_ha.sa_family != ARPHRD_IEEE802)
    			return -EINVAL;
    		break;
    #endif
    	default:
    		if (r->arp_ha.sa_family != dev->type)
    			return -EINVAL;
    		break;
    	}
    
    	neigh = __neigh_lookup_errno(&arp_tbl, &ip, dev);
    	err = PTR_ERR(neigh);
    	if (!IS_ERR(neigh)) {
    		unsigned state = NUD_STALE;
    		if (r->arp_flags & ATF_PERM)
    			state = NUD_PERMANENT;
    		err = neigh_update(neigh, (r->arp_flags&ATF_COM) ?
    				   r->arp_ha.sa_data : NULL, state, 
    				   NEIGH_UPDATE_F_OVERRIDE|
    				   NEIGH_UPDATE_F_ADMIN);
    		neigh_release(neigh);
    	}
    	return err;
    }
    
    static unsigned arp_state_to_flags(struct neighbour *neigh)
    {
    	unsigned flags = 0;
    	if (neigh->nud_state&NUD_PERMANENT)
    		flags = ATF_PERM|ATF_COM;
    	else if (neigh->nud_state&NUD_VALID)
    		flags = ATF_COM;
    	return flags;
    }
    
    /*
     *	Get an ARP cache entry.
     */
    
    static int arp_req_get(struct arpreq *r, struct net_device *dev)
    {
    	u32 ip = ((struct sockaddr_in *) &r->arp_pa)->sin_addr.s_addr;
    	struct neighbour *neigh;
    	int err = -ENXIO;
    
    	neigh = neigh_lookup(&arp_tbl, &ip, dev);
    	if (neigh) {
    		read_lock_bh(&neigh->lock);
    		memcpy(r->arp_ha.sa_data, neigh->ha, dev->addr_len);
    		r->arp_flags = arp_state_to_flags(neigh);
    		read_unlock_bh(&neigh->lock);
    		r->arp_ha.sa_family = dev->type;
    		strlcpy(r->arp_dev, dev->name, sizeof(r->arp_dev));
    		neigh_release(neigh);
    		err = 0;
    	}
    	return err;
    }
    
    static int arp_req_delete(struct arpreq *r, struct net_device * dev)
    {
    	int err;
    	u32 ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
    	struct neighbour *neigh;
    
    	if (r->arp_flags & ATF_PUBL) {
    		u32 mask =
    		       ((struct sockaddr_in *)&r->arp_netmask)->sin_addr.s_addr;
    		if (mask == 0xFFFFFFFF)
    			return pneigh_delete(&arp_tbl, &ip, dev);
    		if (mask == 0) {
    			if (dev == NULL) {
    				ipv4_devconf.proxy_arp = 0;
    				return 0;
    			}
    
    			if (__in_dev_get_rtnl(dev)) {
    				__in_dev_get_rtnl(dev)->cnf.proxy_arp = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				return 0;
    			}
    			return -ENXIO;
    		}
    		return -EINVAL;
    	}
    
    	if (dev == NULL) {
    		struct flowi fl = { .nl_u = { .ip4_u = { .daddr = ip,
    							 .tos = RTO_ONLINK } } };
    		struct rtable * rt;
    		if ((err = ip_route_output_key(&rt, &fl)) != 0)
    			return err;
    		dev = rt->u.dst.dev;
    		ip_rt_put(rt);
    		if (!dev)
    			return -EINVAL;
    	}
    	err = -ENXIO;
    	neigh = neigh_lookup(&arp_tbl, &ip, dev);
    	if (neigh) {
    		if (neigh->nud_state&~NUD_NOARP)
    			err = neigh_update(neigh, NULL, NUD_FAILED, 
    					   NEIGH_UPDATE_F_OVERRIDE|
    					   NEIGH_UPDATE_F_ADMIN);
    		neigh_release(neigh);
    	}
    	return err;
    }
    
    /*
     *	Handle an ARP layer I/O control request.
     */
    
    int arp_ioctl(unsigned int cmd, void __user *arg)
    {
    	int err;
    	struct arpreq r;
    	struct net_device *dev = NULL;
    
    	switch (cmd) {
    		case SIOCDARP:
    		case SIOCSARP:
    			if (!capable(CAP_NET_ADMIN))
    				return -EPERM;
    		case SIOCGARP:
    			err = copy_from_user(&r, arg, sizeof(struct arpreq));
    			if (err)
    				return -EFAULT;
    			break;
    		default:
    			return -EINVAL;
    	}
    
    	if (r.arp_pa.sa_family != AF_INET)
    		return -EPFNOSUPPORT;
    
    	if (!(r.arp_flags & ATF_PUBL) &&
    	    (r.arp_flags & (ATF_NETMASK|ATF_DONTPUB)))
    		return -EINVAL;
    	if (!(r.arp_flags & ATF_NETMASK))
    		((struct sockaddr_in *)&r.arp_netmask)->sin_addr.s_addr =
    							   htonl(0xFFFFFFFFUL);
    	rtnl_lock();
    	if (r.arp_dev[0]) {
    		err = -ENODEV;
    		if ((dev = __dev_get_by_name(r.arp_dev)) == NULL)
    			goto out;
    
    		/* Mmmm... It is wrong... ARPHRD_NETROM==0 */
    		if (!r.arp_ha.sa_family)
    			r.arp_ha.sa_family = dev->type;
    		err = -EINVAL;
    		if ((r.arp_flags & ATF_COM) && r.arp_ha.sa_family != dev->type)
    			goto out;
    	} else if (cmd == SIOCGARP) {
    		err = -ENODEV;
    		goto out;
    	}
    
    	switch(cmd) {
    	case SIOCDARP:
    	        err = arp_req_delete(&r, dev);
    		break;
    	case SIOCSARP:
    		err = arp_req_set(&r, dev);
    		break;
    	case SIOCGARP:
    		err = arp_req_get(&r, dev);
    		if (!err && copy_to_user(arg, &r, sizeof(r)))
    			err = -EFAULT;
    		break;
    	}
    out:
    	rtnl_unlock();
    	return err;
    }
    
    static int arp_netdev_event(struct notifier_block *this, unsigned long event, void *ptr)
    {
    	struct net_device *dev = ptr;
    
    	switch (event) {
    	case NETDEV_CHANGEADDR:
    		neigh_changeaddr(&arp_tbl, dev);
    		rt_cache_flush(0);
    		break;
    	default:
    		break;
    	}
    
    	return NOTIFY_DONE;
    }
    
    static struct notifier_block arp_netdev_notifier = {
    	.notifier_call = arp_netdev_event,
    };
    
    /* Note, that it is not on notifier chain.
       It is necessary, that this routine was called after route cache will be
       flushed.
     */
    void arp_ifdown(struct net_device *dev)
    {
    	neigh_ifdown(&arp_tbl, dev);
    }
    
    
    /*
     *	Called once on startup.
     */
    
    static struct packet_type arp_packet_type = {
    	.type =	__constant_htons(ETH_P_ARP),
    	.func =	arp_rcv,
    };
    
    static int arp_proc_init(void);
    
    void __init arp_init(void)
    {
    	neigh_table_init(&arp_tbl);
    
    	dev_add_pack(&arp_packet_type);
    	arp_proc_init();
    #ifdef CONFIG_SYSCTL
    	neigh_sysctl_register(NULL, &arp_tbl.parms, NET_IPV4,
    			      NET_IPV4_NEIGH, "ipv4", NULL, NULL);
    #endif
    	register_netdevice_notifier(&arp_netdev_notifier);
    }
    
    #ifdef CONFIG_PROC_FS
    #if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
    
    /* ------------------------------------------------------------------------ */
    /*
     *	ax25 -> ASCII conversion
     */
    static char *ax2asc2(ax25_address *a, char *buf)
    {
    	char c, *s;
    	int n;
    
    	for (n = 0, s = buf; n < 6; n++) {
    		c = (a->ax25_call[n] >> 1) & 0x7F;
    
    		if (c != ' ') *s++ = c;
    	}
    	
    	*s++ = '-';
    
    	if ((n = ((a->ax25_call[6] >> 1) & 0x0F)) > 9) {
    		*s++ = '1';
    		n -= 10;
    	}
    	
    	*s++ = n + '0';
    	*s++ = '\0';
    
    	if (*buf == '\0' || *buf == '-')
    	   return "*";
    
    	return buf;
    
    }
    #endif /* CONFIG_AX25 */
    
    #define HBUFFERLEN 30
    
    static void arp_format_neigh_entry(struct seq_file *seq,
    				   struct neighbour *n)
    {
    	char hbuffer[HBUFFERLEN];
    	const char hexbuf[] = "0123456789ABCDEF";
    	int k, j;
    	char tbuf[16];
    	struct net_device *dev = n->dev;
    	int hatype = dev->type;
    
    	read_lock(&n->lock);
    	/* Convert hardware address to XX:XX:XX:XX ... form. */
    #if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
    	if (hatype == ARPHRD_AX25 || hatype == ARPHRD_NETROM)
    		ax2asc2((ax25_address *)n->ha, hbuffer);
    	else {
    #endif
    	for (k = 0, j = 0; k < HBUFFERLEN - 3 && j < dev->addr_len; j++) {
    		hbuffer[k++] = hexbuf[(n->ha[j] >> 4) & 15];
    		hbuffer[k++] = hexbuf[n->ha[j] & 15];
    		hbuffer[k++] = ':';
    	}
    	hbuffer[--k] = 0;
    #if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
    	}
    #endif
    	sprintf(tbuf, "%u.%u.%u.%u", NIPQUAD(*(u32*)n->primary_key));
    	seq_printf(seq, "%-16s 0x%-10x0x%-10x%s     *        %s\n",
    		   tbuf, hatype, arp_state_to_flags(n), hbuffer, dev->name);
    	read_unlock(&n->lock);
    }
    
    static void arp_format_pneigh_entry(struct seq_file *seq,
    				    struct pneigh_entry *n)
    {
    	struct net_device *dev = n->dev;
    	int hatype = dev ? dev->type : 0;
    	char tbuf[16];
    
    	sprintf(tbuf, "%u.%u.%u.%u", NIPQUAD(*(u32*)n->key));
    	seq_printf(seq, "%-16s 0x%-10x0x%-10x%s     *        %s\n",
    		   tbuf, hatype, ATF_PUBL | ATF_PERM, "00:00:00:00:00:00",
    		   dev ? dev->name : "*");
    }
    
    static int arp_seq_show(struct seq_file *seq, void *v)
    {
    	if (v == SEQ_START_TOKEN) {
    		seq_puts(seq, "IP address       HW type     Flags       "
    			      "HW address            Mask     Device\n");
    	} else {
    		struct neigh_seq_state *state = seq->private;
    
    		if (state->flags & NEIGH_SEQ_IS_PNEIGH)
    			arp_format_pneigh_entry(seq, v);
    		else
    			arp_format_neigh_entry(seq, v);
    	}
    
    	return 0;
    }
    
    static void *arp_seq_start(struct seq_file *seq, loff_t *pos)
    {
    	/* Don't want to confuse "arp -a" w/ magic entries,
    	 * so we tell the generic iterator to skip NUD_NOARP.
    	 */
    	return neigh_seq_start(seq, pos, &arp_tbl, NEIGH_SEQ_SKIP_NOARP);
    }
    
    /* ------------------------------------------------------------------------ */
    
    static struct seq_operations arp_seq_ops = {
    	.start  = arp_seq_start,
    	.next   = neigh_seq_next,
    	.stop   = neigh_seq_stop,
    	.show   = arp_seq_show,
    };
    
    static int arp_seq_open(struct inode *inode, struct file *file)
    {
    	struct seq_file *seq;
    	int rc = -ENOMEM;
    
    	struct neigh_seq_state *s = kzalloc(sizeof(*s), GFP_KERNEL);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
           
    	if (!s)
    		goto out;
    
    	rc = seq_open(file, &arp_seq_ops);
    	if (rc)
    		goto out_kfree;
    
    	seq	     = file->private_data;
    	seq->private = s;
    out:
    	return rc;
    out_kfree:
    	kfree(s);
    	goto out;
    }
    
    static struct file_operations arp_seq_fops = {
    	.owner		= THIS_MODULE,
    	.open           = arp_seq_open,
    	.read           = seq_read,
    	.llseek         = seq_lseek,
    	.release	= seq_release_private,
    };
    
    static int __init arp_proc_init(void)
    {
    	if (!proc_net_fops_create("arp", S_IRUGO, &arp_seq_fops))
    		return -ENOMEM;
    	return 0;
    }
    
    #else /* CONFIG_PROC_FS */
    
    static int __init arp_proc_init(void)
    {
    	return 0;
    }
    
    #endif /* CONFIG_PROC_FS */
    
    EXPORT_SYMBOL(arp_broken_ops);
    EXPORT_SYMBOL(arp_find);
    EXPORT_SYMBOL(arp_create);
    EXPORT_SYMBOL(arp_xmit);
    EXPORT_SYMBOL(arp_send);
    EXPORT_SYMBOL(arp_tbl);
    
    #if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
    EXPORT_SYMBOL(clip_tbl_hook);
    #endif