园区网中 IPv6 地址的终端 mac 地址追溯

作者: freedomkk_qfeng | 来源:发表于2018-10-17 11:53 被阅读97次

    前沿

    国家开始大力推 IPv6 了,作为大教育网的高校而言,我们自然早已全网双栈, v6 启用多年了。提供互联网接入服务当然要保存用户的上网认证日志以供查证之需,自然也包括终端的 mac 地址追溯。

    对于 IPv4 而言,这个事情很简单,dhcp 日志就好。然而在 IPv6 中情况就发生了一些变化。且不说无状态地址分配的问题,即便做了 dhcpv6,就能和 v4 一样解决问题吗?不是这样的。

    dhcpv6

    dhcpv4 使用 mac 地址来标识用户 —— 事实上是标识用户系统的接口(网卡)。而在 dhcpv6 中,则采取 DUID + IAID 的模式,DUID 标识系统,IAID 标识接口。

    根据 RFC3315 的规范,DUID 有三种模式

    • DUID Based on Link-layer Address Plus Time [DUID-LLT]
      由 2 字节的类型码 —— 这里是 1,2 字节的硬件类型码,4 字节的时间码,后面加链路层地址组成。
         0                   1                   2                   3
         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |               1               |    hardware type (16 bits)    |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                        time (32 bits)                         |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        .                                                               .
        .             link-layer address (variable length)              .
        .                                                               .
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
    • DUID Assigned by Vendor Based on Enterprise Number [DUID-EN]
      由 2 字节的类型码 —— 这里是 2,2 字节的企业注册号,2 字节的企业标识符,后面加企业自己定义的 Identifier。
         0                   1                   2                   3
         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |               2               |       enterprise-number       |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |   enterprise-number (contd)   |                               |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+                               |
        .                           identifier                          .
        .                       (variable length)                       .
        .                                                               .
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    
    • DUID Based on Link-layer Address [DUID-LL]
      由 2 字节的类型码 —— 这里是 3,2 字节的硬件类型码,后面加链路层地址组成。
         0                   1                   2                   3
         0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |               3               |    hardware type (16 bits)    |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        .                                                               .
        .             link-layer address (variable length)              .
        .                                                               .
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

    无论是哪一种模式,DUID 都直接与用户系统关联,而与实际的网络接口无关。DUID-EN 自然不必多说,DUID-LL 中,选取的 link-layer address 可以是任意的网络接口,无论哪个网络接口接入网络,DUID 都将保持不变。

    The choice of network interface can be completely arbitrary, as long as that interface rovides a unique link-layer address and is permanently attached to the device on which the DUID-LL is being generated. The same DUID-LL SHOULD be used in configuring all network interfaces connected to the device, regardless of which interface's link-layer address was used to generate the DUID.

    而 DUID-LL 是推荐用于没有永久性存储的设备的,比如交换机等。对于有永久性存储的设备,比如我们的电脑,手机,更常见的模式是 DUID-LLT。此模式下,DUID 一经生成,即便你换掉了网卡,系统的 DUID 也不会变更。

    Clients and servers using this type of DUID MUST store the DUID-LLT in stable storage, and MUST continue to use this DUID-LLT even if the network interface used to generate the DUID-LLT is removed.

    想一想为什么 DUID-LLT 要加上时间?因为这样才能保证把一张网卡拔下来换掉另外一台电脑上,生成的 DUID 不会产生冲突嘛~

    在 windows 下我们可以通过 ipconfig /all 来看一下自己的 DUID,比如看下我这块无线网卡的 DUID:

    Wireless LAN adapter WLAN:
    
       Connection-specific DNS Suffix  . :
       Description . . . . . . . . . . . : Intel(R) Wireless-N 7265
       Physical Address. . . . . . . . . : 34-02-86-70-5A-6C
       ……中间省略了……
       DHCPv6 Client DUID. . . . . . . . : 00-01-00-01-22-1B-61-34-68-F7-28-C1-3E-27
    

    可以看到末尾的 68-F7-28-C1-3E-27 和无线网卡的物理地址 34-02-86-70-5A-6C 全然不同。这个 mac 其实是属于有线网卡的。

    Ethernet adapter 以太网:
    
       Media State . . . . . . . . . . . : Media disconnected
       Connection-specific DNS Suffix  . :
       Description . . . . . . . . . . . : Intel(R) Ethernet Connection (3) I218-LM
       Physical Address. . . . . . . . . : 68-F7-28-C1-3E-27
    

    这种 DUID 和网卡解耦,只绑定系统的做法,其实是更利于用户终端的定位的 —— 如果环境是纯 v6 的话。。。
    然而现实是大家都是双栈,这样 v6 中的 DUID 就和 v4 中的 mac 地址关联就成为了麻烦的事情,我们总是希望统一化的来处理对吧。

    况且,还有一堆客户端不支持 dhcpv6 呢,根据 2017 IPv6 支持度报告 描述,仍只有 65% 的操作系统支持 dhcpv6,其中包括很可能永远不会支持 dhcpv6 的安卓。

    image.png

    所以还是无状态分配吧

    ipv6 neighbors

    那么方案还是回到无状态分配上,获取 mac 地址的办法自然就是取三层点上的 ipv6 neighbors 表了。技术上要么 snmp,要么 cli 执行名再分析输出。我们选择走 cli—— 因为 snmp 太慢了。。。

    我们知道 snmp 是有 index 的,并且在响应 snmp 请求时,返回的数据会经过排序,根据 index 的序号来按序返回。这就导致数据量很大的时候,snmp 的响应速度其实是相当慢的。并且,当交换机上的 snmp agent 持续时间很长在响应 snmp 请求时,他会显著的占用交换机的 cpu 资源,可能会导致交换机的 cpu 使用率破表。

    而通过 cli 执行命令来返回 ipv6 neighbors 表时,数据是无序的,速度就自然会快的多。这个差异在 ipv6 neighbors 量级增加时会非常显著。举个极端点的例子:

    实验设备是一台思科的 C6880-X-LE,上面大概有 4万多条 arp 表项和 8万多条 ipv6 邻居表项。

    #show arp | count Vl           
    Number of lines which match regexp = 41207
    
    #show ipv6 neighbors | count Vl
    Number of lines which match regexp = 85116
    

    我们先写个脚本来模拟下 ssh 执行命令,作为对比的是 snmp 采集 ipNetToPhysicalTable,这个指标由 RFC4293 定义, oid1.3.6.1.2.1.4.35.1.4。由于 ipNetToPhysicalTable 同时包含了 ipv4 地址和 ipv6 地址对应的 mac 地址表,公平起见,ssh 执行的命令中同时包含 show arpshow ipv6 neighbors

    一个很简单的 expect 脚本:

    #!/usr/bin/expect
    
    spawn ssh admin@192.168.9.1
    expect "Password:"
    send "123456\r"
    expect "*#"
    send "terminal length 0\r"
    send "show ipv6 neighbors\r"
    send "show arp\r"
    send "quit\r"
    interact
    

    然后我们使用 time 命令来对比下使用 ssh 执行命令和 snmp 请求的时间差异

    # time ./ssh_test.sh>/dev/null 2>&1
    
    real    0m13.783s
    user    0m0.061s
    sys     0m0.134s
    
    # time snmpwalk -v 2c -c community 192.168.9.1 1.3.6.1.2.1.4.35.1.4>/dev/null 2>&1
    
    real    44m28.033s
    user    0m10.151s
    sys     0m5.002s
    

    13.7秒 和 44分半,差距将近 190 倍!而这样体量的三层节点并不罕见,尤其是无线网络的网关,规模很可能还要更大。

    采集和记录

    所以方案就很清晰了

    • 首先并发的通过 ssh 采集全网所有三层节点上的 ipv6 邻居表
    • 然后分析执行结果,根据不同类型的交换机来做匹配处理,提取 ipv6 邻居表项上的各字段
    • 对提取的字段进行封装,然后推送进我们的日志服务器,比如 ELK 或者 Splunk

    并发 ssh 的执行可以使用 multissh 来做,我在 用 Go 写一个轻量级的 ssh 批量操作工具 中介绍过这个工具。我们先使用 multissh 去各个交换机上执行命令,把运行的结果先存下来,然后再通过 python 脚本去分析提取数据。考虑到交换机的类型和口令各异,我们编写一个 v6_neighbors.json 文件来保存不同交换机的口令和命令模板。

    {       
        "SshHosts": [
            {   
                "Host": "192.168.10.1",
                "Port": 22, 
                "Username": "admin",
                "Password": “password",
                "cmdFile":"/opt/swbackup/Ciscov6Ndcmd.txt"
            },
            {   
                "Host": "192.168.31.1",
                "Port": 22, 
                "Username": "admin",
                "Password": "password",
                "CmdFile": "/opt/swbackup/CiscoEnv6NdCmd.txt"
            },
            {
                "Host": "192.168.92.1",
                "Port": 22,
                "Username": "admin",
                "Password": "password",
                "CmdFile": "/opt/swbackup/Huaweiv6NdCmd.txt"
            }
           …………
    

    例如上面所用调用 cmdFile,对于不存在 en 密码的思科交换机,他的 cmdFile 是:

    terminal length 0
    show ipv6 neighbors
    

    而对于存在 en 密码的思科`,则为:

    enable
    enablepassword
    terminal length 0
    show ipv6 neighbors
    

    对于华为,则是:

    display ipv6 neighbors
    quit
    

    等等,我们都提前定义好,然后通过 multissh 调用即可。

    /opt/swbackup/multissh -c /opt/swbackup/v6_neighbors.json -outTxt -f /opt/swbackup/v6nd_out/
    
    2018/10/17 11:45:01 Multissh start
    2018/10/17 11:45:12 Multissh finished. Process time 10.98903995s. Number of active ip is 34
    

    对于 34 台交换机的并行执行,总计的耗时大约是 11 秒。运行的结果会以交换机的 host 作为文件名保存在 v6nd_out/ 目录内。

     v6nd_out]# ls
    192.168.10.1.txt 192.168.31.1.txt 192.168.92.1.txt .....
    

    然后我们根据不同的交换机类型来分析这些文本就好了。执行的输出大体可以分为三个部分:ipv6 neighbors 表以上,ipv6 neighbors 表正文,ipv6 neighbors 表以下,显然除了 ipv6 neighbors 表正文,其他部分我们在处理的时候都可以直接抛弃掉了。 举几个例子:
    Cisco Catalyst 的交换机

    cisco_switch#terminal length 0
    cisco_switch#show ipv6 neighbors   
    IPv6 Address                              Age Link-layer Addr State Interface
    # ipv6 neighbors 表以上部分,抛弃
    # ipv6 neighbors 表正文
    2001:DA8:8005:1328:FCF5:53FA:666E:6CE5      2 3417.eb9b.ed9c  STALE Vl28
    FE80::5908:8EEF:73F3:F295                   0 2089.843b.a63f  STALE Vl28
    FE80::F0:84CE:8CFF:2543                     0 000e.c6c9.59e2  REACH Vl28
    FE80::B44D:C7F9:30B8:48F                    0 0050.56a1.4dd3  DELAY Vl44
    …………
    FE80::250:56FF:FEBC:556D                    3 0050.56bc.556d  STALE Vl44
    FE80::89B2:4851:559E:A102                   1 94c6.9174.74a1  STALE Vl28
    # ipv6 neighbors 表正文
    # ipv6 neighbors 表以下部分,抛弃
    cisco_switch#exit
    

    华为的交换机

    Info: The max number of VTY users is 10, and the number
          of current VTY users on line is 1.
          The current login time is 2018-10-17 09:58:13+00:00.
    <henkou-core>display ipv6 neighbors
    -----------------------------------------------------------------------------
    # ipv6 neighbors 表以上部分,抛弃
    # ipv6 neighbors 表正文
    IPv6 Address : 2001:DA8:8005:7100:1EB:112D:BE4F:8CE3
    Link-layer   : 8cec-4b91-f3e8                     State : STALE
    Interface    : GE4/0/26                           Age   : 00h18m20s
    VLAN         : 903                                CEVLAN: -
    VPN name     :                                    Is Router: FALSE
    …………
    IPv6 Address : FE80::4D02:139A:3ADA:D038
    Link-layer   : 0023-5461-231f                     State : STALE
    Interface    : GE4/0/44                           Age   : 00h08m55s
    VLAN         : 903                                CEVLAN: -
    VPN name     :                                    Is Router: FALSE
    # ipv6 neighbors 表正文
    # ipv6 neighbors 表以下部分,抛弃
    -----------------------------------------------------------------------------
    Total: 15      Dynamic: 15      Static: 0
    
    <henkou-core>quit 
    Info: The max number of VTY users is 10, and the number
          of current VTY users on line is 0.
    

    Cisco Nexus 的交换机

    terminal length 0
    show ipv6 neighbor
    Cisco Nexus Operating System (NX-OS) Software
    TAC support: http://www.cisco.com/tac
    Copyright (C) 2002-2016, Cisco and/or its affiliates.
    All rights reserved.
    The copyrights to certain works contained in this software are
    owned by other third parties and used and distributed under their own
    licenses, such as open source.  This software is provided "as is," and unless
    otherwise stated, there is no warranty, express or implied, including but not
    limited to warranties of merchantability and fitness for a particular purpose.
    Certain components of this software are licensed under
    the GNU General Public License (GPL) version 2.0 or
    GNU General Public License (GPL) version 3.0  or the GNU
    Lesser General Public License (LGPL) Version 2.1 or
    Lesser General Public License (LGPL) Version 2.0.
    A copy of each such license is available at
    http://www.opensource.org/licenses/gpl-2.0.php and
    http://opensource.org/licenses/gpl-3.0.html and
    http://www.opensource.org/licenses/lgpl-2.1.php and
    http://www.gnu.org/licenses/old-licenses/library.txt.
    exit
    ganxunlou# terminal length 0
    ganxunlou# show ipv6 neighbor 
    
    Flags: # - Adjacencies Throttled for Glean
           G - Adjacencies of vPC peer with G/W bit
           R - Adjacencies learnt remotely
    
    IPv6 Adjacency Table for VRF default
    Total number of entries: 290
    Address         Age       MAC Address     Pref Source     Interface
    # ipv6 neighbors 表以上部分,抛弃
    # ipv6 neighbors 表正文
    2001:da8:8005:1542:1118:8f01:d54d:a9d2
                       2d02h  fc4d.d43e.aca5  50   icmpv6     Vlan500
    2001:da8:8005:1542:15e6:6514:2169:47fc
                       6d02h  fc4d.d43e.aca5  50   icmpv6     Vlan500
    …………
    fe80::f2c8:50ff:fe7f:1563
                    00:09:46  f0c8.507f.1563  50   icmpv6     Vlan23
    fe80::f970:4f2a:7c35:1e90
                    00:04:14  94c6.9120.5b83  50   icmpv6     Vlan23
    # ipv6 neighbors 表正文
    # ipv6 neighbors 表以上部分,抛弃
    ganxunlou# exit
    

    然后根据 ipv6 neighbors 表的差异,我们去做适配来提取字段就可以了。首先定义下结构。由于各品牌的 ipv6 neighbors 表所提供的字段并不全部相同,我们尽量定义他们的全集。没有的字段留空就好了,比如思科的交换机 interface 提供的是 vlan 号,而华为则区分为了具体的物理接口和 vlan 两部分,所以这里我们两个字段都定义,思科在 vlan 中留空即可。

    class ipv6_neighbors:
            def  __init__(self):
                    self.ipv6_address = ""
                    self.age = ""
                    self.mac_address = ""
                    self.state = ""
                    self.interface = ""
                    self.vlan = ""
                    self.switch_type = ""
    

    我们针对不同的交换机来行,来编写对应的处理函数,输入文本行的数组,输出分析好的 ipv6_neighbors 数组,举些例子:
    对于华为的交换机,可以这么处理:

    def huawei_swos_v6_nds(lines):
            v6_nds = []
            i = 0
            for l in lines:
                    if "-------------------------" in l:
                            break
                    i = i + 1
            num = 0
            for l in lines[(i+1):]:
                    if "-------------------------" in l:
                            break
                    if num == 0:
                            v6_nd = ipv6_neighbors()
                            v6_nd.ipv6_address = l.split(":", 1)[1].strip()
                    elif num == 1:
                            splitstr = l.split(":")
                            v6_nd.mac_address = splitstr[1].replace("State", "").strip()
                            v6_nd.state = splitstr[2].strip()
                    elif num == 2:
                            splitstr = l.split(":")
                            v6_nd.interface = splitstr[1].replace("Age", "").strip()
                            v6_nd.age = splitstr[2].strip()
                    elif num == 3:
                            splitstr = l.split(":")
                            v6_nd.vlan = splitstr[1].replace("CEVLAN", "").strip()
                    elif num == 4:
                            num = num + 1
                            continue
                    else:
                            v6_nd.switch_type = "huawei_swos"
                            v6_nds.append(v6_nd)
                            num = 0
                            continue
                    num = num + 1
            return v6_nds
    

    做法就是逐行扫描,直至碰到 ----------- 之前都抛弃。
    然后开始处理 ipv6 neighbors 表。每个 ipv6 neighbor 数据可以分为 5 行:
    第一行是 IPv6 Address
    第二行是 Link-layer,也就是 Mac AddressState
    第三行是 InterfaceAge
    第四行是 VLANCEVLAN
    第五行是 VPN nameIs Router
    分别提取字段追加到 v6_nds 数组中。 然后经过一个空行进入到下一个 ipv6 neighbor 数据。
    直至再次碰到 ----------- ,退出,返回数据。

    类似的,我们可以处理其他的交换机数据,然后把他们封装成 json

    {"switch_type": "huawei_swos", "switch": "192.168.92.1", "ipv6_address": "2001:DA8:8005:7100:4D02:139A:3ADA:D038", "mac_address": "0023-5461-231f", "interface": "GE4/0/44", "age": "00h04m18s", "vlan": "903", "state": "STALE"}
    

    然后我们再把这些 json 数据推到我们的日志服务器就好啦,我们可以直接通过 API 推送进 ELK 或者 Splunk。或者更简单的直接把他们作为日志打下来,然后通过 packetbeat/splunkforwarder 去采集就行了。

    数据分析

    既然数据进了日志服务器,而且已经以 json 方式封装好了字段,那么再做数据分析就是水到渠成的事情了。随便举几个例子:
    根据 mac_address 来计数,可以统计全网的上线终端数量。


    结合 switch,可以统计不同三层点下的用户规模。
    image.png
    或者看一下 mac_address 相同的 v6 地址情况
    image.png

    根据实际的情况,可以任意组合来做,总之只要进了日志服务器后面一切就都好办。

    以上

    参考文献

    RFC3315
    2017 IPv6 支持度报告
    RFC4293

    转载授权

    CC BY-SA

    相关文章

      网友评论

        本文标题:园区网中 IPv6 地址的终端 mac 地址追溯

        本文链接:https://www.haomeiwen.com/subject/xxxynftx.html