Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6d0bfe22 authored by Lorenzo Colitti's avatar Lorenzo Colitti Committed by David S. Miller
Browse files

net: ipv6: Add IPv6 support to the ping socket.



This adds the ability to send ICMPv6 echo requests without a
raw socket. The equivalent ability for ICMPv4 was added in
2011.

Instead of having separate code paths for IPv4 and IPv6, make
most of the code in net/ipv4/ping.c dual-stack and only add a
few IPv6-specific bits (like the protocol definition) to a new
net/ipv6/ping.c. Hopefully this will reduce divergence and/or
duplication of bugs in the future.

Caveats:

- Setting options via ancillary data (e.g., using IPV6_PKTINFO
  to specify the outgoing interface) is not yet supported.
- There are no separate security settings for IPv4 and IPv6;
  everything is controlled by /proc/net/ipv4/ping_group_range.
- The proc interface does not yet display IPv6 ping sockets
  properly.

Tested with a patched copy of ping6 and using raw socket calls.
Compiles and works with all of CONFIG_IPV6={n,m,y}.

Signed-off-by: default avatarLorenzo Colitti <lorenzo@google.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 6e2842f4
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -260,6 +260,12 @@ static inline void fl6_sock_release(struct ip6_flowlabel *fl)

extern void icmpv6_notify(struct sk_buff *skb, u8 type, u8 code, __be32 info);

int icmpv6_push_pending_frames(struct sock *sk, struct flowi6 *fl6,
			       struct icmp6hdr *thdr, int len);

struct dst_entry *icmpv6_route_lookup(struct net *net, struct sk_buff *skb,
				      struct sock *sk, struct flowi6 *fl6);

extern int 			ip6_ra_control(struct sock *sk, int sel);

extern int			ipv6_parse_hopopts(struct sk_buff *skb);
+46 −3
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@
#ifndef _PING_H
#define _PING_H

#include <net/icmp.h>
#include <net/netns/hash.h>

/* PING_HTABLE_SIZE must be power of 2 */
@@ -28,6 +29,18 @@
 */
#define GID_T_MAX (((gid_t)~0U) >> 1)

/* Compatibility glue so we can support IPv6 when it's compiled as a module */
struct pingv6_ops {
	int (*ipv6_recv_error)(struct sock *sk, struct msghdr *msg, int len);
	int (*ip6_datagram_recv_ctl)(struct sock *sk, struct msghdr *msg,
				     struct sk_buff *skb);
	int (*icmpv6_err_convert)(u8 type, u8 code, int *err);
	void (*ipv6_icmp_error)(struct sock *sk, struct sk_buff *skb, int err,
				__be16 port, u32 info, u8 *payload);
	int (*ipv6_chk_addr)(struct net *net, const struct in6_addr *addr,
			     struct net_device *dev, int strict);
};

struct ping_table {
	struct hlist_nulls_head	hash[PING_HTABLE_SIZE];
	rwlock_t		lock;
@@ -39,10 +52,39 @@ struct ping_iter_state {
};

extern struct proto ping_prot;
extern struct ping_table ping_table;
#if IS_ENABLED(CONFIG_IPV6)
extern struct pingv6_ops pingv6_ops;
#endif

struct pingfakehdr {
	struct icmphdr icmph;
	struct iovec *iov;
	sa_family_t family;
	__wsum wcheck;
};

extern void ping_rcv(struct sk_buff *);
extern void ping_err(struct sk_buff *, u32 info);
int  ping_get_port(struct sock *sk, unsigned short ident);
void ping_hash(struct sock *sk);
void ping_unhash(struct sock *sk);

int  ping_init_sock(struct sock *sk);
void ping_close(struct sock *sk, long timeout);
int  ping_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len);
void ping_err(struct sk_buff *skb, int offset, u32 info);
int  ping_getfrag(void *from, char *to, int offset, int fraglen, int odd,
		  struct sk_buff *);

int  ping_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
		  size_t len, int noblock, int flags, int *addr_len);
int  ping_common_sendmsg(int family, struct msghdr *msg, size_t len,
			 void *user_icmph, size_t icmph_len);
int  ping_v4_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
		     size_t len);
int  ping_v6_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
		     size_t len);
int  ping_queue_rcv_skb(struct sock *sk, struct sk_buff *skb);
void ping_rcv(struct sk_buff *skb);

