From 2ef6ebabb62eb23b5eda28c5eb0aba4027110860 Mon Sep 17 00:00:00 2001 From: Qiao Ma Date: Wed, 18 Nov 2020 10:50:02 +0800 Subject: [PATCH 2568/2944] alinux: net: add pingtrace feature support to #32701737 PingTrace is a ICMP based network protocol, which is used to debug network problems. It reuses ICMP echo/echoreply packets to collect different position's timestamps of its transmission link, then calculates delays between these positions, and detemine which transmission phase might the problem occur in. This patch adds kernel's native supporting of PingTrace, so the remote host could support PingTrace protocol without running a server, which is similar to the ping tool. Signed-off-by: Qiao Ma Acked-by: Tony Lu --- Documentation/networking/ip-sysctl.txt | 22 ++++ include/linux/skbuff.h | 3 + include/net/netns/ipv4.h | 4 + include/net/pingtrace.h | 69 ++++++++++++ include/uapi/linux/pingtrace.h | 57 ++++++++++ include/uapi/linux/snmp.h | 3 + net/core/dev.c | 13 +++ net/ipv4/Kconfig | 10 ++ net/ipv4/Makefile | 2 + net/ipv4/icmp.c | 15 +++ net/ipv4/pingtrace.c | 198 +++++++++++++++++++++++++++++++++ net/ipv4/proc.c | 7 ++ net/ipv4/sysctl_net_ipv4.c | 9 ++ 13 files changed, 412 insertions(+) create mode 100644 include/net/pingtrace.h create mode 100644 include/uapi/linux/pingtrace.h create mode 100644 net/ipv4/pingtrace.c diff --git a/Documentation/networking/ip-sysctl.txt b/Documentation/networking/ip-sysctl.txt index f4f9ae2..e8afacc 100644 --- a/Documentation/networking/ip-sysctl.txt +++ b/Documentation/networking/ip-sysctl.txt @@ -1007,6 +1007,28 @@ icmp_errors_use_inbound_ifaddr - BOOLEAN Default: 0 +icmp_pingtrace - BOOLEAN + PingTrace is a protocol based on ICMP echo/echoreply types, which is + used to collect different position's timestamps of its transmission + link, and then calculates delays between these positions, to detemine + which transmission phase might the problem occur in. + + If non-zero, kernel will enable native supporting for PingTrace + protocol. It will automatically detect and add some position's timestamp + infomations to pingtrace packet. + + Default: 0 + +icmp_pingtrace_node_id - INTEGER + PingTrace protocol uses node id to distinguish different hosts, which + could help pingtrace packet initiator to debug and solving networking + problems. + + pingtrace_node_id is used to specific current host node id. Note that + this is a 64-bit integer. + + Default: 0 + igmp_max_memberships - INTEGER Change the maximum number of multicast groups we can subscribe to. Default: 20 diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 8519b55..29e9b48c 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -799,6 +799,9 @@ struct sk_buff { #ifdef CONFIG_TLS_DEVICE __u8 decrypted:1; #endif +#ifdef CONFIG_ICMP_PINGTRACE + __u8 icmp_pingtrace:1; +#endif #ifdef CONFIG_NET_SCHED __u16 tc_index; /* traffic control index */ diff --git a/include/net/netns/ipv4.h b/include/net/netns/ipv4.h index 373d3e0..7fad6d5 100644 --- a/include/net/netns/ipv4.h +++ b/include/net/netns/ipv4.h @@ -91,6 +91,10 @@ struct netns_ipv4 { int sysctl_icmp_ratemask; int sysctl_icmp_errors_use_inbound_ifaddr; +#ifdef CONFIG_ICMP_PINGTRACE + u64 sysctl_icmp_pingtrace_node_id; +#endif + struct local_ports ip_local_ports; int sysctl_tcp_ecn; diff --git a/include/net/pingtrace.h b/include/net/pingtrace.h new file mode 100644 index 0000000..b4eb9fe --- /dev/null +++ b/include/net/pingtrace.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018 Alibaba Group + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef _PINGTRACE_H +#define _PINGTRACE_H + +#include + +struct sk_buff; + +#define PINGTRACE_CODE_MAGIC 1 +#define PINGTRACE_HDR_MAGIC 0x7ace + +#define PINGTRACE_F_ECHO 1 +#define PINGTRACE_F_ECHOREPLY 2 +#define PINGTRACE_F_BOTH 3 +#define PINGTRACE_F_CALCULATE_CHECKSUM 4 + +enum pingtrace_function { + P_L_TX_USER, + P_L_TX_DEVQUEUE, + P_L_TX_DEVOUT, + P_R_RX_ICMPRCV, + P_R_TX_DEVOUT, + P_L_RX_IPRCV, + P_L_RX_SKDATAREADY, + P_L_RX_WAKEUP, + P_L_RX_USER, +}; + +enum PINGTRACE_HDR_FLAGS { + PINGTRACE_F_DONTADD = 1, +}; + +struct pingtrace_timestamp { + u64 node_id; + u32 function_id; + u32 ts; +}; + +struct pingtrace_hdr { + u8 version; + u8 num; + u16 flags; + u16 magic; + u16 reserve; + u32 id; + u32 seq; +}; + +struct pingtrace_pkt { + struct pingtrace_hdr hdr; + struct pingtrace_timestamp entries[]; +}; + +DECLARE_STATIC_KEY_FALSE(pingtrace_control); + +int skb_pingtrace_check(struct sk_buff *skb, u64 flags); +int skb_pingtrace_add_ts(struct sk_buff *skb, struct net *net, u32 function_id, + u64 flags); + +#endif /* _PINGTRACE_H */ diff --git a/include/uapi/linux/pingtrace.h b/include/uapi/linux/pingtrace.h new file mode 100644 index 0000000..a3538de --- /dev/null +++ b/include/uapi/linux/pingtrace.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018 Alibaba Group + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#ifndef __LINUX_PING_TRACE_H +#define __LINUX_PING_TRACE_H + +#include + +#define PINGTRACE_CODE_MAGIC 1 +#define PINGTRACE_HDR_MAGIC 0x7ace +#define PINGTRACE_MAP_ENTRY_NUM 8 + +enum pingtrace_function { + P_L_TX_USER, + P_L_TX_DEVQUEUE, + P_L_TX_DEVOUT, + P_R_RX_ICMPRCV, + P_R_TX_DEVOUT, + P_L_RX_IPRCV, + P_L_RX_SKDATAREADY, + P_L_RX_WAKEUP, + P_L_RX_USER, +}; + +enum PINGTRACE_HDR_FLAGS { + PINGTRACE_F_DONTADD = 1, +}; + +struct pingtrace_timestamp { + u64 node_id; + u32 function_id; + u32 ts; +}; + +struct pingtrace_hdr { + u8 version; + u8 num; + u16 flags; + u16 magic; + u16 reserve; + u32 id; + u32 seq; +}; + +struct pingtrace_pkt { + struct pingtrace_hdr hdr; + struct pingtrace_timestamp entries[]; +}; + +#endif diff --git a/include/uapi/linux/snmp.h b/include/uapi/linux/snmp.h index abae27c..f333db5 100644 --- a/include/uapi/linux/snmp.h +++ b/include/uapi/linux/snmp.h @@ -95,6 +95,9 @@ enum ICMP_MIB_OUTADDRMASKS, /* OutAddrMasks */ ICMP_MIB_OUTADDRMASKREPS, /* OutAddrMaskReps */ ICMP_MIB_CSUMERRORS, /* InCsumErrors */ +#ifdef CONFIG_ICMP_PINGTRACE + ICMP_MIB_INPINGTRACEMSG, /* PingTrace */ +#endif __ICMP_MIB_MAX }; diff --git a/net/core/dev.c b/net/core/dev.c index 8ff21d4..f933301 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -145,6 +145,7 @@ #include #include #include +#include #include "net-sysfs.h" @@ -3265,10 +3266,22 @@ struct sk_buff *dev_hard_start_xmit(struct sk_buff *first, struct net_device *de { struct sk_buff *skb = first; int rc = NETDEV_TX_OK; +#ifdef CONFIG_ICMP_PINGTRACE + struct net *net = dev_net(dev); +#endif while (skb) { struct sk_buff *next = skb->next; +#ifdef CONFIG_ICMP_PINGTRACE + if (static_branch_unlikely(&pingtrace_control) && + unlikely(skb->icmp_pingtrace) && + skb_pingtrace_check(skb, PINGTRACE_F_ECHOREPLY)) { + skb_pingtrace_add_ts(skb, net, P_R_TX_DEVOUT, + PINGTRACE_F_CALCULATE_CHECKSUM); + } +#endif + skb->next = NULL; rc = xmit_one(skb, dev, txq, next != NULL); if (unlikely(!dev_xmit_complete(rc))) { diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig index b2263d2..966c923 100644 --- a/net/ipv4/Kconfig +++ b/net/ipv4/Kconfig @@ -768,3 +768,13 @@ config TCP_RT tcprt module support. source "net/ipv4/tcp_rt/Kconfig" + +config ICMP_PINGTRACE + bool "ICMP pingtrace support" + default n + help + PingTrace is a protocol based on ICMP echo/echoreply packets, and + is used for solving network problems. + + This is used for automatically identify and support pingtrace + protocols, which is similar to kernel's native support of ping packet. diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile index 746d41d..fed265a 100644 --- a/net/ipv4/Makefile +++ b/net/ipv4/Makefile @@ -68,5 +68,7 @@ obj-$(CONFIG_NETLABEL) += cipso_ipv4.o obj-$(CONFIG_TCP_RT) += tcp_rt.o obj-$(CONFIG_TCP_RT_MODULE) += tcp_rt/ +obj-$(CONFIG_ICMP_PINGTRACE) += pingtrace.o + obj-$(CONFIG_XFRM) += xfrm4_policy.o xfrm4_state.o xfrm4_input.o \ xfrm4_output.o xfrm4_protocol.o diff --git a/net/ipv4/icmp.c b/net/ipv4/icmp.c index 65f1ddf..a2c7c3a 100644 --- a/net/ipv4/icmp.c +++ b/net/ipv4/icmp.c @@ -97,6 +97,7 @@ #include #include #include +#include /* * Build xmit assembly blocks @@ -357,6 +358,10 @@ static int icmp_glue_bits(void *from, char *to, int offset, int len, int odd, struct icmp_bxm *icmp_param = (struct icmp_bxm *)from; __wsum csum; +#ifdef CONFIG_ICMP_PINGTRACE + skb->icmp_pingtrace = icmp_param->skb->icmp_pingtrace; +#endif + csum = skb_copy_and_csum_bits(icmp_param->skb, icmp_param->offset + offset, to, len, 0); @@ -1007,6 +1012,16 @@ int icmp_rcv(struct sk_buff *skb) struct net *net = dev_net(rt->dst.dev); bool success; +#ifdef CONFIG_ICMP_PINGTRACE + if (static_branch_unlikely(&pingtrace_control) && + skb_pingtrace_check(skb, PINGTRACE_F_ECHO)) { + skb->icmp_pingtrace = 1; + __ICMP_INC_STATS(net, ICMP_MIB_INPINGTRACEMSG); + skb_pingtrace_add_ts(skb, net, P_R_RX_ICMPRCV, + PINGTRACE_F_CALCULATE_CHECKSUM); + } +#endif + if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) { struct sec_path *sp = skb_sec_path(skb); int nh; diff --git a/net/ipv4/pingtrace.c b/net/ipv4/pingtrace.c new file mode 100644 index 0000000..ffc46ed --- /dev/null +++ b/net/ipv4/pingtrace.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static unsigned int sysctl_pingtrace; +static unsigned int zero; +static unsigned int one = 1; + +DEFINE_STATIC_KEY_FALSE(pingtrace_control); + +static inline bool iphdr_check(struct iphdr *iph) +{ + return iph && iph->version == 4 && iph->protocol == IPPROTO_ICMP; +} + +static inline bool icmphdr_check(struct icmphdr *icmph, u64 flags) +{ + return icmph && + ((icmph->type == ICMP_ECHO && (flags & PINGTRACE_F_ECHO)) || + (icmph->type == ICMP_ECHOREPLY && (flags & PINGTRACE_F_ECHOREPLY))) && + icmph->code == PINGTRACE_CODE_MAGIC; +} + +static inline bool pingtracehdr_check(struct pingtrace_pkt *pt) +{ + return pt->hdr.version == 0 && + ntohs(pt->hdr.magic) == PINGTRACE_HDR_MAGIC; +} + +static inline u32 truncate_ts_usec(u64 usec) +{ + return usec & ((1UL << 31) - 1); +} + +static inline u32 get_current_usec(void) +{ + return truncate_ts_usec(ktime_get_mono_fast_ns() / 1000); +} + +static inline void +build_pingtrace_timestamp(struct pingtrace_timestamp *ts, struct net *net, + u32 function_id, u32 usec) +{ + ts->node_id = cpu_to_be64(net->ipv4.sysctl_icmp_pingtrace_node_id); + ts->function_id = htonl(function_id); + ts->ts = htonl(usec); +} + +static int pingtrace_add_ts(struct sk_buff *skb, struct pingtrace_pkt *pkt, + u32 packet_size, struct pingtrace_timestamp *entry) +{ + u32 offset, len; + + len = sizeof(struct pingtrace_hdr) + + sizeof(struct pingtrace_timestamp) * pkt->hdr.num; + if (len + sizeof(struct pingtrace_timestamp) > packet_size) + return -E2BIG; + + offset = (void *)pkt - (void *)(skb->data) + len; + skb_store_bits(skb, offset, entry, sizeof(*entry)); + pkt->hdr.num += 1; + return 0; +} + +static void +calculate_checksum(struct sk_buff *skb, struct icmphdr *icmph, u32 icmp_size) +{ + u32 offset = ((void *)icmph) - (void *)(skb->data); + + icmph->checksum = 0; + icmph->checksum = csum_fold(skb_checksum(skb, offset, icmp_size, 0)); +} + +static void header_pointer_set(struct sk_buff *skb, struct iphdr **piph, + struct icmphdr **picmph, + struct pingtrace_pkt **ppt) +{ + *piph = ip_hdr(skb); + *picmph = icmp_hdr(skb); + *ppt = (void *)((*picmph) + 1); +} + +static int do_skb_pingtrace_check(struct pingtrace_pkt *pt, + struct icmphdr *icmph, struct iphdr *iph, + u64 flags) +{ + if (!(iphdr_check(iph) && icmphdr_check(icmph, flags) && + pingtracehdr_check(pt))) + return 0; + return 1; +} + +int skb_pingtrace_check(struct sk_buff *skb, u64 flags) +{ + struct pingtrace_pkt *pt; + struct icmphdr *icmph; + struct iphdr *iph; + + header_pointer_set(skb, &iph, &icmph, &pt); + return do_skb_pingtrace_check(pt, icmph, iph, flags); +} + +static inline int icmp_packet_size(struct icmphdr *icmph, struct iphdr *iph) +{ + return ntohs(iph->tot_len) - ((void *)icmph - (void *)iph); +} + +static inline bool is_dontadd_flag_set(struct pingtrace_pkt *pt) +{ + u16 flags = ntohs(pt->hdr.flags); + + return flags & PINGTRACE_F_DONTADD; +} + +static void +pingtrace_process_flags(struct sk_buff *skb, struct icmphdr *icmph, + struct pingtrace_pkt *pt, int icmp_size, u64 flags) +{ + if (flags & PINGTRACE_F_CALCULATE_CHECKSUM) + calculate_checksum(skb, icmph, icmp_size); +} + +int skb_pingtrace_add_ts(struct sk_buff *skb, struct net *net, u32 function_id, + u64 flags) +{ + struct pingtrace_pkt *pt; + struct icmphdr *icmph; + struct iphdr *iph; + struct pingtrace_timestamp entry; + int ret = 0, pt_size, icmp_size; + u32 usec; + + header_pointer_set(skb, &iph, &icmph, &pt); + icmp_size = icmp_packet_size(icmph, iph); + pt_size = icmp_size - sizeof(*icmph); + + if (is_dontadd_flag_set(pt)) + goto out; + + usec = truncate_ts_usec(get_current_usec()); + build_pingtrace_timestamp(&entry, net, function_id, usec); + ret = pingtrace_add_ts(skb, pt, pt_size, &entry); + +out: + pingtrace_process_flags(skb, icmph, pt, icmp_size, flags); + return ret; +} + +static int pingtrace_sysctl_proc(struct ctl_table *table, int write, + void __user *buffer, size_t *lenp, + loff_t *ppos) +{ + int ret; + unsigned int old_value = sysctl_pingtrace; + + ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos); + if (ret == 0 && write && sysctl_pingtrace != old_value) { + if (!sysctl_pingtrace) + static_branch_disable(&pingtrace_control); + else + static_branch_enable(&pingtrace_control); + } + return ret; +} + +static __attribute__((unused)) struct ctl_table pingtrace_table[] = { + { + .procname = "icmp_pingtrace", + .data = &sysctl_pingtrace, + .maxlen = sizeof(unsigned int), + .mode = 0644, + .proc_handler = pingtrace_sysctl_proc, + .extra1 = &zero, + .extra2 = &one, + }, + {} +}; + +static __init int sysctl_pingtrace_init(void) +{ + struct ctl_table_header *header; + + header = register_net_sysctl(&init_net, "net/ipv4", pingtrace_table); + if (IS_ERR(header)) + return PTR_ERR(header); + return 0; +} + +fs_initcall(sysctl_pingtrace_init); diff --git a/net/ipv4/proc.c b/net/ipv4/proc.c index eab5c02..70e785d 100644 --- a/net/ipv4/proc.c +++ b/net/ipv4/proc.c @@ -349,6 +349,9 @@ static void icmp_put(struct seq_file *seq) seq_puts(seq, " OutMsgs OutErrors"); for (i = 0; icmpmibmap[i].name; i++) seq_printf(seq, " Out%s", icmpmibmap[i].name); +#ifdef CONFIG_ICMP_PINGTRACE + seq_puts(seq, " InPingTraceMsgs"); +#endif seq_printf(seq, "\nIcmp: %lu %lu %lu", snmp_fold_field(net->mib.icmp_statistics, ICMP_MIB_INMSGS), snmp_fold_field(net->mib.icmp_statistics, ICMP_MIB_INERRORS), @@ -362,6 +365,10 @@ static void icmp_put(struct seq_file *seq) for (i = 0; icmpmibmap[i].name; i++) seq_printf(seq, " %lu", atomic_long_read(ptr + (icmpmibmap[i].index | 0x100))); +#ifdef CONFIG_ICMP_PINGTRACE + seq_printf(seq, " %lu", + snmp_fold_field(net->mib.icmp_statistics, ICMP_MIB_INPINGTRACEMSG)); +#endif } /* diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c index fb45c24..f7c0d7f 100644 --- a/net/ipv4/sysctl_net_ipv4.c +++ b/net/ipv4/sysctl_net_ipv4.c @@ -620,6 +620,15 @@ static int proc_tcp_rto_min(struct ctl_table *table, .mode = 0644, .proc_handler = proc_dointvec }, +#ifdef CONFIG_ICMP_PINGTRACE + { + .procname = "icmp_pingtrace_node_id", + .data = &init_net.ipv4.sysctl_icmp_pingtrace_node_id, + .maxlen = sizeof(u64), + .mode = 0644, + .proc_handler = proc_doulongvec_minmax, + }, +#endif { .procname = "ping_group_range", .data = &init_net.ipv4.ping_group_range.range, -- 1.8.3.1