使用systemtap实时获取系统中半连接的数量_systemtap percpu 变量

统计系统中TCP半连接的数量,对于分析SYN攻击行为非常有意义,目前,我们有多种手段可以统计系统半连接的数量,比如采用netstat/ss遍历系统的连接,再grep出半连接。

但是这种遍历的方式在攻击已经形成时并不可取,会让本来已经不堪重负的系统更加是雪上加霜。遍历操作本身是

O

(

n

)

O(n)

O(n)的,工具越剧烈,获取半连接统计信息这件事本身消耗的资源就越多。

于是,昨天中午,我写了个简单代码,通过仅仅遍历Listener的方式来获取所有附着在每一个Listener上的request的累加和,这种方式之所以可取,是因为在任意时刻,系统中的Listener数量是常数,因为我们这个方式是一个

O

(

1

)

O(1)

O(1)的时间复杂度。详情参见:
https://blog.csdn.net/dog250/article/details/105013772

但是,能不能更近一步呢?

我的意思是说,系统中存在一个值,比如叫counter,它表示当前时间快照的TCP半连接的数量,当有TCP Synrequest进来时,counter加1,当有握手完成或者TCP Synrequest超时释放时,counter减1,那么是不是更加优雅呢?这样一来,连遍历Listener的开销都省了。

我们先用stap试一下。

通过内核代码,我们知道每一个Listener的半连接计数是在下面两个函数中被更新的:

static inline int reqsk_queue_removed(struct request_sock_queue *queue,
                      struct request_sock *req);
static inline int reqsk_queue_added(struct request_sock_queue *queue);                

虽然它们是inline,但是别忘了,kprobe是支持指令probe的,在其封装成stap后,我们可以用kernel.statement来玩:

[root@localhost ~]# stap -L 'kernel.statement("**")'|grep '\"reqsk_queue_added\|\"reqsk_queue_removed'
kernel.statement("reqsk_queue_added@include/net/request_sock.h:241") $queue:struct request_sock_queue*
kernel.statement("reqsk_queue_removed@include/net/request_sock.h:232")

OK,有戏!

下面就开整,写下stap脚本:

global counter

probe begin {
    printf("hello!\n")
}

probe kernel.statement("reqsk_queue_removed@include/net/request_sock.h:232") {
    counter--
    printf("1 request removed: %d\n", counter)
}

probe kernel.statement("reqsk_queue_added@include/net/request_sock.h:241") {
    counter++
    printf("1 request added: %d\n", counter)
}

probe end {
    printf("bye...\n")
}

趁着系统中没有半连接的时候,将它跑起来(不然数据会不准):

[root@localhost ~]# stap ./synrecv.stp
hello!

这个时候,在另一个终端或者另一个机器上打一波syn flood,对着侦听端口打,不然会reset:

root@zhaoya-VirtualBox:/home/zhaoya# hping3 -i u1 -S -p 22 192.168.56.110 -q &
[1] 2067
root@zhaoya-VirtualBox:/home/zhaoya# hping3 -i u1 -S -p 23 192.168.56.110 -q &
[2] 2068

观察stap的实时输出:

1 request added: 512
1 request removed: 511
1 request removed: 510
1 request added: 511
1 request added: 512
1 request removed: 511
1 request removed: 510
1 request added: 511
1 request added: 512
1 request removed: 511
1 request removed: 510
...

此时杀掉hping3,我们得到了预期的实时输出,最后我们杀掉stap,结束这一切:

...
1 request removed: 15
1 request removed: 14
1 request removed: 13
1 request removed: 12
1 request removed: 11
1 request removed: 10
1 request removed: 9
1 request removed: 8
1 request removed: 7
1 request removed: 6
1 request removed: 5
1 request removed: 4
1 request removed: 3
1 request removed: 2
1 request removed: 1
1 request removed: 0
^Cbye...
[root@localhost ~]#

这意味着我们可以随时获取counter的值,绘制出曲线后,就能明显看出系统半连接在任意时间段内的变化情况,这可以帮助我们理解攻击的行为特征。

但是,由于kprobe机制是有损的,生产环境真的可以部署stap吗?显然不能!那么这也只是个POC了,再者说了,我们也没有考虑同步问题,行百里者半九十,现在才行十里。


接下来怎么做?

