/*
* INET An implementation of the TCP/IP protocol suite for the LINUX
* operating system. INET is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* "Ping" sockets
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Based on ipv4/udp.c code.
*
* Authors: Vasiliy Kulikov / Openwall (for Linux 2.6),
* Pavel Kankovsky (for Linux 2.4.32)
*
* Pavel gave all rights to bugs to Vasiliy,
* none of the bugs are Pavel's now.
*
*/
#include <asm/system.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/in.h>
#include <linux/errno.h>
#include <linux/timer.h>
#include <linux/mm.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <net/snmp.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include <net/icmp.h>
#include <net/protocol.h>
#include <linux/skbuff.h>
#include <linux/proc_fs.h>
#include <linux/export.h>
#include <net/sock.h>
#include <net/ping.h>
#include <net/udp.h>
#include <net/route.h>
#include <net/inet_common.h>
#include <net/checksum.h>
static struct ping_table ping_table;
static u16 ping_port_rover;
static inline int ping_hashfn(struct net *net, unsigned num, unsigned mask)
{
int res = (num + net_hash_mix(net)) & mask;
pr_debug("hash(%d) = %d\n", num, res);
return res;
}
static inline struct hlist_nulls_head *ping_hashslot(struct ping_table *table,
struct net *net, unsigned num)
{
return &table->hash[ping_hashfn(net, num, PING_HTABLE_MASK)];
}
static int ping_v4_get_port(struct sock *sk, unsigned short ident)
{
struct hlist_nulls_node *node;
struct hlist_nulls_head *hlist;
struct inet_sock *isk, *isk2;
struct sock *sk2 = NULL;
isk = inet_sk(sk);
write_lock_bh(&ping_table.lock);
if (ident == 0) {
u32 i;
u16 result = ping_port_rover + 1;
for (i = 0; i < (1L << 16); i++, result++) {
if (!result)
result++; /* avoid zero */
hlist = ping_hashslot(&ping_table, sock_net(sk),
result);
ping_portaddr_for_each_entry(sk2, node, hlist) {
isk2 = inet_sk(sk2);
if (isk2->inet_num == result)
goto next_port;
}
/* found */
ping_port_rover = ident = result;
break;
next_port:
;
}
if (i >= (1L << 16))
goto fail;
} else {
hlist = ping_hashslot(&ping_table, sock_net(sk), ident);
ping_portaddr_for_each_entry(sk2, node, hlist) {
isk2 = inet_sk(sk2);
if ((isk2->inet_num == ident) &&
(sk2 != sk) &&
(!sk2->sk_reuse || !sk->sk_reuse))
goto fail;
}
}
pr_debug("found port/ident = %d\n", ident);
isk->inet_num = ident;
if (sk_unhashed(sk)) {
pr_debug("was not hashed\n");
sock_hold(sk);
hlist_nulls_add_head(&sk->sk_nulls_node, hlist);
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
}
write_unlock_bh(&ping_table.lock);
return 0;
fail:
write_unlock_bh(&ping_table.lock);
return 1;
}
static void ping_v4_hash(struct sock *sk)
{
pr_debug("ping_v4_hash(sk->port=%u)\n", inet_sk(sk)->inet_num);
BUG(); /* "Please do not press this button again." */
}
static void ping_v4_unhash(struct sock *sk)
{
struct inet_sock *isk = inet_sk(sk);
pr_debug("ping_v4_unhash(isk=%p,isk->num=%u)\n", isk, isk->inet_num);
if (sk_hashed(sk)) {
write_lock_bh(&ping_table.lock);
hlist_nulls_del(&sk->sk_nulls_node);
sock_put(sk);
isk->inet_num = 0;
isk->inet_sport = 0;
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, -1);
write_unlock_bh(&ping_table.lock);
}
}
static struct sock *ping_v4_lookup(struct net *net, __be32 saddr, __be32 daddr,
u16 ident, int dif)
{
struct hlist_nulls_head *hslot = ping_hashslot(&ping_table, net, ident);
struct sock *sk = NULL;
struct inet_sock *isk;
struct hlist_nulls_node *hnode;
pr_debug(