// SPDX-License-Identifier: GPL-2.0-or-later
/*
* 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
*
* 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 <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/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>
#if IS_ENABLED(CONFIG_IPV6)
#include <linux/in6.h>
#include <linux/icmpv6.h>
#include <net/addrconf.h>
#include <net/ipv6.h>
#include <net/transp_v6.h>
#endif
struct ping_table {
struct hlist_nulls_head hash[PING_HTABLE_SIZE];
rwlock_t lock;
};
static struct ping_table ping_table;
struct pingv6_ops pingv6_ops;
EXPORT_SYMBOL_GPL(pingv6_ops);
static u16 ping_port_rover;
static inline u32 ping_hashfn(const struct net *net, u32 num, u32 mask)
{
u32 res = (num + net_hash_mix(net)) & mask;
pr_debug("hash(%u) = %u\n", num, res);
return res;
}
EXPORT_SYMBOL_GPL(ping_hash);
static inline struct hlist_nulls_head *ping_hashslot(struct ping_table *table,
struct net *net, unsigned int num)
{
return &table->hash[ping_hashfn(net, num, PING_HTABLE_MASK)];
}
int ping_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);
/* BUG? Why is this reuse and not reuseaddr? ping.c
* doesn't turn off SO_REUSEADDR, and it doesn't expect
* that other ping processes can steal its packets.
*/
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;
}
EXPORT_SYMBOL_GPL(ping_get_port);
int ping_hash(struct