暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

跟踪Linux服务器的IP访问关系

生有可恋 2022-03-31
1294

在梳理服务器的IP访问关系时,我们需要长时间观察网络会话信息,并通过会话记录来描述主机间的访问关系。

网络会话的结构,类似 netstat 的输出

    IP:源端口  目的IP:目的端口  会话状态
    192.168.10.23:81 10.1.1.2:59757 ESTABLISHED

    作为服务器端口,访问关系是一对多;如果是服务器主动访问外部,访问关系可能是一对一。我们使用 python 字典来存储这种访问关系,源IP:端口 作为字典的 key,而访问者的IP集合作为这个 key 的 value。

    单条访问关系如下:

      {'192.168.10.23:80': {'172.18.52.235'
      '192.168.23.5'
      '172.18.19.102'}
      }

      如果调用 netstat 来收集会话信息,这个效率会比较低。我们选择直接解析 /proc/net/tcp 文件,因为文件不大,读取信息效率比较高。

      对 /proc/net/tcp 文件解析之后,可以得到

      • 源IP

      • 源端口

      • 目的IP

      • 目的端口

      • 会话状态

      过滤掉本机‘0.0.0.0' 和 '127.0.0.1' 的本地IP之后,我们对访问关系进行梳理,最终执行效果如下:

      程序中加了控制参数,用来调整 /proc/net/tcp 文件的读取次数,以及每次读取间隔。这样可以控制时长和更新频率,对于长时间监控网络会话,可以每3秒读取一次,一天就是 3600x24/3 = 28800 次,程序执行条件为:

        python3 ./my_netstat.py 28800 3

        在程序的当前目录下会生成一个同名 *_tmp.log 的日志文件,日志文件会实时刷新当前的IP访问关系。

          cat my_netstat_tmp.log

          程序使用了 python 集合对重复目的IP去重,只保留新出现的目的IP。我们关注目的IP,主要目的是长时间监控网络会话,发现可能的入侵主机。

          程序提供了默认参数,当不传参时,将使用默认参数。默认每次更新IP集合的等待时间为0,如果不做长时间监控,则类似 netstat 单次查询的效果。

          程序代码如下:

            #!python3

            import time
            import sys

            PROC_TCP = "/proc/net/tcp"
            STATE = {
            '01':'ESTABLISHED',
            '02':'SYN_SENT',
            '03':'SYN_RECV',
            '04':'FIN_WAIT1',
            '05':'FIN_WAIT2',
            '06':'TIME_WAIT',
            '07':'CLOSE',
            '08':'CLOSE_WAIT',
            '09':'LAST_ACK',
            '0A':'LISTEN',
            '0B':'CLOSING'
            }

            def _load():
            ''' Read the table of tcp connections & remove header '''
            with open(PROC_TCP,'r') as f:
            content = f.readlines()
            content.pop(0)
            return content

            def _hex2dec(s):
            return str(int(s,16))

            def _ip(s):
            ip = [(_hex2dec(s[6:8])),(_hex2dec(s[4:6])),(_hex2dec(s[2:4])),(_hex2dec(s[0:2]))]
            return '.'.join(ip)

            def _remove_empty(array):
            return [x for x in array if x !='']

            def _convert_ip_port(array):
            host,port = array.split(':')
            return _ip(host),_hex2dec(port)

            def netstat():
            '''
            Function to return a list with status of tcp connections at linux systems
                '''
            content=_load()
            result = []
            for line in content:
            line_array = _remove_empty(line.split(' ')) # Split lines and remove empty spaces.
            l_host,l_port = _convert_ip_port(line_array[1]) # Convert ipaddress and port from hex to decimal.
            r_host,r_port = _convert_ip_port(line_array[2])
            state = STATE[line_array[3]]
            if l_host == '0.0.0.0' or l_host == '127.0.0.1':
            continue
            nline = [l_host, l_port, r_host, r_port, state]
            result.append(nline)
            return result


            def my_print(n, f=sys.stdout):
            d = n
            single = []
            multi = []
            for key in d.keys():
            if len(d[key]) == 0:
            continue
            if len(d[key]) == 1:
            single.append([key, list(d[key])[0]])
            else:
            multi.append([key, list(d[key])])
            f.write('# 访问关系为一对一\n')
            f.write('-'*15)
            f.write('\n')
            for i in single:
            f.write(i[0] + " " + i[1])
            f.write('\n')
            f.write('\n')
            f.write('# 访问关系为一对多\n')
            f.write('-'*15)
            f.write('\n')
            for i in multi:
            f.write("%s 数量: %d\n" %(i[0], len(i[1])))
            for j in i[1]:
            f.write('\t' + j)
            f.write('\n')
            f.write('\n')


            if __name__ == '__main__':
            '''
            用法:python3 ./my_netstat.py 30 1
            参数1: 读取 proc/net/tcp 的次数
            参数2: 每次读取 proc/net/tcp 文件后等待的时间 (秒)
            当不提供参数时,使用默认值 python3 ./my_netstat.py 30 0
            '''
            # 默认读取 30 次
            br = 30
            if len(sys.argv) < 2:
            pass
            else:
            br = int(sys.argv[1])
            # 默认等待时间
            delay = 0
            if len(sys.argv) == 3:
            delay = int(sys.argv[2])
            d = {}
            n = 0
            while True:
            for conn in netstat():
            key = conn[0]+':'+conn[1]
            if key not in d:
            d[key] = set()
            else:
            d[key].add(conn[2])
            with open(sys.argv[0].replace('.py', '_tmp.log'), mode='w') as f:
            my_print(d, f)
            time.sleep(delay)
            n += 1
            if n == br:
            break
            my_print(d)


            参考:

            • https://gist.github.com/cafd0edcf107ac2f66b9.git

            文章转载自生有可恋,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

            评论