Skip to content
Snippets Groups Projects
arp.c 34.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • static int arp_req_set(struct net *net, struct arpreq *r,
    
    Changli Gao's avatar
    Changli Gao committed
    		       struct net_device *dev)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct neighbour *neigh;
    	int err;
    
    
    	if (r->arp_flags & ATF_PUBL)
    
    		return arp_req_set_public(net, r, dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (r->arp_flags & ATF_PERM)
    		r->arp_flags |= ATF_COM;
    	if (dev == NULL) {
    
    		struct rtable *rt = ip_route_output(net, ip, 0, RTO_ONLINK, 0);
    
    
    		if (IS_ERR(rt))
    			return PTR_ERR(rt);
    
    		dev = rt->dst.dev;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		ip_rt_put(rt);
    		if (!dev)
    			return -EINVAL;
    	}
    	switch (dev->type) {
    
    #if IS_ENABLED(CONFIG_FDDI)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	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 int state = NUD_STALE;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (r->arp_flags & ATF_PERM)
    			state = NUD_PERMANENT;
    
    Changli Gao's avatar
    Changli Gao committed
    		err = neigh_update(neigh, (r->arp_flags & ATF_COM) ?
    
    				   r->arp_ha.sa_data : NULL, state,
    
    Changli Gao's avatar
    Changli Gao committed
    				   NEIGH_UPDATE_F_OVERRIDE |
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				   NEIGH_UPDATE_F_ADMIN);
    		neigh_release(neigh);
    	}
    	return err;
    }
    
    
    static unsigned int arp_state_to_flags(struct neighbour *neigh)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	if (neigh->nud_state&NUD_PERMANENT)
    
    Changli Gao's avatar
    Changli Gao committed
    		return ATF_PERM | ATF_COM;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	else if (neigh->nud_state&NUD_VALID)
    
    Changli Gao's avatar
    Changli Gao committed
    		return ATF_COM;
    	else
    		return 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    /*
     *	Get an ARP cache entry.
     */
    
    static int arp_req_get(struct arpreq *r, struct net_device *dev)
    {
    
    	__be32 ip = ((struct sockaddr_in *) &r->arp_pa)->sin_addr.s_addr;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	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;
    }
    
    
    int arp_invalidate(struct net_device *dev, __be32 ip)
    {
    	struct neighbour *neigh = neigh_lookup(&arp_tbl, &ip, dev);
    	int err = -ENXIO;
    
    	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;
    }
    EXPORT_SYMBOL(arp_invalidate);
    
    
    static int arp_req_delete_public(struct net *net, struct arpreq *r,
    		struct net_device *dev)
    
    {
    	__be32 ip = ((struct sockaddr_in *) &r->arp_pa)->sin_addr.s_addr;
    	__be32 mask = ((struct sockaddr_in *)&r->arp_netmask)->sin_addr.s_addr;
    
    	if (mask == htonl(0xFFFFFFFF))
    
    		return pneigh_delete(&arp_tbl, net, &ip, dev);
    
    	return arp_req_set_proxy(net, dev, 0);
    
    static int arp_req_delete(struct net *net, struct arpreq *r,
    
    Changli Gao's avatar
    Changli Gao committed
    			  struct net_device *dev)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (r->arp_flags & ATF_PUBL)
    
    		return arp_req_delete_public(net, r, dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (dev == NULL) {
    
    		struct rtable *rt = ip_route_output(net, ip, 0, RTO_ONLINK, 0);
    
    		if (IS_ERR(rt))
    			return PTR_ERR(rt);
    
    		dev = rt->dst.dev;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		ip_rt_put(rt);
    		if (!dev)
    			return -EINVAL;
    	}
    
    	return arp_invalidate(dev, ip);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    /*
     *	Handle an ARP layer I/O control request.
     */
    
    
    int arp_ioctl(struct net *net, unsigned int cmd, void __user *arg)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	int err;
    	struct arpreq r;
    	struct net_device *dev = NULL;
    
    	switch (cmd) {
    
    Changli Gao's avatar
    Changli Gao committed
    	case SIOCDARP:
    	case SIOCSARP:
    
    		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
    
    Changli Gao's avatar
    Changli Gao committed
    			return -EPERM;
    	case SIOCGARP:
    		err = copy_from_user(&r, arg, sizeof(struct arpreq));
    		if (err)
    			return -EFAULT;
    		break;
    	default:
    		return -EINVAL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	if (r.arp_pa.sa_family != AF_INET)
    		return -EPFNOSUPPORT;
    
    	if (!(r.arp_flags & ATF_PUBL) &&
    
    Changli Gao's avatar
    Changli Gao committed
    	    (r.arp_flags & (ATF_NETMASK | ATF_DONTPUB)))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EINVAL;
    	if (!(r.arp_flags & ATF_NETMASK))
    		((struct sockaddr_in *)&r.arp_netmask)->sin_addr.s_addr =
    							   htonl(0xFFFFFFFFUL);
    
    	rtnl_lock();
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (r.arp_dev[0]) {
    		err = -ENODEV;
    
    		dev = __dev_get_by_name(net, r.arp_dev);
    
    Changli Gao's avatar
    Changli Gao committed
    		if (dev == NULL)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			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;
    	}
    
    
    Stephen Hemminger's avatar
    Stephen Hemminger committed
    	switch (cmd) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	case SIOCDARP:
    
    		err = arp_req_delete(net, &r, dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		break;
    	case SIOCSARP:
    
    		err = arp_req_set(net, &r, dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		break;
    	case SIOCGARP:
    		err = arp_req_get(&r, dev);
    		break;
    	}
    out:
    
    	rtnl_unlock();
    
    	if (cmd == SIOCGARP && !err && copy_to_user(arg, &r, sizeof(r)))
    		err = -EFAULT;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return err;
    }
    
    
    Changli Gao's avatar
    Changli Gao committed
    static int arp_netdev_event(struct notifier_block *this, unsigned long event,
    			    void *ptr)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct net_device *dev = ptr;
    
    	switch (event) {
    	case NETDEV_CHANGEADDR:
    		neigh_changeaddr(&arp_tbl, dev);
    
    		rt_cache_flush(dev_net(dev));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		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 __read_mostly = {
    
    	.type =	cpu_to_be16(ETH_P_ARP),
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.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, "ipv4", NULL);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #endif
    	register_netdevice_notifier(&arp_netdev_notifier);
    }
    
    #ifdef CONFIG_PROC_FS
    
    #if IS_ENABLED(CONFIG_AX25)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    /* ------------------------------------------------------------------------ */
    /*
     *	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;
    
    
    Changli Gao's avatar
    Changli Gao committed
    		if (c != ' ')
    			*s++ = c;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	*s++ = '-';
    
    Changli Gao's avatar
    Changli Gao committed
    	n = (a->ax25_call[6] >> 1) & 0x0F;
    	if (n > 9) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		*s++ = '1';
    		n -= 10;
    	}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	*s++ = n + '0';
    	*s++ = '\0';
    
    	if (*buf == '\0' || *buf == '-')
    
    Changli Gao's avatar
    Changli Gao committed
    		return "*";
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	return buf;
    }
    #endif /* CONFIG_AX25 */
    
    #define HBUFFERLEN 30
    
    static void arp_format_neigh_entry(struct seq_file *seq,
    				   struct neighbour *n)
    {
    	char hbuffer[HBUFFERLEN];
    	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 IS_ENABLED(CONFIG_AX25)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	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++] = hex_asc_hi(n->ha[j]);
    		hbuffer[k++] = hex_asc_lo(n->ha[j]);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		hbuffer[k++] = ':';
    	}
    
    	if (k != 0)
    		--k;
    	hbuffer[k] = 0;
    
    #if IS_ENABLED(CONFIG_AX25)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    #endif
    
    	sprintf(tbuf, "%pI4", n->primary_key);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	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, "%pI4", n->key);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	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 const struct seq_operations arp_seq_ops = {
    
    Changli Gao's avatar
    Changli Gao committed
    	.start	= arp_seq_start,
    	.next	= neigh_seq_next,
    	.stop	= neigh_seq_stop,
    	.show	= arp_seq_show,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    };
    
    static int arp_seq_open(struct inode *inode, struct file *file)
    {
    
    	return seq_open_net(inode, file, &arp_seq_ops,
    			    sizeof(struct neigh_seq_state));
    
    static const struct file_operations arp_seq_fops = {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	.owner		= THIS_MODULE,
    	.open           = arp_seq_open,
    	.read           = seq_read,
    	.llseek         = seq_lseek,
    
    
    static int __net_init arp_net_init(struct net *net)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	if (!proc_net_fops_create(net, "arp", S_IRUGO, &arp_seq_fops))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -ENOMEM;
    	return 0;
    }
    
    
    static void __net_exit arp_net_exit(struct net *net)
    {
    	proc_net_remove(net, "arp");
    }
    
    static struct pernet_operations arp_net_ops = {
    	.init = arp_net_init,
    	.exit = arp_net_exit,
    };
    
    static int __init arp_proc_init(void)
    {
    	return register_pernet_subsys(&arp_net_ops);
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #else /* CONFIG_PROC_FS */
    
    static int __init arp_proc_init(void)
    {
    	return 0;
    }
    
    #endif /* CONFIG_PROC_FS */