前言
国家开始大力推 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 的安卓。
所以还是无状态分配吧
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 定义, oid
为 1.3.6.1.2.1.4.35.1.4
。由于 ipNetToPhysicalTable
同时包含了 ipv4
地址和 ipv6
地址对应的 mac
地址表,公平起见,ssh
执行的命令中同时包含 show arp
和 show 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 Address
和 State
。
第三行是 Interface
和 Age
第四行是 VLAN
和 CEVLAN
第五行是 VPN name
和 Is 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
,可以统计不同三层点下的用户规模。
或者看一下 mac_address
相同的 v6
地址情况
根据实际的情况,可以任意组合来做,总之只要进了日志服务器后面一切就都好办。
参考文献
RFC3315
2017 IPv6 支持度报告
RFC4293
以上
原文于 2018 年 10 月首发于简书,搬家存档。
行文有微调。
评论