android_kernel_samsung_sm8650/drivers/net/dropdump/dropdump.c
2024-10-20 20:09:27 +02:00

990 lines
23 KiB
C
Executable File

/*
* Monitoring code for network dropped packet alerts
*
* Copyright (C) 2018 SAMSUNG Electronics, Co,LTD
*/
#include <net/ip.h>
#include <net/tcp.h>
#if defined(CONFIG_ANDROID_VENDOR_HOOKS)
#include <trace/hooks/net.h>
#endif
#include <trace/events/skb.h>
#include <net/dropdump.h>
int debug_drd = 0;
#define drd_info(fmt, ...) pr_info("drd: %s: " pr_fmt(fmt), __func__, ##__VA_ARGS__)
#define drd_dbg(flag, ...) \
do { \
if (unlikely(debug_drd & flag)) { drd_info(__VA_ARGS__); } \
else {} \
} while (0)
DEFINE_RATELIMIT_STATE(drd_ratelimit_print, 1 * HZ, 10);
#define drd_limit(...) \
do { \
if (__ratelimit(&drd_ratelimit_print)) \
drd_info(__VA_ARGS__); \
} while (0)
DEFINE_RATELIMIT_STATE(drd_ratelimit_pkt, 1 * HZ, 32);
struct list_head ptype_log __read_mostly;
EXPORT_SYMBOL_GPL(ptype_log);
int support_dropdump;
EXPORT_SYMBOL_GPL(support_dropdump);
extern struct list_head ptype_all;
struct st_item hmap[DRD_HSIZE];
spinlock_t hlock;
u64 hmap_count;
u64 hdup_count;
uint hmax_depth;
u16 skip_count;
u64 dropped_count;
#ifdef DRD_WQ
struct _drd_worker drd_worker;
unsigned int budget_default = BUDGET_DEFAULT;
unsigned int budget_limit;
#define BUDGET_MAX (budget_default << 2)
#define LIST_MAX (BUDGET_MAX << 2)
#endif
void init_st_item(struct st_item *item)
{
INIT_LIST_HEAD(&item->list);
item->p = 0;
item->matched = 0;
item->st[0] = '\n';
}
int get_hkey(u64 *hvalue)
{
u64 key = 0;
u64 src = *hvalue & 0x00000000ffffffff;
while (src) {
key += src & 0x000000ff;
src >>= 8;
}
key %= DRD_HSIZE;
return (int)key;
}
char *get_hmap(u64 *hvalue)
{
int hkey = get_hkey(hvalue);
struct st_item *lookup = &hmap[hkey];
uint depth = 1;
do {
drd_dbg(DEBUG_HASH, "hvalue search[%d]: <%pK|%pK|%pK> p:[%llx], hvalue:{%llx}\n",
hkey, lookup, lookup->list.next, &hmap[hkey], lookup->p, *hvalue);
if (lookup->p == *hvalue) {
drd_dbg(DEBUG_HASH, "hvalue found: '%s'\n", lookup->st);
if (lookup->matched < 0xffffffffffffffff)
lookup->matched++;
if (depth >=3 && lookup->matched > ((struct st_item *)hmap[hkey].list.next)->matched) {
// simply reorder the list by matched count, except the hmap array head
list_del(&lookup->list);
__list_add(&lookup->list, &hmap[hkey].list, hmap[hkey].list.next);
}
return lookup->st;
}
lookup = (struct st_item *)lookup->list.next;
if (hmax_depth < ++depth)
hmax_depth = depth;
} while (lookup != &hmap[hkey]);
drd_dbg(DEBUG_HASH, "hvalue not found\n");
return NULL;
}
char *set_hmap(u64 *hvalue)
{
int hkey = get_hkey(hvalue);
struct st_item *newItem = NULL;
bool first_hit = false;
drd_dbg(DEBUG_HASH, "hvalue {%d}: <%llx> %llx\n", hkey, *hvalue, hmap[hkey].p);
if (hmap[hkey].p == 0) {
newItem = &hmap[hkey];
first_hit = true;
} else {
newItem = kmalloc(sizeof(struct st_item), GFP_ATOMIC);
if (newItem == NULL) {
drd_dbg(DEBUG_HASH, "fail to alloc\n");
spin_unlock_bh(&hlock);
return NULL;
}
init_st_item(newItem);
list_add_tail(&newItem->list, &hmap[hkey].list);
hdup_count++;
}
newItem->p = *hvalue;
hmap_count++;
spin_unlock_bh(&hlock);
snprintf(newItem->st, ST_SIZE, "%pS", (void *)*hvalue);
drd_dbg(DEBUG_HASH, "{%d:%d} <%pK> '%s'\n", hkey, first_hit, hvalue, newItem->st);
return newItem->st;
}
/* use direct call instead of recursive stack trace */
u64 __stack(int depth)
{
u64 *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;
case 25 :
func = __builtin_return_address(25);
break;
default :
return 0;
}
return (u64)func;
}
#define NOT_TRACE (0xDD)
#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 + 4, "f_nbu", 5))// __qdf_nbuf_free
return NOT_TRACE;
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, "loc", 3))// local_*
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, "el", 2)) // el*
return FIN_TRACE;
if (!strncmp(pos, "gic", 3)) // gic_xxx
return FIN_TRACE;
if (!strncmp(pos + 3, "rt_ke", 5))// start_kernel
return FIN_TRACE;
if (!strncmp(pos + 13, "rt_ke", 5))// secondary_start_kernel
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, "icmp", 4))
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 symbol_lookup(u64 *addr, int net_pkt) {
char *symbol = NULL;
spin_lock_bh(&hlock);
symbol = get_hmap(addr);
if (symbol != NULL)
spin_unlock_bh(&hlock);
else
symbol = set_hmap(addr);
memcpy((char *)addr, symbol, strlen(symbol));
return chk_stack(symbol, net_pkt);
}
u8 get_stack(struct sk_buff *skb, struct sk_buff *dmy, unsigned int offset, unsigned int reason)
{
u8 depth = 0, max_depth = ST_MAX;
struct _dmy_info *dmy_info = (struct _dmy_info *)(dmy->data + offset);
u64 *stack_base = &dmy_info->stack;
#ifdef DRD_WQ
// sometimes __builtin_return_address() returns invalid address for deep stack of
// ksoftirq or kworker, and migration task. limit the maximun depth for them.
if ((current->comm[0] == 'k' && (current->comm[4] == 't' || current->comm[4] == 'k')) ||
(current->comm[0] == 'm' && current->comm[4] == 'a')) {
dmy_info->flag |= LIMIT_DEPTH_BIT;
max_depth >>= 1;
}
#else
int chk = 0, net_pkt = 0;
#endif
if (skb->tstamp >> 48 < 5000) {
// packet has kernel timestamp, not utc.
// using a zero-value for updating to utc at tpacket_rcv()
dmy_info->flag |= UPDATE_TIME_BIT;
dmy->tstamp = 0;
} else {
// using utc of original packet
dmy->tstamp = skb->tstamp;
}
drd_dbg(DEBUG_SAVE, "trace <%pK>\n", skb);
for (depth = 0; depth < max_depth; depth++) {
*stack_base = __stack(depth);
#ifdef DRD_WQ
drd_dbg(DEBUG_SAVE, "%02d: <%pK>\n", depth, (u64 *)*stack_base);
if (*stack_base == 0) {
// __builtin_return_address() returned root stack
depth--;
break;
}
#else
/* functions that instead of when set_stack_work not used */
chk = symbol_lookup(stack_base, net_pkt);
drd_dbg(DEBUG_SAVE, "[%2d:%d] <%s>\n", depth, chk, (char *)stack_base);
if (chk == NOT_TRACE) {
drd_dbg(DEBUG_TRACE, "not target stack\n");
return NOT_TRACE;
}
if (chk == FIN_TRACE)
break;
if (chk == ACK_TRACE) {
if (is_tcp_ack(skb)) {
drd_dbg(DEBUG_TRACE, "don't trace tcp ack\n");
return NOT_TRACE;
} else {
net_pkt = 1;
}
}
if (chk == GET_TRACE)
net_pkt = 1;
#endif
stack_base += (ST_SIZE / sizeof(u64));
}
memcpy(dmy_info->magic, "DRD", 3);
dmy_info->depth = depth;
if (skip_count > 0) {
dmy_info->skip_count = skip_count;
skip_count = 0;
}
dmy_info->count = ++dropped_count;
dmy_info->reason_id = reason;
if (reason < DRD_REASON_MAX) {
memcpy(dmy_info->reason_str, drd_reasons[reason], min(16, (int)strlen(drd_reasons[reason])));
} else {
memcpy(dmy_info->reason_str, "UNDEFINED_REASON", 16);
}
drd_dbg(DEBUG_RAW, "<%pK:%pK> %*ph\n", dmy, dmy_info, 16, dmy_info);
return depth;
}
int set_stack_work(struct sk_buff *skb, struct _dmy_info *dmy_info)
{
int chk = 0, net_pkt = 0;
u8 depth;
u64 *stack_base;
drd_dbg(DEBUG_RAW, "<%pK:%pK> %*ph\n", skb, dmy_info, 16, dmy_info);
if (strncmp(dmy_info->magic, "DRD", 3)) {
drd_dbg(DEBUG_TRACE, "invalid magic <%pK>\n", skb);
return -1;
}
stack_base = &dmy_info->stack;
for (depth = 0; depth < dmy_info->depth; depth++) {
chk = symbol_lookup(stack_base, net_pkt);
drd_dbg(DEBUG_RESTORE, "[%2d:%d] <%s>\n", depth, chk, (char *)stack_base);
if (chk == NOT_TRACE) {
drd_dbg(DEBUG_TRACE, "not target stack\n");
return NOT_TRACE;
}
if (chk == FIN_TRACE)
break;
if (chk == ACK_TRACE) {
if (is_tcp_ack(skb)) {
drd_dbg(DEBUG_TRACE, "don't trace tcp ack\n");
return NOT_TRACE;
} else {
net_pkt = 1;
}
}
if (chk == GET_TRACE)
net_pkt = 1;
stack_base += (ST_SIZE / sizeof(u64));
}
if (net_pkt == 0) {
drd_dbg(DEBUG_TRACE, "not defined packet\n");
return -3;
}
return depth;
}
#ifdef DRD_WQ
static void save_pkt_work(struct work_struct *ws)
{
struct sk_buff *skb, *next;
struct packet_type *ptype = NULL;
struct _dmy_info *dmy_info = NULL;
int st_depth = 0;
u16 budget = 0;
list_for_each_entry_safe(skb, next, &drd_worker.list, list) {
spin_lock_bh(&drd_worker.lock);
if (support_dropdump) {
list_for_each_entry_rcu(ptype, &ptype_log, list) {
if (ptype != NULL)
break;
}
drd_dbg(DEBUG_LOG, "del %u:%llu <%llx>\n", budget, drd_worker.num, (u64)(skb));
skb_list_del_init(skb);
drd_worker.num--;
} else {
spin_unlock_bh(&drd_worker.lock);
return;
}
spin_unlock_bh(&drd_worker.lock);
if (ptype == NULL || list_empty(&ptype->list)) {
drd_dbg(DEBUG_LOG,"pt list not ready\n");
__kfree_skb(skb);
continue;
}
dmy_info = (struct _dmy_info *)(skb->data + PKTINFO_OFFSET(skb));
st_depth = set_stack_work(skb, dmy_info);
if (st_depth != NOT_TRACE) {
ptype->func(skb, skb->dev, ptype, skb->dev);
} else {
__kfree_skb(skb);
}
if (++budget >= budget_limit)
break;
}
if (!list_empty(&drd_worker.list)) {
if (budget_limit < BUDGET_MAX)
budget_limit <<= 1;
queue_delayed_work(drd_worker.wq, &drd_worker.dwork, msecs_to_jiffies(1));
drd_dbg(DEBUG_LOG, "pkt remained(%llu), trigger again. budget:%d\n", drd_worker.num, budget_limit);
} else {
drd_worker.num = 0;
}
return;
}
#else
void save_pkt(struct sk_buff *skb)
{
struct packet_type *ptype = NULL;
rcu_read_lock();
list_for_each_entry_rcu(ptype, &ptype_log, list) {
if (ptype != NULL)
break;
}
if (ptype == NULL || list_empty(&ptype->list)) {
drd_dbg(DEBUG_LOG,"pt list not ready\n");
__kfree_skb(skb);
goto out;
}
drd_dbg(DEBUG_LOG, "%llu <%llx>\n", dropped_count, (u64)(skb));
ptype->func(skb, skb->dev, ptype, skb->dev);
out:
rcu_read_unlock();
}
#endif
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 'v': // v4-rmnet*
case 't': // tun
case 'e': // epdg
break;
case 'l': // lo
case 'b': // bt*
case 'w': // wlan
case 's': // swlan
if (__ratelimit(&drd_ratelimit_pkt))
break;
if (skip_count < 0xffff)
skip_count++;
dropped_count++;
return -9;
default:
drd_dbg(DEBUG_LOG, "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;
drd_dbg(DEBUG_RAW, "ndev: %s\n", skb->dev->name);
return 0;
}
return -255;
}
struct sk_buff *get_dummy(struct sk_buff *skb, unsigned int reason)//, char *pos, int st_depth)
{
struct sk_buff *dummy = NULL;
struct skb_shared_info *shinfo;
unsigned int copy_len = PKTINFO_COPYLEN_MAX;
unsigned int copy_buf_len = PKTINFO_COPYLEN_MAX;
unsigned int org_len, dummy_len;
u8 ret = 0;
struct iphdr *ip4hdr = (struct iphdr *)(skb_network_header(skb));
struct ipv6hdr *ip6hdr;
if (ip4hdr->version == 4) {
org_len = ntohs(ip4hdr->tot_len);
} else {
ip6hdr = (struct ipv6hdr *)ip4hdr;
org_len = skb_network_header_len(skb) + ntohs(ip6hdr->payload_len);
}
if (org_len < PKTINFO_COPYLEN_MAX) {
copy_len = org_len;
copy_buf_len = round_up(org_len, 0x10);
}
dummy_len = copy_buf_len + sizeof(struct _dmy_info) + ST_BUF_SIZE;
dummy = alloc_skb(dummy_len, GFP_ATOMIC);
if (unlikely(!dummy)) {
drd_dbg(DEBUG_LOG, "alloc fail, %u\n", dummy_len);
return NULL;
}
drd_dbg(DEBUG_SAVE, "skb->len:%u org_len:%u copy_len:%u copy_buf_len:%u dummy_len:%u\n",
skb->len, org_len, copy_len, copy_buf_len, dummy_len);
dummy->dev = skb->dev;
dummy->protocol = skb->protocol;
dummy->ip_summed = CHECKSUM_UNNECESSARY;
refcount_set(&skb->users, 1);
skb_put(dummy, dummy_len);
skb_reset_mac_header(dummy);
skb_reset_network_header(dummy);
skb_set_transport_header(dummy, skb_network_header_len(skb));
shinfo = skb_shinfo(dummy);
memset(shinfo, 0, sizeof(struct skb_shared_info));
atomic_set(&shinfo->dataref, 1);
INIT_LIST_HEAD(&dummy->list);
memcpy(dummy->data, skb_network_header(skb), copy_len);
memset((void *)((u64)dummy->data + (u64)copy_len), 0,
0x10 - (copy_len % 0x10) + sizeof(struct _dmy_info) + ST_BUF_SIZE);
ret = get_stack(skb, dummy, copy_buf_len, reason);
if (ret != NOT_TRACE) {
PKTINFO_OFFSET(dummy) = copy_buf_len;
} else {
drd_dbg(DEBUG_SAVE, "not saving pkt\n");
__kfree_skb(dummy);
return NULL;
}
return dummy;
}
void drd_kfree_skb(struct sk_buff *skb, unsigned int reason)
{
struct sk_buff *dmy;
#ifdef DRD_WQ
struct sk_buff *next;
#endif
if (support_dropdump < 2) {
#ifdef DRD_WQ
if (drd_worker.num) {
drd_dbg(DEBUG_LOG, "purge drd list\n");
cancel_delayed_work(&drd_worker.dwork);
spin_lock_bh(&drd_worker.lock);
list_for_each_entry_safe(dmy, next, &drd_worker.list, list) {
skb_list_del_init(dmy);
__kfree_skb(dmy);
}
drd_worker.num = 0;
spin_unlock_bh(&drd_worker.lock);
}
#endif
return;
}
if (skb_validate(skb))
return;
#ifdef DRD_WQ
if (unlikely(drd_worker.num >= LIST_MAX - 1)) {
drd_dbg(DEBUG_LOG, "drd list full\n");
return;
}
#endif
dmy = get_dummy(skb, reason);
if (dmy == NULL)
return;
#ifdef DRD_WQ
spin_lock_bh(&drd_worker.lock);
if (support_dropdump) {
list_add_tail(&dmy->list, &drd_worker.list);
drd_worker.num++;
drd_dbg(DEBUG_LOG, "add %llu <%pK>\n", drd_worker.num, dmy);
}
spin_unlock_bh(&drd_worker.lock);
budget_limit = budget_default;
queue_delayed_work(drd_worker.wq, &drd_worker.dwork, 0);
#else
save_pkt(dmy);
#endif
}
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);
}
struct kobject *drd_kobj;
int get_attr_input(const char *buf, int *val)
{
int ival;
int err = kstrtoint(buf, 0, &ival);
if (err >= 0)
*val = ival;
else
drd_info("invalid input: %s\n", buf);
return err;
}
static ssize_t hstat_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf,
"stack : total %d, used %lld, dupplicated %llu, max_depth %u, dropped %llu\n",
DRD_HSIZE, hmap_count, hdup_count, hmax_depth, dropped_count);
}
static struct kobj_attribute hstat_attribute = {
.attr = {.name = "hstat", .mode = 0660},
.show = hstat_show,
};
static ssize_t hmap_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int i;
struct st_item *lookup;
for (i = 0; i < DRD_HSIZE; i++) {
lookup = &hmap[i];
drd_info("---------------------------------------------------------------------\n");
do {
drd_info("%03d <%llx:%llu> '%s'\n", i, lookup->p, lookup->matched, lookup->st);
lookup = (struct st_item *)lookup->list.next;
} while (lookup != &hmap[i]);
}
drd_info("---------------------------------------------------------------------\n");
return sprintf(buf, "hmap checked\n");
}
static struct kobj_attribute hmap_attribute = {
.attr = {.name = "hmap", .mode = 0660},
.show = hmap_show,
};
static ssize_t debug_drd_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "current debug_drd: %d (0x%x)\n", debug_drd, debug_drd);
}
ssize_t debug_drd_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
if (get_attr_input(buf, &debug_drd) >= 0)
drd_info("debug_drd = %d\n", debug_drd);
return count;
}
static struct kobj_attribute debug_drd_attribute = {
.attr = {.name = "debug_drd", .mode = 0660},
.show = debug_drd_show,
.store = debug_drd_store,
};
#ifdef DRD_WQ
static ssize_t budget_default_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "current budget_default: %u\n", budget_default);
}
ssize_t budget_default_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
if (get_attr_input(buf, &budget_default) >= 0)
drd_info("budget_default = %u\n", budget_default);
return count;
}
static struct kobj_attribute budget_default_attribute = {
.attr = {.name = "budget_default", .mode = 0660},
.show = budget_default_show,
.store = budget_default_store,
};
#endif
static struct attribute *dropdump_attrs[] = {
&hstat_attribute.attr,
&hmap_attribute.attr,
&debug_drd_attribute.attr,
#ifdef DRD_WQ
&budget_default_attribute.attr,
#endif
NULL,
};
ATTRIBUTE_GROUPS(dropdump);
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, i;
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;
}
#ifdef DRD_WQ
drd_worker.wq = create_workqueue("drd_work");
if (!drd_worker.wq) {
drd_info("fail to create wq\n");
return -ENOMEM;
}
INIT_DELAYED_WORK(&drd_worker.dwork, save_pkt_work);
INIT_LIST_HEAD(&drd_worker.list);
spin_lock_init(&drd_worker.lock);
drd_worker.num = 0;
#endif
drd_kobj = kobject_create_and_add("dropdump", kernel_kobj);
if (!drd_kobj) {
drd_info("fail to create kobj\n");
rc = -ENOMEM;
goto kobj_error;
}
rc = sysfs_create_groups(drd_kobj, dropdump_groups);
if (rc) {
drd_info("fail to create attr\n");
goto attr_error;
}
for (i = 0; i < DRD_HSIZE; i++) {
init_st_item(&hmap[i]);
}
spin_lock_init(&hlock);
support_dropdump = 0;
goto out;
attr_error:
kobject_put(drd_kobj);
kobj_error:
#ifdef DRD_WQ
destroy_workqueue(drd_worker.wq);
#endif
out:
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");