Loading include/net/ipv6.h +6 −0 Original line number Diff line number Diff line Loading @@ -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); Loading include/net/ping.h +47 −3 Original line number Diff line number Diff line Loading @@ -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 */ Loading @@ -28,6 +29,19 @@ */ #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 *addr_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, const struct net_device *dev, int strict); }; struct ping_table { struct hlist_nulls_head hash[PING_HTABLE_SIZE]; rwlock_t lock; Loading @@ -39,10 +53,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); Loading @@ -50,6 +93,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 */ include/net/transp_v6.h +3 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); Loading net/ipv4/icmp.c +3 −2 Original line number Diff line number Diff line Loading @@ -939,7 +939,8 @@ error: 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); Loading @@ -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; } Loading net/ipv4/ping.c +399 −160 Original line number Diff line number Diff line Loading @@ -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> Loading @@ -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; Loading @@ -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) Loading @@ -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; Loading Loading @@ -103,6 +113,10 @@ next_port: 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)) Loading @@ -125,17 +139,18 @@ fail: 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); Loading @@ -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; Loading Loading @@ -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(); Loading @@ -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); Loading @@ -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; Loading @@ -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; Loading @@ -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); } /* Loading @@ -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 */ Loading @@ -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: Loading @@ -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: Loading Loading @@ -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; Loading @@ -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); Loading @@ -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; Loading @@ -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. */ Loading Loading @@ -595,13 +794,14 @@ back_from_confirm: 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: Loading @@ -622,10 +822,13 @@ do_confirm: 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); int family = sk->sk_family; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; struct sk_buff *skb; int copied, err; Loading @@ -635,8 +838,23 @@ static int ping_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, if (flags & MSG_OOB) goto out; if (flags & MSG_ERRQUEUE) 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 (family == AF_INET) { return ip_recv_error(sk, msg, len, addr_len); #if IS_ENABLED(CONFIG_IPV6) } else if (family == AF_INET6) { return pingv6_ops.ipv6_recv_error(sk, msg, len, addr_len); #endif } } skb = skb_recv_datagram(sk, flags, noblock, &err); if (!skb) Loading @@ -655,18 +873,40 @@ static int ping_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, sock_recv_timestamp(msg, sk, skb); /* Copy the address. */ if (msg->msg_name) { struct sockaddr_in *sin = (struct sockaddr_in *)msg->msg_name; /* 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)); *addr_len = sizeof(*sin); } 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: Loading @@ -675,8 +915,9 @@ out: 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); Loading @@ -687,6 +928,7 @@ static int ping_queue_rcv_skb(struct sock *sk, struct sk_buff *skb) } return 0; } EXPORT_SYMBOL_GPL(ping_queue_rcv_skb); /* Loading @@ -697,10 +939,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 */ Loading @@ -710,8 +949,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)); Loading @@ -722,6 +960,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", Loading @@ -732,14 +971,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 Loading
include/net/ipv6.h +6 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
include/net/ping.h +47 −3 Original line number Diff line number Diff line Loading @@ -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 */ Loading @@ -28,6 +29,19 @@ */ #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 *addr_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, const struct net_device *dev, int strict); }; struct ping_table { struct hlist_nulls_head hash[PING_HTABLE_SIZE]; rwlock_t lock; Loading @@ -39,10 +53,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); Loading @@ -50,6 +93,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 */
include/net/transp_v6.h +3 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); Loading
net/ipv4/icmp.c +3 −2 Original line number Diff line number Diff line Loading @@ -939,7 +939,8 @@ error: 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); Loading @@ -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; } Loading
net/ipv4/ping.c +399 −160 Original line number Diff line number Diff line Loading @@ -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> Loading @@ -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; Loading @@ -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) Loading @@ -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; Loading Loading @@ -103,6 +113,10 @@ next_port: 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)) Loading @@ -125,17 +139,18 @@ fail: 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); Loading @@ -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; Loading Loading @@ -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(); Loading @@ -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); Loading @@ -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; Loading @@ -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; Loading @@ -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); } /* Loading @@ -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 */ Loading @@ -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: Loading @@ -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: Loading Loading @@ -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; Loading @@ -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); Loading @@ -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; Loading @@ -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. */ Loading Loading @@ -595,13 +794,14 @@ back_from_confirm: 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: Loading @@ -622,10 +822,13 @@ do_confirm: 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); int family = sk->sk_family; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; struct sk_buff *skb; int copied, err; Loading @@ -635,8 +838,23 @@ static int ping_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, if (flags & MSG_OOB) goto out; if (flags & MSG_ERRQUEUE) 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 (family == AF_INET) { return ip_recv_error(sk, msg, len, addr_len); #if IS_ENABLED(CONFIG_IPV6) } else if (family == AF_INET6) { return pingv6_ops.ipv6_recv_error(sk, msg, len, addr_len); #endif } } skb = skb_recv_datagram(sk, flags, noblock, &err); if (!skb) Loading @@ -655,18 +873,40 @@ static int ping_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, sock_recv_timestamp(msg, sk, skb); /* Copy the address. */ if (msg->msg_name) { struct sockaddr_in *sin = (struct sockaddr_in *)msg->msg_name; /* 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)); *addr_len = sizeof(*sin); } 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: Loading @@ -675,8 +915,9 @@ out: 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); Loading @@ -687,6 +928,7 @@ static int ping_queue_rcv_skb(struct sock *sk, struct sk_buff *skb) } return 0; } EXPORT_SYMBOL_GPL(ping_queue_rcv_skb); /* Loading @@ -697,10 +939,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 */ Loading @@ -710,8 +949,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)); Loading @@ -722,6 +960,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", Loading @@ -732,14 +971,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