icmp_socket.c 8.5 KB
Newer Older
1
/* Copyright (C) 2007-2012 B.A.T.M.A.N. contributors:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
 *
 * Marek Lindner
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA
 */

#include "main.h"
#include <linux/debugfs.h>
#include <linux/slab.h>
#include "icmp_socket.h"
#include "send.h"
#include "hash.h"
#include "originator.h"
#include "hard-interface.h"

static struct socket_client *socket_client_hash[256];

static void bat_socket_add_packet(struct socket_client *socket_client,
				  struct icmp_packet_rr *icmp_packet,
				  size_t icmp_len);

35
void batadv_socket_init(void)
36 37 38 39 40 41 42 43 44 45 46
{
	memset(socket_client_hash, 0, sizeof(socket_client_hash));
}

static int bat_socket_open(struct inode *inode, struct file *file)
{
	unsigned int i;
	struct socket_client *socket_client;

	nonseekable_open(inode, file);

47
	socket_client = kmalloc(sizeof(*socket_client), GFP_KERNEL);
48 49 50 51 52 53 54 55 56 57 58 59

	if (!socket_client)
		return -ENOMEM;

	for (i = 0; i < ARRAY_SIZE(socket_client_hash); i++) {
		if (!socket_client_hash[i]) {
			socket_client_hash[i] = socket_client;
			break;
		}
	}

	if (i == ARRAY_SIZE(socket_client_hash)) {
60
		pr_err("Error - can't add another packet client: maximum number of clients reached\n");
61 62 63 64 65 66 67 68 69 70 71 72 73
		kfree(socket_client);
		return -EXFULL;
	}

	INIT_LIST_HEAD(&socket_client->queue_list);
	socket_client->queue_len = 0;
	socket_client->index = i;
	socket_client->bat_priv = inode->i_private;
	spin_lock_init(&socket_client->lock);
	init_waitqueue_head(&socket_client->queue_wait);

	file->private_data = socket_client;

74
	batadv_inc_module_count();
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
	return 0;
}

static int bat_socket_release(struct inode *inode, struct file *file)
{
	struct socket_client *socket_client = file->private_data;
	struct socket_packet *socket_packet;
	struct list_head *list_pos, *list_pos_tmp;

	spin_lock_bh(&socket_client->lock);

	/* for all packets in the queue ... */
	list_for_each_safe(list_pos, list_pos_tmp, &socket_client->queue_list) {
		socket_packet = list_entry(list_pos,
					   struct socket_packet, list);

		list_del(list_pos);
		kfree(socket_packet);
	}

	socket_client_hash[socket_client->index] = NULL;
	spin_unlock_bh(&socket_client->lock);

	kfree(socket_client);
99
	batadv_dec_module_count();
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135

	return 0;
}

static ssize_t bat_socket_read(struct file *file, char __user *buf,
			       size_t count, loff_t *ppos)
{
	struct socket_client *socket_client = file->private_data;
	struct socket_packet *socket_packet;
	size_t packet_len;
	int error;

	if ((file->f_flags & O_NONBLOCK) && (socket_client->queue_len == 0))
		return -EAGAIN;

	if ((!buf) || (count < sizeof(struct icmp_packet)))
		return -EINVAL;

	if (!access_ok(VERIFY_WRITE, buf, count))
		return -EFAULT;

	error = wait_event_interruptible(socket_client->queue_wait,
					 socket_client->queue_len);

	if (error)
		return error;

	spin_lock_bh(&socket_client->lock);

	socket_packet = list_first_entry(&socket_client->queue_list,
					 struct socket_packet, list);
	list_del(&socket_packet->list);
	socket_client->queue_len--;

	spin_unlock_bh(&socket_client->lock);

136 137
	packet_len = min(count, socket_packet->icmp_len);
	error = copy_to_user(buf, &socket_packet->icmp_packet, packet_len);
138 139 140 141 142 143 144 145 146 147 148 149 150 151

	kfree(socket_packet);

	if (error)
		return -EFAULT;

	return packet_len;
}

