Skip to content
Snippets Groups Projects
arp.c 34.8 KiB
Newer Older
		dev = dev_getbyhwaddr_rcu(net, r->arp_ha.sa_family,
Changli Gao's avatar
Changli Gao committed
				      r->arp_ha.sa_data);
		if (!dev)
			return -ENODEV;
	}
	if (mask) {
		if (pneigh_lookup(&arp_tbl, net, &ip, dev, 1) == NULL)
	return arp_req_set_proxy(net, dev, 1);
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 (!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;
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), 0);
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 */