/* * Monitoring code for network dropped packet alerts * * Copyright (C) 2018 SAMSUNG Electronics, Co,LTD */ #include #include #if defined(CONFIG_ANDROID_VENDOR_HOOKS) #include #endif #include /******************************************************************* To extend dropdump to all dropped packets, do followings 1. move kfree_skb() trace point to __kfree_skb() //net/core/skbuff.c void __kfree_skb(struct sk_buff *skb) { + trace_kfree_skb(skb, __builtin_return_address(0)); skb_release_all(skb); kfree_skbmem(skb); } void kfree_skb(struct sk_buff *skb) { if (!skb_unref(skb)) return; - trace_kfree_skb(skb, __builtin_return_address(0)); __kfree_skb(skb); } 2. Enable definition for 'EXTENDED_DROPDUMP' *******************************************************************/ //#define EXTENDED_DROPDUMP /******************************************************************/ int debug_drd = 0; module_param(debug_drd, int, S_IRUGO | S_IWUSR | S_IWGRP); MODULE_PARM_DESC(debug_drd, "dropdump debug flags"); #define drd_info(fmt, ...) pr_info("drd: %s: " pr_fmt(fmt), __func__, ##__VA_ARGS__) #define drd_dbg(...) \ do { \ if (unlikely(debug_drd)) { drd_info(__VA_ARGS__); } \ else {} \ } while (0) DEFINE_RATELIMIT_STATE(drd_ratelimit_state, 1 * HZ, 10); #define drd_limit(...) \ do { \ if (__ratelimit(&drd_ratelimit_state)) \ drd_info(__VA_ARGS__); \ } while (0) static inline int deliver_skb(struct sk_buff *skb, struct packet_type *pt_prev, struct net_device *orig_dev) { if (unlikely(skb_orphan_frags_rx(skb, GFP_ATOMIC))) return -ENOMEM; refcount_inc(&skb->users); return pt_prev->func(skb, skb->dev, pt_prev, orig_dev); } /* add definition for logging */ #define ETH_P_LOG 0x00FA struct list_head ptype_log __read_mostly; EXPORT_SYMBOL_GPL(ptype_log); int support_dropdump; EXPORT_SYMBOL_GPL(support_dropdump); #define ST_MAX 20 #define ST_SIZE 0x30 #ifdef EXTENDED_DROPDUMP #define ST_START 5 #else #define ST_START 4 #endif static char save_stack[NR_CPUS][ST_SIZE * ST_MAX]; /* use direct call instead of recursive stack trace */ char *__stack(int depth) { char *func = NULL; switch (depth + ST_START) { case 3 : func = __builtin_return_address(3); break; case 4 : func = __builtin_return_address(4); break; case 5 : func = __builtin_return_address(5); break; case 6 : func = __builtin_return_address(6); break; case 7 : func = __builtin_return_address(7); break; case 8 : func = __builtin_return_address(8); break; case 9 : func = __builtin_return_address(9); break; case 10 : func = __builtin_return_address(10); break; case 11 : func = __builtin_return_address(11); break; case 12 : func = __builtin_return_address(12); break; case 13 : func = __builtin_return_address(13); break; case 14 : func = __builtin_return_address(14); break; case 15 : func = __builtin_return_address(15); break; case 16 : func = __builtin_return_address(16); break; case 17 : func = __builtin_return_address(17); break; case 18 : func = __builtin_return_address(18); break; case 19 : func = __builtin_return_address(19); break; case 20 : func = __builtin_return_address(20); break; case 21 : func = __builtin_return_address(21); break; case 22 : func = __builtin_return_address(22); break; case 23 : func = __builtin_return_address(23); break; case 24 : func = __builtin_return_address(24); break; default : return NULL; } return func; } #define NOT_TRACE (-1) #define FIN_TRACE 1 #define ACK_TRACE 2 #define GET_TRACE 3 int chk_stack(char *pos, int net_pkt) { /* stop tracing */ if (!strncmp(pos, "unix", 4)) // unix_xxx return NOT_TRACE; if (!strncmp(pos + 2, "tlin", 4)) // netlink_xxx return NOT_TRACE; if (!strncmp(pos, "tpac", 4)) // tpacket_rcv return NOT_TRACE; if (!strncmp(pos, "drd", 3)) // drd_xxx return NOT_TRACE; if (!strncmp(pos + 1, "_sk_d", 5))// __sk_destruct return NOT_TRACE; #ifdef EXTENDED_DROPDUMP /* ignore normally consumed packets on TX path */ if (!strncmp(pos + 2, "it_on", 5))// xmit_one return NOT_TRACE; if (!strncmp(pos + 2, "t_tx_", 5))// net_tx_action return NOT_TRACE; if (!strncmp(pos, "dp_tx", 5)) //dp_tx_comp_xxx return NOT_TRACE; /* prevent recursive call by __kfree_skb() */ if (!strncmp(pos + 4, "ree_s", 5))// __kfree_skb return NOT_TRACE; #endif /* end of callstack */ if (!strncmp(pos + 7, "_bh_", 4))// __local_bh_xxx return FIN_TRACE; if (!strncmp(pos + 7, "ftir", 4))// __do_softirq return FIN_TRACE; if (!strncmp(pos + 7, "rk_r", 4))// task_work_run return FIN_TRACE; if (!strncmp(pos, "SyS", 3)) // SyS_xxx return FIN_TRACE; if (!strncmp(pos, "ret_", 4)) // ret_from_xxx return FIN_TRACE; if (!strncmp(pos, "el0", 3)) // el0_irq return FIN_TRACE; if (!strncmp(pos, "el1", 3)) // el1_irq return FIN_TRACE; if (!strncmp(pos, "gic", 3)) // gic_xxx return FIN_TRACE; if (!strncmp(pos + 4, "f_nbu", 5))// __qdf_nbuf_free return FIN_TRACE; /* network pkt */ if (!net_pkt) { if (!strncmp(pos, "net", 3)) return GET_TRACE; if (!strncmp(pos, "tcp", 3)) { // packet from tcp_drop() could be normal operation. // don't logging pure ack. if (!strncmp(pos, "tcp_drop", 8)) return ACK_TRACE; return GET_TRACE; } if (!strncmp(pos, "ip", 2)) return GET_TRACE; if (!strncmp(pos, "xfr", 3)) return GET_TRACE; } return 0; } static bool _is_tcp_ack(struct sk_buff *skb) { switch (skb->protocol) { /* TCPv4 ACKs */ case htons(ETH_P_IP): if ((ip_hdr(skb)->protocol == IPPROTO_TCP) && (ntohs(ip_hdr(skb)->tot_len) - (ip_hdr(skb)->ihl << 2) == tcp_hdr(skb)->doff << 2) && ((tcp_flag_word(tcp_hdr(skb)) & cpu_to_be32(0x00FF0000)) == TCP_FLAG_ACK)) return true; break; /* TCPv6 ACKs */ case htons(ETH_P_IPV6): if ((ipv6_hdr(skb)->nexthdr == IPPROTO_TCP) && (ntohs(ipv6_hdr(skb)->payload_len) == (tcp_hdr(skb)->doff) << 2) && ((tcp_flag_word(tcp_hdr(skb)) & cpu_to_be32(0x00FF0000)) == TCP_FLAG_ACK)) return true; break; } return false; } static inline bool is_tcp_ack(struct sk_buff *skb) { if (skb_is_tcp_pure_ack(skb)) return true; if (unlikely(_is_tcp_ack(skb))) return true; return false; } int pr_stack(struct sk_buff *skb, char *dst) { int n = 0, depth = 0, chk = 0, net_pkt = 0; char pos[ST_SIZE], *st; for (depth = 0; depth < ST_MAX; depth++) { st = __stack(depth); if (st) { n = snprintf(pos, ST_SIZE, "%pS", st); if (n < ST_SIZE) memset(pos + n, 0, ST_SIZE - n); else n = ST_SIZE; chk = chk_stack(pos, net_pkt); drd_dbg("[%2d:%d] <%s>\n", depth, chk, pos); if (chk < 0) return NOT_TRACE; if (chk == FIN_TRACE) break; if (chk == ACK_TRACE) { if (is_tcp_ack(skb)) { drd_dbg("don't trace tcp ack\n"); return NOT_TRACE; } else net_pkt = 1; } if (chk == GET_TRACE) net_pkt = 1; memcpy(dst + ST_SIZE * depth, pos, ST_SIZE); } else { /* end of callstack */ depth--; break; } } if (net_pkt == 0) { drd_dbg("not defined packet\n"); return -3; } return depth > 5 ? depth - 4 : depth; /* do not use root stacks */ } struct sk_buff *get_dummy(struct sk_buff *skb, unsigned int reason, char *pos, int st_depth) { struct sk_buff *dummy = NULL; unsigned int stack_len = ST_SIZE * st_depth; /* initialize dummy packet with ipv4 : iphdr + udp_hdr + align */ unsigned int hdr_len, hdr_tot_len, hdr_align; unsigned int data_len, data_offset; struct udphdr *udph; if (skb->protocol == htons(ETH_P_IP)) { hdr_len = (unsigned int)sizeof(struct iphdr); hdr_align = 4; } else { hdr_len = (unsigned int)sizeof(struct ipv6hdr); hdr_align = 0; } hdr_tot_len = hdr_len + (unsigned int)sizeof(struct udphdr); data_len = hdr_tot_len + hdr_align; data_offset = data_len; /* expand data_len */ data_len += stack_len; dummy = alloc_skb(data_len, GFP_ATOMIC); if (likely(dummy)) { /* set skb info */ memset(dummy->data, 0, data_offset); dummy->dev = skb->dev; dummy->hdr_len = hdr_tot_len; dummy->len = data_len; dummy->transport_header = hdr_len; dummy->protocol = skb->protocol; /* set ip_hdr info */ if (dummy->protocol == htons(ETH_P_IP)) { struct iphdr *iph = (struct iphdr *)dummy->data; memcpy(dummy->data, ip_hdr(skb), hdr_len); iph->protocol = 17; // UDP iph->tot_len = htons(data_len); iph->ttl = (__u8)(reason & 0xff); } else { struct ipv6hdr *ip6h = (struct ipv6hdr *)dummy->data; memcpy(dummy->data, ipv6_hdr(skb), hdr_len); ip6h->nexthdr = 17; // UDP ip6h->payload_len = htons(data_len - hdr_len); ip6h->hop_limit = (__u8)(reason & 0xff); } /* set udp_hdr info */ udph = udp_hdr(dummy); udph->len = htons(data_len - hdr_len); /* save callstack */ memcpy(dummy->data + data_offset, pos, stack_len); } else { drd_dbg("fail to alloc dummy\n"); } return dummy; } #if 0 // somtimes this log causes lock starvation when too many called (P230510-05174) // tempory disable until find smart solution static void drd_print_skb(struct sk_buff *skb, unsigned int len, unsigned int reason) { struct iphdr *ip4hdr = (struct iphdr *)skb_network_header(skb); if (ip4hdr->version == 4) { drd_limit("<%pS><%pS> [%u] src:%pI4 | dst:%pI4 | %*ph\n", __builtin_return_address(ST_START - 2), __builtin_return_address(ST_START-1), reason, &ip4hdr->saddr, &ip4hdr->daddr, len < 48 ? len : 48, ip4hdr); } else { struct ipv6hdr *ip6hdr = (struct ipv6hdr *)ip4hdr; drd_limit("<%pS><%pS> [%u] src:%pI6c | dst:%pI6c | %*ph\n", __builtin_return_address(ST_START - 2), __builtin_return_address(ST_START-1), reason, &ip6hdr->saddr, &ip6hdr->daddr, len < 48 ? len : 48, ip4hdr); } } #endif extern struct list_head ptype_all; struct sk_buff *drd_queue_skb(struct sk_buff *skb, int mode, unsigned int reason) { struct packet_type *ptype; struct packet_type *pt_prev = NULL; struct sk_buff *skb2 = NULL; struct list_head *ptype_list = &ptype_log; struct net_device *dev = NULL; struct net_device *old_dev; struct iphdr *ip4hdr = (struct iphdr *)(skb_network_header(skb)); struct ipv6hdr *ip6hdr; bool logged = false; unsigned int len, clen; static ktime_t tstamp_bck; int st_depth = 0; char *pos; static struct sk_buff *dmy_skb[NR_CPUS]; int cur_cpu = get_cpu();put_cpu(); if (mode) { if (dmy_skb[cur_cpu] == skb) return NULL; pos = (char *)save_stack + (ST_SIZE * ST_MAX) * (unsigned long)cur_cpu; st_depth = pr_stack(skb, pos); if (st_depth < 0) { drd_dbg("can't trace [%d]\n", st_depth); return NULL; } } if (ip4hdr->version == 4) { len = ntohs(ip4hdr->tot_len); } else { ip6hdr = (struct ipv6hdr *)ip4hdr; len = skb_network_header_len(skb) + ntohs(ip6hdr->payload_len); } if (mode) { clen = min(0x80u, len); /* set timestamp for dummy packet */ if (skb->tstamp >> 48 < 5000) { /* packet has kernel timestamp, not utc using a zero-value for updating to utc at tpacket_rcv() */ tstamp_bck = 0; } else { /* using utc of original packet */ tstamp_bck = skb->tstamp; } } else { clen = len; } rcu_read_lock_bh(); old_dev = skb->dev; if (skb->dev) dev = skb->dev; else if (skb_dst(skb) && skb_dst(skb)->dev) dev = skb_dst(skb)->dev; else dev = init_net.loopback_dev; skb->dev = dev; list_for_each_entry_rcu(ptype, ptype_list, list) { if (pt_prev) { deliver_skb(skb2, pt_prev, dev); pt_prev = ptype; logged = true; continue; } skb2 = netdev_alloc_skb(dev, clen); if (unlikely(!skb2)) { drd_dbg("alloc fail, %u\n", clen); goto out_unlock; } dmy_skb[cur_cpu] = skb2; skb2->protocol = skb->protocol; skb2->tstamp = mode ? skb->tstamp : tstamp_bck; skb_put(skb2, clen); skb_reset_mac_header(skb2); skb_reset_network_header(skb2); skb_set_transport_header(skb2, skb_network_header_len(skb)); memcpy(skb_network_header(skb2), skb_network_header(skb), clen); pt_prev = ptype; } if (pt_prev) { if (!skb_orphan_frags_rx(skb2, GFP_ATOMIC)) { pt_prev->func(skb2, skb->dev, pt_prev, skb->dev); logged = true; } else __kfree_skb(skb2); } out_unlock: skb->dev = old_dev; rcu_read_unlock_bh(); if (mode && logged) { //drd_print_skb(skb, len, reason ); return get_dummy(skb, reason, pos, st_depth); } return NULL; } int skb_validate(struct sk_buff *skb) { if (virt_addr_valid(skb) && virt_addr_valid(skb->dev)) { struct iphdr *ip4hdr = (struct iphdr *)skb_network_header(skb); if (skb->protocol != htons(ETH_P_IPV6) && skb->protocol != htons(ETH_P_IP)) return -1; switch (skb->dev->name[0]) { case 'r' : // rmnet* //case 'w' : // wlan case 'v' : // v4-rmnet* case 'l' : // lo //case 's' : // swlan /* swlan logging makes rcu lock starvation */ case 't' : // tun case 'b' : // bt* break; default : drd_dbg("invalid dev: %s\n", skb->dev->name); return -2; } if (unlikely((ip4hdr->version != 4 && ip4hdr->version != 6) || ip4hdr->id == 0x6b6b)) return -3; if (unlikely(!skb->len)) return -4; if (unlikely(skb->len > skb->tail)) return -5; if (unlikely(skb->data <= skb->head)) return -6; if (unlikely(skb->tail > skb->end)) return -7; if (unlikely(skb->pkt_type == PACKET_LOOPBACK)) return -8; return 0; } return -255; } void drd_kfree_skb(struct sk_buff *skb, unsigned int reason) { struct sk_buff *dmy; if (unlikely(!support_dropdump)) return; if (unlikely(skb_validate(skb))) return; dmy = drd_queue_skb(skb, 1, reason); if (unlikely(dmy)) { drd_queue_skb(dmy, 0, 0); __kfree_skb(dmy); } } EXPORT_SYMBOL_GPL(drd_kfree_skb); void drd_ptype_head(const struct packet_type *pt, struct list_head *vendor_pt) { if (pt->type == htons(ETH_P_LOG)) vendor_pt->next = &ptype_log; } EXPORT_SYMBOL_GPL(drd_ptype_head); #if defined(CONFIG_ANDROID_VENDOR_HOOKS) static void drd_ptype_head_handler(void *data, const struct packet_type *pt, struct list_head *vendor_pt) { drd_ptype_head(pt, vendor_pt); } #else /* can't use macro directing the drd_xxx functions instead of lapper. * * because of have to use EXPORT_SYMBOL macro for module parts. * * it should to be used at here with it's definition */ void trace_android_vh_ptype_head(const struct packet_type *pt, struct list_head *vendor_pt) { drd_ptype_head(pt, vendor_pt); } EXPORT_SYMBOL_GPL(trace_android_vh_ptype_head); #endif #if defined(TRACE_SKB_DROP_REASON) || defined(DEFINE_DROP_REASON) static void drd_kfree_skb_handler(void *data, struct sk_buff *skb, void *location, enum skb_drop_reason reason) { #else static void drd_kfree_skb_handler(void *data, struct sk_buff *skb, void *location) { unsigned int reason = 0; #endif drd_kfree_skb(skb, (unsigned int)reason); } static struct ctl_table drd_proc_table[] = { { .procname = "support_dropdump", .data = &support_dropdump, .maxlen = sizeof(int), .mode = 0644, .proc_handler = proc_dointvec, }, #ifdef EXTENDED_DROPDUMP { .procname = "support_dropdump_ext", .data = &support_dropdump, .maxlen = sizeof(int), .mode = 0644, .proc_handler = proc_dointvec, }, #endif { } }; static int __init init_net_drop_dump(void) { int rc = 0; drd_info("\n"); INIT_LIST_HEAD(&ptype_log); init_net.core.sysctl_hdr = register_net_sysctl(&init_net, "net/core", drd_proc_table); if (init_net.core.sysctl_hdr == NULL) { drd_info("init sysctrl failed\n"); return -ENODEV; } #if defined(CONFIG_ANDROID_VENDOR_HOOKS) rc = register_trace_android_vh_ptype_head(drd_ptype_head_handler, NULL); #endif rc += register_trace_kfree_skb(drd_kfree_skb_handler, NULL); if (rc) { drd_info("fail to register android_trace\n"); return -EIO; } support_dropdump = 0; return rc; } static void exit_net_drop_dump(void) { drd_info("\n"); support_dropdump = 0; } module_init(init_net_drop_dump); module_exit(exit_net_drop_dump); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Samsung dropdump module");