#ifdef CONFIG_PROC_FS
extern int __init ping_proc_init(void);
@@ -50,6 +92,7 @@ extern void ping_proc_exit(void);
#endif

void __init ping_init(void);

int  __init pingv6_init(void);
void pingv6_exit(void);

#endif /* _PING_H */
+3 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ extern struct proto rawv6_prot;
extern struct proto udpv6_prot;
extern struct proto udplitev6_prot;
extern struct proto tcpv6_prot;
extern struct proto pingv6_prot;

struct flowi6;

@@ -21,6 +22,8 @@ extern int ipv6_frag_init(void);
extern void				ipv6_frag_exit(void);

/* transport protocols */
extern int				pingv6_init(void);
extern void				pingv6_exit(void);
extern int				rawv6_init(void);
extern void				rawv6_exit(void);
extern int				udpv6_init(void);
+3 −2
Original line number Diff line number Diff line
@@ -939,7 +939,8 @@ int icmp_rcv(struct sk_buff *skb)
void icmp_err(struct sk_buff *skb, u32 info)
{
	struct iphdr *iph = (struct iphdr *)skb->data;
	struct icmphdr *icmph = (struct icmphdr *)(skb->data+(iph->ihl<<2));
	int offset = iph->ihl<<2;
	struct icmphdr *icmph = (struct icmphdr *)(skb->data + offset);
	int type = icmp_hdr(skb)->type;
	int code = icmp_hdr(skb)->code;
	struct net *net = dev_net(skb->dev);
@@ -949,7 +950,7 @@ void icmp_err(struct sk_buff *skb, u32 info)
	 * triggered by ICMP_ECHOREPLY which sent from kernel.
	 */
	if (icmph->type != ICMP_ECHOREPLY) {
		ping_err(skb, info);
		ping_err(skb, offset, info);
		return;
	}

+397 −160
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@
#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>
@@ -46,8 +45,18 @@
#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


static struct ping_table ping_table;
struct ping_table ping_table;
struct pingv6_ops pingv6_ops;
EXPORT_SYMBOL_GPL(pingv6_ops);

static u16 ping_port_rover;

@@ -58,6 +67,7 @@ static inline int ping_hashfn(struct net *net, unsigned int num, unsigned int ma
	pr_debug("hash(%d) = %d\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)
@@ -65,7 +75,7 @@ static inline struct hlist_nulls_head *ping_hashslot(struct ping_table *table,
	return &table->hash[ping_hashfn(net, num, PING_HTABLE_MASK)];
}

static int ping_v4_get_port(struct sock *sk, unsigned short ident)
int ping_get_port(struct sock *sk, unsigned short ident)
{
	struct hlist_nulls_node *node;
	struct hlist_nulls_head *hlist;
@@ -103,6 +113,10 @@ static int ping_v4_get_port(struct sock *sk, unsigned short 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))
@@ -125,17 +139,18 @@ static int ping_v4_get_port(struct sock *sk, unsigned short ident)
	write_unlock_bh(&ping_table.lock);
	return 1;
}
EXPORT_SYMBOL_GPL(ping_get_port);

static void ping_v4_hash(struct sock *sk)
void ping_hash(struct sock *sk)
{
	pr_debug("ping_v4_hash(sk->port=%u)\n", inet_sk(sk)->inet_num);
	pr_debug("ping_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)
void ping_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);
	pr_debug("ping_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);
@@ -146,31 +161,61 @@ static void ping_v4_unhash(struct sock *sk)
		write_unlock_bh(&ping_table.lock);
	}
}
EXPORT_SYMBOL_GPL(ping_unhash);

static struct sock *ping_v4_lookup(struct net *net, __be32 saddr, __be32 daddr,
				   u16 ident, int dif)
static struct sock *ping_lookup(struct net *net, struct sk_buff *skb, u16 ident)
{
	struct hlist_nulls_head *hslot = ping_hashslot(&ping_table, net, ident);
	struct sock *sk = NULL;
	struct inet_sock *isk;
	struct hlist_nulls_node *hnode;
	int dif = skb->dev->ifindex;

	if (skb->protocol == htons(ETH_P_IP)) {
		pr_debug("try to find: num = %d, daddr = %pI4, dif = %d\n",
		 (int)ident, &daddr, dif);
			 (int)ident, &ip_hdr(skb)->daddr, dif);
#if IS_ENABLED(CONFIG_IPV6)
	} else if (skb->protocol == htons(ETH_P_IPV6)) {
		pr_debug("try to find: num = %d, daddr = %pI6c, dif = %d\n",
			 (int)ident, &ipv6_hdr(skb)->daddr, dif);
#endif
	}

	read_lock_bh(&ping_table.lock);

	ping_portaddr_for_each_entry(sk, hnode, hslot) {
		isk = inet_sk(sk);

		pr_debug("iterate\n");
		if (isk->inet_num != ident)
			continue;

		if (skb->protocol == htons(ETH_P_IP) &&
		    sk->sk_family == AF_INET) {
			pr_debug("found: %p: num=%d, daddr=%pI4, dif=%d\n", sk,
				 (int) isk->inet_num, &isk->inet_rcv_saddr,
				 sk->sk_bound_dev_if);

		pr_debug("iterate\n");
		if (isk->inet_num != ident)
			if (isk->inet_rcv_saddr &&
			    isk->inet_rcv_saddr != ip_hdr(skb)->daddr)
				continue;
		if (isk->inet_rcv_saddr && isk->inet_rcv_saddr != daddr)
#if IS_ENABLED(CONFIG_IPV6)
		} else if (skb->protocol == htons(ETH_P_IPV6) &&
			   sk->sk_family == AF_INET6) {
			struct ipv6_pinfo *np = inet6_sk(sk);

			pr_debug("found: %p: num=%d, daddr=%pI6c, dif=%d\n", sk,
				 (int) isk->inet_num,
				 &inet6_sk(sk)->rcv_saddr,
				 sk->sk_bound_dev_if);

			if (!ipv6_addr_any(&np->rcv_saddr) &&
			    !ipv6_addr_equal(&np->rcv_saddr,
					     &ipv6_hdr(skb)->daddr))
				continue;
#endif
		}

		if (sk->sk_bound_dev_if && sk->sk_bound_dev_if != dif)
			continue;

@@ -200,7 +245,7 @@ static void inet_get_ping_group_range_net(struct net *net, kgid_t *low,
}


static int ping_init_sock(struct sock *sk)
int ping_init_sock(struct sock *sk)
{
	struct net *net = sock_net(sk);
	kgid_t group = current_egid();
@@ -225,8 +270,9 @@ static int ping_init_sock(struct sock *sk)

	return -EACCES;
}
EXPORT_SYMBOL_GPL(ping_init_sock);

static void ping_close(struct sock *sk, long timeout)
void ping_close(struct sock *sk, long timeout)
{
	pr_debug("ping_close(sk=%p,sk->num=%u)\n",
		 inet_sk(sk), inet_sk(sk)->inet_num);
@@ -234,27 +280,24 @@ static void ping_close(struct sock *sk, long timeout)

	sk_common_release(sk);
}
EXPORT_SYMBOL_GPL(ping_close);

/*
 * We need our own bind because there are no privileged id's == local ports.
 * Moreover, we don't allow binding to multi- and broadcast addresses.
 */

static int ping_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
/* Checks the bind address and possibly modifies sk->sk_bound_dev_if. */
int ping_check_bind_addr(struct sock *sk, struct inet_sock *isk,
			 struct sockaddr *uaddr, int addr_len) {
	struct net *net = sock_net(sk);
	if (sk->sk_family == AF_INET) {
		struct sockaddr_in *addr = (struct sockaddr_in *) uaddr;
	struct inet_sock *isk = inet_sk(sk);
	unsigned short snum;
		int chk_addr_ret;
	int err;

	if (addr_len < sizeof(struct sockaddr_in))
		if (addr_len < sizeof(*addr))
			return -EINVAL;

	pr_debug("ping_v4_bind(sk=%p,sa_addr=%08x,sa_port=%d)\n",
		 sk, addr->sin_addr.s_addr, ntohs(addr->sin_port));
		pr_debug("ping_check_bind_addr(sk=%p,addr=%pI4,port=%d)\n",
			 sk, &addr->sin_addr.s_addr, ntohs(addr->sin_port));

		chk_addr_ret = inet_addr_type(net, addr->sin_addr.s_addr);

	chk_addr_ret = inet_addr_type(sock_net(sk), addr->sin_addr.s_addr);
		if (addr->sin_addr.s_addr == htonl(INADDR_ANY))
			chk_addr_ret = RTN_LOCAL;

@@ -265,6 +308,95 @@ static int ping_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
		    chk_addr_ret == RTN_BROADCAST)
			return -EADDRNOTAVAIL;

#if IS_ENABLED(CONFIG_IPV6)
	} else if (sk->sk_family == AF_INET6) {
		struct sockaddr_in6 *addr = (struct sockaddr_in6 *) uaddr;
		int addr_type, scoped, has_addr;
		struct net_device *dev = NULL;

		if (addr_len < sizeof(*addr))
			return -EINVAL;

		pr_debug("ping_check_bind_addr(sk=%p,addr=%pI6c,port=%d)\n",
			 sk, addr->sin6_addr.s6_addr, ntohs(addr->sin6_port));

		addr_type = ipv6_addr_type(&addr->sin6_addr);
		scoped = __ipv6_addr_needs_scope_id(addr_type);
		if ((addr_type != IPV6_ADDR_ANY &&
		     !(addr_type & IPV6_ADDR_UNICAST)) ||
		    (scoped && !addr->sin6_scope_id))
			return -EINVAL;

		rcu_read_lock();
		if (addr->sin6_scope_id) {
			dev = dev_get_by_index_rcu(net, addr->sin6_scope_id);
			if (!dev) {
				rcu_read_unlock();
				return -ENODEV;
			}
		}
		has_addr = pingv6_ops.ipv6_chk_addr(net, &addr->sin6_addr, dev,
						    scoped);
		rcu_read_unlock();

		if (!(isk->freebind || isk->transparent || has_addr ||
		      addr_type == IPV6_ADDR_ANY))
			return -EADDRNOTAVAIL;

		if (scoped)
			sk->sk_bound_dev_if = addr->sin6_scope_id;
#endif
	} else {
		return -EAFNOSUPPORT;
	}
	return 0;
}

void ping_set_saddr(struct sock *sk, struct sockaddr *saddr)
{
	if (saddr->sa_family == AF_INET) {
		struct inet_sock *isk = inet_sk(sk);
		struct sockaddr_in *addr = (struct sockaddr_in *) saddr;
		isk->inet_rcv_saddr = isk->inet_saddr = addr->sin_addr.s_addr;
#if IS_ENABLED(CONFIG_IPV6)
	} else if (saddr->sa_family == AF_INET6) {
		struct sockaddr_in6 *addr = (struct sockaddr_in6 *) saddr;
		struct ipv6_pinfo *np = inet6_sk(sk);
		np->rcv_saddr = np->saddr = addr->sin6_addr;
#endif
	}
}

void ping_clear_saddr(struct sock *sk, int dif)
{
	sk->sk_bound_dev_if = dif;
	if (sk->sk_family == AF_INET) {
		struct inet_sock *isk = inet_sk(sk);
		isk->inet_rcv_saddr = isk->inet_saddr = 0;
#if IS_ENABLED(CONFIG_IPV6)
	} else if (sk->sk_family == AF_INET6) {
		struct ipv6_pinfo *np = inet6_sk(sk);
		memset(&np->rcv_saddr, 0, sizeof(np->rcv_saddr));
		memset(&np->saddr, 0, sizeof(np->saddr));
#endif
	}
}
/*
 * We need our own bind because there are no privileged id's == local ports.
 * Moreover, we don't allow binding to multi- and broadcast addresses.
 */

int ping_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
	struct inet_sock *isk = inet_sk(sk);
	unsigned short snum;
	int err;
	int dif = sk->sk_bound_dev_if;

	err = ping_check_bind_addr(sk, isk, uaddr, addr_len);
	if (err)
		return err;

	lock_sock(sk);

	err = -EINVAL;
@@ -272,42 +404,50 @@ static int ping_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
		goto out;

	err = -EADDRINUSE;
	isk->inet_rcv_saddr = isk->inet_saddr = addr->sin_addr.s_addr;
	snum = ntohs(addr->sin_port);
	if (ping_v4_get_port(sk, snum) != 0) {
		isk->inet_saddr = isk->inet_rcv_saddr = 0;
	ping_set_saddr(sk, uaddr);
	snum = ntohs(((struct sockaddr_in *)uaddr)->sin_port);
	if (ping_get_port(sk, snum) != 0) {
		ping_clear_saddr(sk, dif);
		goto out;
	}

	pr_debug("after bind(): num = %d, daddr = %pI4, dif = %d\n",
	pr_debug("after bind(): num = %d, dif = %d\n",
		 (int)isk->inet_num,
		 &isk->inet_rcv_saddr,
		 (int)sk->sk_bound_dev_if);

	err = 0;
	if (isk->inet_rcv_saddr)
	if ((sk->sk_family == AF_INET && isk->inet_rcv_saddr) ||
	    (sk->sk_family == AF_INET6 &&
	     !ipv6_addr_any(&inet6_sk(sk)->rcv_saddr)))
		sk->sk_userlocks |= SOCK_BINDADDR_LOCK;

	if (snum)
		sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
	isk->inet_sport = htons(isk->inet_num);
	isk->inet_daddr = 0;
	isk->inet_dport = 0;

#if IS_ENABLED(CONFIG_IPV6)
	if (sk->sk_family == AF_INET6)
		memset(&inet6_sk(sk)->daddr, 0, sizeof(inet6_sk(sk)->daddr));
#endif

	sk_dst_reset(sk);
out:
	release_sock(sk);
	pr_debug("ping_v4_bind -> %d\n", err);
	return err;
}
EXPORT_SYMBOL_GPL(ping_bind);

/*
 * Is this a supported type of ICMP message?
 */

static inline int ping_supported(int type, int code)
static inline int ping_supported(int family, int type, int code)
{
	if (type == ICMP_ECHO && code == 0)
		return 1;
	return 0;
	return (family == AF_INET && type == ICMP_ECHO && code == 0) ||
	       (family == AF_INET6 && type == ICMPV6_ECHO_REQUEST && code == 0);
}

/*
@@ -315,30 +455,42 @@ static inline int ping_supported(int type, int code)
 * sort of error condition.
 */

static int ping_queue_rcv_skb(struct sock *sk, struct sk_buff *skb);

void ping_err(struct sk_buff *skb, u32 info)
void ping_err(struct sk_buff *skb, int offset, u32 info)
{
	struct iphdr *iph = (struct iphdr *)skb->data;
	struct icmphdr *icmph = (struct icmphdr *)(skb->data+(iph->ihl<<2));
	int family;
	struct icmphdr *icmph;
	struct inet_sock *inet_sock;
	int type = icmp_hdr(skb)->type;
	int code = icmp_hdr(skb)->code;
	int type;
	int code;
	struct net *net = dev_net(skb->dev);
	struct sock *sk;
	int harderr;
	int err;

	if (skb->protocol == htons(ETH_P_IP)) {
		family = AF_INET;
		type = icmp_hdr(skb)->type;
		code = icmp_hdr(skb)->code;
		icmph = (struct icmphdr *)(skb->data + offset);
	} else if (skb->protocol == htons(ETH_P_IPV6)) {
		family = AF_INET6;
		type = icmp6_hdr(skb)->icmp6_type;
		code = icmp6_hdr(skb)->icmp6_code;
		icmph = (struct icmphdr *) (skb->data + offset);
	} else {
		BUG();
	}

	/* We assume the packet has already been checked by icmp_unreach */

	if (!ping_supported(icmph->type, icmph->code))
	if (!ping_supported(family, icmph->type, icmph->code))
		return;

	pr_debug("ping_err(type=%04x,code=%04x,id=%04x,seq=%04x)\n", type,
		 code, ntohs(icmph->un.echo.id), ntohs(icmph->un.echo.sequence));
	pr_debug("ping_err(proto=0x%x,type=%d,code=%d,id=%04x,seq=%04x)\n",
		 skb->protocol, type, code, ntohs(icmph->un.echo.id),
		 ntohs(icmph->un.echo.sequence));

	sk = ping_v4_lookup(net, iph->daddr, iph->saddr,
			    ntohs(icmph->un.echo.id), skb->dev->ifindex);
	sk = ping_lookup(net, skb, ntohs(icmph->un.echo.id));
	if (sk == NULL) {
		pr_debug("no socket, dropping\n");
		return;	/* No socket for error */
@@ -349,6 +501,7 @@ void ping_err(struct sk_buff *skb, u32 info)
	harderr = 0;
	inet_sock = inet_sk(sk);

	if (skb->protocol == htons(ETH_P_IP)) {
		switch (type) {
		default:
		case ICMP_TIME_EXCEEDED:
@@ -356,7 +509,8 @@ void ping_err(struct sk_buff *skb, u32 info)
			break;
		case ICMP_SOURCE_QUENCH:
			/* This is not a real error but ping wants to see it.
		 * Report it with some fake errno. */
			 * Report it with some fake errno.
			 */
			err = EREMOTEIO;
			break;
		case ICMP_PARAMETERPROB:
@@ -385,35 +539,44 @@ void ping_err(struct sk_buff *skb, u32 info)
			err = EREMOTEIO;
			break;
		}
#if IS_ENABLED(CONFIG_IPV6)
	} else if (skb->protocol == htons(ETH_P_IPV6)) {
		harderr = pingv6_ops.icmpv6_err_convert(type, code, &err);
#endif
	}

	/*
	 *      RFC1122: OK.  Passes ICMP errors back to application, as per
	 *	4.1.3.3.
	 */
	if (!inet_sock->recverr) {
	if ((family == AF_INET && !inet_sock->recverr) ||
	    (family == AF_INET6 && !inet6_sk(sk)->recverr)) {
		if (!harderr || sk->sk_state != TCP_ESTABLISHED)
			goto out;
	} else {
		if (family == AF_INET) {
			ip_icmp_error(sk, skb, err, 0 /* no remote port */,
				      info, (u8 *)icmph);
#if IS_ENABLED(CONFIG_IPV6)
		} else if (family == AF_INET6) {
			pingv6_ops.ipv6_icmp_error(sk, skb, err, 0,
						   info, (u8 *)icmph);
#endif
		}
	}
	sk->sk_err = err;
	sk->sk_error_report(sk);
out:
	sock_put(sk);
}
EXPORT_SYMBOL_GPL(ping_err);

/*
 *	Copy and checksum an ICMP Echo packet from user space into a buffer.
 *	Copy and checksum an ICMP Echo packet from user space into a buffer
 *	starting from the payload.
 */

struct pingfakehdr {
	struct icmphdr icmph;
	struct iovec *iov;
	__wsum wcheck;
};

static int ping_getfrag(void *from, char *to,
int ping_getfrag(void *from, char *to,
		 int offset, int fraglen, int odd, struct sk_buff *skb)
{
	struct pingfakehdr *pfh = (struct pingfakehdr *)from;
@@ -425,19 +588,32 @@ static int ping_getfrag(void *from, char *to,
			    pfh->iov, 0, fraglen - sizeof(struct icmphdr),
			    &pfh->wcheck))
			return -EFAULT;

		return 0;
	}
	if (offset < sizeof(struct icmphdr))
	} else if (offset < sizeof(struct icmphdr)) {
			BUG();
	} else {
		if (csum_partial_copy_fromiovecend
				(to, pfh->iov, offset - sizeof(struct icmphdr),
				 fraglen, &pfh->wcheck))
			return -EFAULT;
	}

#if IS_ENABLED(CONFIG_IPV6)
	/* For IPv6, checksum each skb as we go along, as expected by
	 * icmpv6_push_pending_frames. For IPv4, accumulate the checksum in
	 * wcheck, it will be finalized in ping_v4_push_pending_frames.
	 */
	if (pfh->family == AF_INET6) {
		skb->csum = pfh->wcheck;
		skb->ip_summed = CHECKSUM_NONE;
		pfh->wcheck = 0;
	}
#endif

	return 0;
}
EXPORT_SYMBOL_GPL(ping_getfrag);

static int ping_push_pending_frames(struct sock *sk, struct pingfakehdr *pfh,
static int ping_v4_push_pending_frames(struct sock *sk, struct pingfakehdr *pfh,
				       struct flowi4 *fl4)
{
	struct sk_buff *skb = skb_peek(&sk->sk_write_queue);
@@ -450,24 +626,9 @@ static int ping_push_pending_frames(struct sock *sk, struct pingfakehdr *pfh,
	return ip_push_pending_frames(sk, fl4);
}

static int ping_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
			size_t len)
{
	struct net *net = sock_net(sk);
	struct flowi4 fl4;
	struct inet_sock *inet = inet_sk(sk);
	struct ipcm_cookie ipc;
	struct icmphdr user_icmph;
	struct pingfakehdr pfh;
	struct rtable *rt = NULL;
	struct ip_options_data opt_copy;
	int free = 0;
	__be32 saddr, daddr, faddr;
	u8  tos;
	int err;

	pr_debug("ping_sendmsg(sk=%p,sk->num=%u)\n", inet, inet->inet_num);

int ping_common_sendmsg(int family, struct msghdr *msg, size_t len,
			void *user_icmph, size_t icmph_len) {
	u8 type, code;

	if (len > 0xFFFF)
		return -EMSGSIZE;
@@ -482,15 +643,53 @@ static int ping_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,

	/*
	 *	Fetch the ICMP header provided by the userland.
	 *	iovec is modified!
	 *	iovec is modified! The ICMP header is consumed.
	 */

	if (memcpy_fromiovec((u8 *)&user_icmph, msg->msg_iov,
			     sizeof(struct icmphdr)))
	if (memcpy_fromiovec(user_icmph, msg->msg_iov, icmph_len))
		return -EFAULT;
	if (!ping_supported(user_icmph.type, user_icmph.code))

	if (family == AF_INET) {
		type = ((struct icmphdr *) user_icmph)->type;
		code = ((struct icmphdr *) user_icmph)->code;
#if IS_ENABLED(CONFIG_IPV6)
	} else if (family == AF_INET6) {
		type = ((struct icmp6hdr *) user_icmph)->icmp6_type;
		code = ((struct icmp6hdr *) user_icmph)->icmp6_code;
#endif
	} else {
		BUG();
	}

	if (!ping_supported(family, type, code))
		return -EINVAL;

	return 0;
}
EXPORT_SYMBOL_GPL(ping_common_sendmsg);

int ping_v4_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
		    size_t len)
{
	struct net *net = sock_net(sk);
	struct flowi4 fl4;
	struct inet_sock *inet = inet_sk(sk);
	struct ipcm_cookie ipc;
	struct icmphdr user_icmph;
	struct pingfakehdr pfh;
	struct rtable *rt = NULL;
	struct ip_options_data opt_copy;
	int free = 0;
	__be32 saddr, daddr, faddr;
	u8  tos;
	int err;

	pr_debug("ping_v4_sendmsg(sk=%p,sk->num=%u)\n", inet, inet->inet_num);

	err = ping_common_sendmsg(AF_INET, msg, len, &user_icmph,
				  sizeof(user_icmph));
	if (err)
		return err;

	/*
	 *	Get and verify the address.
	 */
@@ -595,13 +794,14 @@ static int ping_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
	pfh.icmph.un.echo.sequence = user_icmph.un.echo.sequence;
	pfh.iov = msg->msg_iov;
	pfh.wcheck = 0;
	pfh.family = AF_INET;

	err = ip_append_data(sk, &fl4, ping_getfrag, &pfh, len,
			0, &ipc, &rt, msg->msg_flags);
	if (err)
		ip_flush_pending_frames(sk);
	else
		err = ping_push_pending_frames(sk, &pfh, &fl4);
		err = ping_v4_push_pending_frames(sk, &pfh, &fl4);
	release_sock(sk);

out:
@@ -622,11 +822,13 @@ static int ping_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
	goto out;
}

static int ping_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
int ping_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
		 size_t len, int noblock, int flags, int *addr_len)
{
	struct inet_sock *isk = inet_sk(sk);
	struct sockaddr_in *sin = (struct sockaddr_in *)msg->msg_name;
	int family = sk->sk_family;
	struct sockaddr_in *sin;
	struct sockaddr_in6 *sin6;
	struct sk_buff *skb;
	int copied, err;

@@ -636,11 +838,22 @@ static int ping_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
	if (flags & MSG_OOB)
		goto out;

	if (addr_len)
	if (addr_len) {
		if (family == AF_INET)
			*addr_len = sizeof(*sin);
		else if (family == AF_INET6 && addr_len)
			*addr_len = sizeof(*sin6);
	}

	if (flags & MSG_ERRQUEUE)
	if (flags & MSG_ERRQUEUE) {
		if (family == AF_INET) {
			return ip_recv_error(sk, msg, len);
#if IS_ENABLED(CONFIG_IPV6)
		} else if (family == AF_INET6) {
			return pingv6_ops.ipv6_recv_error(sk, msg, len);
#endif
		}
	}

	skb = skb_recv_datagram(sk, flags, noblock, &err);
	if (!skb)
@@ -659,15 +872,40 @@ static int ping_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,

	sock_recv_timestamp(msg, sk, skb);

	/* Copy the address. */
	if (sin) {
	/* Copy the address and add cmsg data. */
	if (family == AF_INET) {
		sin = (struct sockaddr_in *) msg->msg_name;
		sin->sin_family = AF_INET;
		sin->sin_port = 0 /* skb->h.uh->source */;
		sin->sin_addr.s_addr = ip_hdr(skb)->saddr;
		memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
	}

		if (isk->cmsg_flags)
			ip_cmsg_recv(msg, skb);

#if IS_ENABLED(CONFIG_IPV6)
	} else if (family == AF_INET6) {
		struct ipv6_pinfo *np = inet6_sk(sk);
		struct ipv6hdr *ip6 = ipv6_hdr(skb);
		sin6 = (struct sockaddr_in6 *) msg->msg_name;
		sin6->sin6_family = AF_INET6;
		sin6->sin6_port = 0;
		sin6->sin6_addr = ip6->saddr;

		if (np->sndflow)
			sin6->sin6_flowinfo = ip6_flowinfo(ip6);

		if (__ipv6_addr_needs_scope_id(
		    ipv6_addr_type(&sin6->sin6_addr)))
			sin6->sin6_scope_id = IP6CB(skb)->iif;

		if (inet6_sk(sk)->rxopt.all)
			pingv6_ops.ip6_datagram_recv_ctl(sk, msg, skb);
#endif
	} else {
		BUG();
	}

	err = copied;

done:
@@ -676,8 +914,9 @@ static int ping_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
	pr_debug("ping_recvmsg -> %d\n", err);
	return err;
}
EXPORT_SYMBOL_GPL(ping_recvmsg);

static int ping_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
int ping_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
{
	pr_debug("ping_queue_rcv_skb(sk=%p,sk->num=%d,skb=%p)\n",
		 inet_sk(sk), inet_sk(sk)->inet_num, skb);
@@ -688,6 +927,7 @@ static int ping_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
	}
	return 0;
}
EXPORT_SYMBOL_GPL(ping_queue_rcv_skb);


/*
@@ -698,10 +938,7 @@ void ping_rcv(struct sk_buff *skb)
{
	struct sock *sk;
	struct net *net = dev_net(skb->dev);
	struct iphdr *iph = ip_hdr(skb);
	struct icmphdr *icmph = icmp_hdr(skb);
	__be32 saddr = iph->saddr;
	__be32 daddr = iph->daddr;

	/* We assume the packet has already been checked by icmp_rcv */

@@ -711,8 +948,7 @@ void ping_rcv(struct sk_buff *skb)
	/* Push ICMP header back */
	skb_push(skb, skb->data - (u8 *)icmph);

	sk = ping_v4_lookup(net, saddr, daddr, ntohs(icmph->un.echo.id),
			    skb->dev->ifindex);
	sk = ping_lookup(net, skb, ntohs(icmph->un.echo.id));
	if (sk != NULL) {
		pr_debug("rcv on socket %p\n", sk);
		ping_queue_rcv_skb(sk, skb_get(skb));
@@ -723,6 +959,7 @@ void ping_rcv(struct sk_buff *skb)

	/* We're called from icmp_rcv(). kfree_skb() is done there. */
}
EXPORT_SYMBOL_GPL(ping_rcv);

struct proto ping_prot = {
	.name =		"PING",
@@ -733,14 +970,14 @@ struct proto ping_prot = {
	.disconnect =	udp_disconnect,
	.setsockopt =	ip_setsockopt,
	.getsockopt =	ip_getsockopt,
	.sendmsg =	ping_sendmsg,
	.sendmsg =	ping_v4_sendmsg,
	.recvmsg =	ping_recvmsg,
	.bind =		ping_bind,
	.backlog_rcv =	ping_queue_rcv_skb,
	.release_cb =	ip4_datagram_release_cb,
	.hash =		ping_v4_hash,
	.unhash =	ping_v4_unhash,
	.get_port =	ping_v4_get_port,
	.hash =		ping_hash,
	.unhash =	ping_unhash,
	.get_port =	ping_get_port,
	.obj_size =	sizeof(struct inet_sock),
};
EXPORT_SYMBOL(ping_prot);
Loading