很容易,我们只需要用kpatch技术去hotfix这些inline函数的调用函数就可以了,在它们后面增加或者递减全局的计数器。

通过代码可以知道,这其实非常容易:

  • reqsk_queue_added仅在inet_csk_reqsk_queue_hash_add中被调用。
  • reqsk_queue_removed在2个函数中被调用。

我们仅仅需要修改两个函数即可,由于要考虑多核同步问题,所以我们有两个选择:

  • 使用atomic变量。
  • 使用percpu变量。

我更倾向于使用percpu变量,因为它几乎是无损的,在读取的时候,只需要把每个CPU的对应变量对无锁累加即可,虽然这会有误差,但误差仅仅限于最后的累加过程,遍历CPU的过程是一个很快的过程,平均到一个时间段,误差可以忽略了。

下面是需要修改的地方。

// net/ipv4/inet_connection_sock.c
void inet_csk_reqsk_queue_hash_add(struct sock *sk, struct request_sock *req,
                   unsigned long timeout)
{
    unsigned int this_cpu_counter = ... // percpu counter的获取
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct listen_sock *lopt = icsk->icsk_accept_queue.listen_opt;
    const u32 h = inet_synq_hash(inet_rsk(req)->ir_rmt_addr,
                     inet_rsk(req)->ir_rmt_port,
                     lopt->hash_rnd, lopt->nr_table_entries);

    reqsk_queue_hash_req(&icsk->icsk_accept_queue, h, req, timeout);
    inet_csk_reqsk_queue_added(sk, timeout);
    this_cpu_counter ++;
}
void inet_csk_reqsk_queue_prune(struct sock *parent,
                const unsigned long interval,
                const unsigned long timeout,
                const unsigned long max_rto)
{
    unsigned int this_cpu_counter = ... // percpu counter的获取
    ...
                /* Drop this request */
                inet_csk_reqsk_queue_unlink(parent, req, reqp);
                reqsk_queue_removed(queue, req);
                this_cpu_counter --;
                reqsk_free(req);
                continue;
            }
            reqp = &req->dl_next;
        }
        ...
}
// net/ipv4/tcp_minisock.c
struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
               struct request_sock *req,
               struct request_sock **prev,
               bool fastopen)
{
    unsigned int this_cpu_counter = ... // percpu counter的获取
    ...
    inet_csk_reqsk_queue_removed(sk, req);
    this_cpu_counter --;

    inet_csk_reqsk_queue_add(sk, req, child);
    return child;
    ...
    if (!fastopen) {
        inet_csk_reqsk_queue_drop(sk, req, prev);
        this_cpu_counter --;
        NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_EMBRYONICRSTS);
    }
    return NULL;
}
// net/ipv4/tcp_ipv4.c
void tcp_v4_err(struct sk_buff *icmp_skb, u32 info)
{
    unsigned int this_cpu_counter = ... // percpu counter的获取
    ...
    inet_csk_reqsk_queue_drop(sk, req, prev);
    this_cpu_counter --;
    NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);
    goto out;
    ...
}

这个代码我没有proc接口读出来数据,因为我不知道如何在kpatch中注册一个proc接口或者注册一个sysctl,当然,可以hook任意函数这么做:

static int initial = 0;
void hooked_func(...)
{
    ...
    if (!initial) {
        register_proc(...);
        initial = 1;
    }
}

不晓得可否?

kpatch一定要在系统没有流量的时候加载,不然会遗漏加载时已有的半连接统计。

如果想在有流量的时候无卡顿热加载,那也不难,kpatch加载的时候,使用我昨天扫描Listener的方式获取一下当前的半连接队列总长度的值,然后为全局counter设置一个初始值即可。在读取值的时候,所有percpu变量累加之后,全局加上该初始值,就是结果。 记住,percpu变量在这种情况下,就必须是signed int,而不是unsigned int咯!

当然了,这些都只是手艺人的小把戏,经理肯定不是这么想的,经理肯定也不在乎这些东西。


浙江温州皮鞋湿,下雨进水不会胖。

原文链接: https://blog.csdn.net/dog250/article/details/105022347

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    使用systemtap实时获取系统中半连接的数量_systemtap percpu 变量

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/406065

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午9:36
下一篇 2023年4月26日 上午9:36

相关推荐