static ssize_t bat_socket_write(struct file *file, const char __user *buff,
				size_t len, loff_t *off)
{
	struct socket_client *socket_client = file->private_data;
	struct bat_priv *bat_priv = socket_client->bat_priv;
152
	struct hard_iface *primary_if = NULL;
153 154 155
	struct sk_buff *skb;
	struct icmp_packet_rr *icmp_packet;

156 157
	struct orig_node *orig_node = NULL;
	struct neigh_node *neigh_node = NULL;
158 159 160 161
	size_t packet_len = sizeof(struct icmp_packet);

	if (len < sizeof(struct icmp_packet)) {
		bat_dbg(DBG_BATMAN, bat_priv,
162
			"Error - can't send packet from char device: invalid packet size\n");
163 164 165
		return -EINVAL;
	}

166
	primary_if = batadv_primary_if_get_selected(bat_priv);
167 168 169 170 171

	if (!primary_if) {
		len = -EFAULT;
		goto out;
	}
172 173 174 175

	if (len >= sizeof(struct icmp_packet_rr))
		packet_len = sizeof(struct icmp_packet_rr);

176
	skb = dev_alloc_skb(packet_len + ETH_HLEN);
177 178 179 180
	if (!skb) {
		len = -ENOMEM;
		goto out;
	}
181

182
	skb_reserve(skb, ETH_HLEN);
183 184
	icmp_packet = (struct icmp_packet_rr *)skb_put(skb, packet_len);

185
	if (copy_from_user(icmp_packet, buff, packet_len)) {
186 187 188 189
		len = -EFAULT;
		goto free_skb;
	}

190
	if (icmp_packet->header.packet_type != BAT_ICMP) {
191
		bat_dbg(DBG_BATMAN, bat_priv,
192
			"Error - can't send packet from char device: got bogus packet type (expected: BAT_ICMP)\n");
193 194 195 196 197 198
		len = -EINVAL;
		goto free_skb;
	}

	if (icmp_packet->msg_type != ECHO_REQUEST) {
		bat_dbg(DBG_BATMAN, bat_priv,
199
			"Error - can't send packet from char device: got bogus message type (expected: ECHO_REQUEST)\n");
200 201 202 203 204 205
		len = -EINVAL;
		goto free_skb;
	}

	icmp_packet->uid = socket_client->index;

206
	if (icmp_packet->header.version != COMPAT_VERSION) {
207
		icmp_packet->msg_type = PARAMETER_PROBLEM;
208
		icmp_packet->header.version = COMPAT_VERSION;
209 210 211 212 213 214 215
		bat_socket_add_packet(socket_client, icmp_packet, packet_len);
		goto free_skb;
	}

	if (atomic_read(&bat_priv->mesh_state) != MESH_ACTIVE)
		goto dst_unreach;

216
	orig_node = orig_hash_find(bat_priv, icmp_packet->dst);
217
	if (!orig_node)
218
		goto dst_unreach;
219

220
	neigh_node = batadv_orig_node_get_router(orig_node);
221
	if (!neigh_node)
222
		goto dst_unreach;
223

224
	if (!neigh_node->if_incoming)
225 226
		goto dst_unreach;

227
	if (neigh_node->if_incoming->if_status != IF_ACTIVE)
228 229 230
		goto dst_unreach;

	memcpy(icmp_packet->orig,
231
	       primary_if->net_dev->dev_addr, ETH_ALEN);
232 233

	if (packet_len == sizeof(struct icmp_packet_rr))
234
		memcpy(icmp_packet->rr,
235
		       neigh_node->if_incoming->net_dev->dev_addr, ETH_ALEN);
236

237
	batadv_send_skb_packet(skb, neigh_node->if_incoming, neigh_node->addr);
238 239 240 241 242 243 244 245
	goto out;

dst_unreach:
	icmp_packet->msg_type = DESTINATION_UNREACHABLE;
	bat_socket_add_packet(socket_client, icmp_packet, packet_len);
free_skb:
	kfree_skb(skb);
out:
246
	if (primary_if)
247
		batadv_hardif_free_ref(primary_if);
248
	if (neigh_node)
249
		batadv_neigh_node_free_ref(neigh_node);
250
	if (orig_node)
251
		batadv_orig_node_free_ref(orig_node);
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
	return len;
}

static unsigned int bat_socket_poll(struct file *file, poll_table *wait)
{
	struct socket_client *socket_client = file->private_data;

	poll_wait(file, &socket_client->queue_wait, wait);

	if (socket_client->queue_len > 0)
		return POLLIN | POLLRDNORM;

	return 0;
}

static const struct file_operations fops = {
	.owner = THIS_MODULE,
	.open = bat_socket_open,
	.release = bat_socket_release,
	.read = bat_socket_read,
	.write = bat_socket_write,
	.poll = bat_socket_poll,
	.llseek = no_llseek,
};

277
int batadv_socket_setup(struct bat_priv *bat_priv)
278 279 280 281 282 283 284 285
{
	struct dentry *d;

	if (!bat_priv->debug_dir)
		goto err;

	d = debugfs_create_file(ICMP_SOCKET, S_IFREG | S_IWUSR | S_IRUSR,
				bat_priv->debug_dir, bat_priv, &fops);
286
	if (!d)
287 288 289 290 291
		goto err;

	return 0;

err:
292
	return -ENOMEM;
293 294 295 296 297 298 299 300
}

static void bat_socket_add_packet(struct socket_client *socket_client,
				  struct icmp_packet_rr *icmp_packet,
				  size_t icmp_len)
{
	struct socket_packet *socket_packet;

301
	socket_packet = kmalloc(sizeof(*socket_packet), GFP_ATOMIC);
302 303 304 305 306 307 308 309 310 311 312

	if (!socket_packet)
		return;

	INIT_LIST_HEAD(&socket_packet->list);
	memcpy(&socket_packet->icmp_packet, icmp_packet, icmp_len);
	socket_packet->icmp_len = icmp_len;

	spin_lock_bh(&socket_client->lock);

	/* while waiting for the lock the socket_client could have been
313 314
	 * deleted
	 */
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
	if (!socket_client_hash[icmp_packet->uid]) {
		spin_unlock_bh(&socket_client->lock);
		kfree(socket_packet);
		return;
	}

	list_add_tail(&socket_packet->list, &socket_client->queue_list);
	socket_client->queue_len++;

	if (socket_client->queue_len > 100) {
		socket_packet = list_first_entry(&socket_client->queue_list,
						 struct socket_packet, list);

		list_del(&socket_packet->list);
		kfree(socket_packet);
		socket_client->queue_len--;
	}

	spin_unlock_bh(&socket_client->lock);

	wake_up(&socket_client->queue_wait);
}

338 339
void batadv_socket_receive_packet(struct icmp_packet_rr *icmp_packet,
				  size_t icmp_len)
340 341 342 343 344 345
{
	struct socket_client *hash = socket_client_hash[icmp_packet->uid];

	if (hash)
		bat_socket_add_packet(hash, icmp_packet, icmp_len);
}