网络运维工程师日常打交道的设备,无非是路由器交换机还有防火墙。防火墙在其中,属于有一定技术含量,但又比较繁琐的部分。为了更细粒度的企业安全,企业环境中的防火墙一般都是先deny all,再逐个开启需要访问的网络关系,而网络关系的载体就是一条条的防火墙策略,这样就带来了大量的防火墙策略配置工作。在过往的防火墙管理中,经常需要付出一定的人力物力去执行防火墙的策略生成和下发工作,如何对防火墙进行自动化,削减这方面的工作。本系列会分专题进行阐述并针对实际产生的问题提出优化建议。
首先是防火墙的配置解析。本文主要针对Juniper防火墙,H3C防火墙,思科ASA防火墙,给出配置解析的一般思路,并提供参考代码。分为以下三点进行阐述。
在进入正式的防火墙配置解析之前,先对以下这些问题做一些说明。
[HTTP/HTTPS]
部分厂商部分型号的防火墙是支持web页面管理的,也就是说支持通过80/8080/443端口进行信息的查询和修改。这类防火墙的官网一般都会有对应的 API文档,如果所在企业已经购买了产品和服务,可以向厂商或代理商索要对应的API文档。但也存在部分厂商或产品有web界面但给不出对应的API文档的情况,此时处理过程可能要麻烦一些,需要通过分析web页面的方式获取到背后的数据API。利用API文档中的API或分析出来的API,我们可能可以获取到的信息包括防火墙策略、nat策略、运行状态等。
[SSH]
一般的防火墙都带有远程ssh管理功能(如无,请尽早退货),如果可以通过secureCRT、putty、Teminal这些工具访问到防火墙的管理页面,则可以通过SSH协议获取到我们想要的策略数据。通用的做法是用代码模拟SSH登录,在输入流里输入对应的命令,从输出流里获取到我们需要的数据。
[SNMP]
作为专门设计用于在 IP 网络管理网络节点(服务器、工作站、路由器、交换机及HUBS等)的一种标准协议,基本上防火墙设备都支持这种协议。如果防火墙开通了SNMP功能,可以视作一个key-value形式的数据库,我们只要拿着OID这个key,通过SNMP方法就能取出对应的value.这种方法适合用于监控,实时状态信息一般都能从这个数据库取到。
[NETCONF协议]
这类协议也是可以获取到防火墙的网络设备的信息,但是依赖于专门的厂商,并且使用到xml格式进行传输。具体能获取到的信息和HTTP/HTTPS类似,也可以对网络设备进行管理。
首先理解一点,现在商用的防火墙或其他网络设备,大多都是转发和控制层面使用的CPU会进行分离(ps:转发和控制分离是SDN)。所以即使是控制层面的CPU性能消耗殆尽,也会停留在控制层面瘫痪,下面**转发还是会按原有的规则转发**,但这样就已经**无法对防火墙做任何修改**,已经属于比较严重的事故。上面四种途径,都需要在控制层面(可以看作一个linux系统)启动后台进程,提供出来的服务,都需要消耗控制层面的CPU和内存,所以必须要尽量高效地使用。
[HTTP/HTTPS]
如果拿到了API文档,开发过程会十分简单,API是规律的,返回数据也一般是json格式,在python中可以直接使用。但开启HTTP/HTTPS服务消耗防火墙CPU和内存会较多。
[SSH]
开发起来也比较简单,数据以文本流输入输出,输出时可能会有一定的数据粘连问题,以及获取到文本后,还需要二次解析才能建立对应的映射。SSH消耗防火墙CPU和内存会较少。
[SNMP]
开发起来比较复杂,因为有相当多的OID,需要根据情况挑选OID,再去取数据。key-value类型的取数据方式导致的是取单个数据最快,取连续大块数据最慢。比如同样的一个CPU使用率值,如果使用SNMP去取,时间开销肯定是最少的,但如果我想去取一整个路由表下来,则需要花费最长的时间。所以SNMP适用于监控场景,较少用于大块数据的获取。
[NETCONF协议]
开发过程和消耗都和HTTP/HTTPS方式差不多,可能略低于HTTP/HTTPS,但开发中使用XML格式进行传输导致有额外的学习和使用成本。
[HTTP/HTTPS]
获取到的信息一般都为JSON格式,一般可以从HTTPresponse中取出来直接使用,有可能需要编写程序解析。
[SSH]
获取到的信息一般都为文本格式,返回值一般要经过编写程序解析,才能投入后续自动化开发的使用。
[SNMP]
获取到的信息一般都为文本格式的单个值,不需要太多额外的解析。
[NETCONF协议]
获取到信息一般都为XML格式,需要解析,可以使用特定的包进行解析。
这个问题实际上要结合实际情况来回答。我只给出我倾向于使用什么,以及使用的理由。个人倾向是HTTP/HTTPS>SSH>NETCONF>SNMP。
SNMP排末尾是因为不知道能不能取出来,到底有没有这个OID,因为通用OID范围内是不包含这个信息的。
NETCONF排倒二是因为xml的解析比较麻烦,而且是专有协议,开发出来只能CISCO设备使用。
SSH作为次选,是可以一次性取出所有配置的,相当于一次性取出所有策略。但是通过配置解析出防火墙策略需要对防火墙策略很熟悉。
HTTP/HTTPS体验最佳,一次或多次请求,就能获取到json格式的策略数据。但是防火墙不一定有HTTP/HTTPS功能,有这个功能不一定会允许开(别问我为什么,一般是领导觉得有风险不给开),所以能不能用上还得看情况。
所以下面的内容都基于SSH取到完整的配置,然后对之进行解析。以下为SSH取数据的简单示例,关于SSH如何取数据,已经有大量可搜索到文章讲解,此处不在赘述。
#encoding=utf-8
import paramiko
import socket
import time
import re
class SSHConnection(object):
def __init__(self, host, port, username, password):
self._host = host
self._port = port
self._username = username
self._password = password
def get_connect(self):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(self._host, 22, self._username, self._password, timeout=300, allow_agent=False,
look_for_keys=False)
return ssh
def close(cls, ssh):
if ssh:
ssh.close()
class SSHInfo(object):
def __init__(self,ip,username, password,port=22,delay=0.5,buffsize =99999999):
self.ip = ip
self.usename = username
self.password = password
self.port = port
self.delay = delay
self.buffsize = buffsize
self.conn = SSHConnection(ip, 22, username, password)
self.ssh = self.conn.get_connect()
self.ch = self.ssh.invoke_shell()
def read_buff(self, nbyte, delay=0.5, greedy_mode=True):
buff = ""
try:
self.ch.settimeout(3)
time.sleep(delay)
if greedy_mode:
while True:
_buff = self.ch.recv(nbyte)
try:
buff += str(_buff, encoding = "utf-8")
except:
buff += str(_buff, encoding="gbk")
else:
buff = self.ch.recv(nbyte)
except socket.timeout as ex:
pass
print("{}:{} read buff to end: {}(socket timeout)".format(self.ip,self.port,ex))
except Exception as ex:
more = traceback.format_exc()
print(more)
print("{}:{} {}".format(self.ip,self.port,ex))
return buff
def runCmd(self, cmd):
_cmd = cmd + "\n"
print("{}:{} {}".format(self.ip,self.port,cmd))
time.sleep(self.delay)
self.ch.send(_cmd)
def getInfo(self, cmd):
self.runCmd(cmd)
time.sleep(self.delay)
config_content = self.read_buff(self.buffsize, 5)
idx = config_content.rfind('\r\n')
self.runCmd("quit")
return config_content[:idx]
def getInfoNoQuit(self, cmd):
self.runCmd(cmd)
time.sleep(self.delay)
config_content = self.read_buff(self.buffsize, 5)
idx = config_content.rfind('\r\n')
return config_content[:idx]
cn = SSHInfo(ip, username, password, port, delay, buffsize) #请自定对括号中的变量进行定义
cn.runCmd("show config")
content = cn.read_buff(100000)
print(content)
每个独立的IP地址都能用一个单独的int数来存储,点分十进制字符串形式和int形式的IP地址在一定意义上是等价的。
下图从完整的二进制数据包开始,到三层IP头部的构成,再到IP头部中源地址的构成,最终描述了点分十进制字符串形式和int形式的IP的关系。
由上图可知,不管是使用int形式,还是点分十进制字符串形式,它们都可以对一个二进制的IP地址进行准确描述,并且两者之间存在一定的转化关系。高效的转化关系用python函数可以描述为:
#encoding=utf-8
import struct
import socket
def ip2num(ip):
"""将点分十进制ip转化成int数"""
return struct.unpack("!L", socket.inet_aton(ip))[0]
def num2ip(num):
"""将int数转化成对应的点分十进制ip"""
a, c, b, d = (num & 0xff000000) >> 24, (num & 0x0000ff00) >> 8, (num & 0x00ff0000) >> 16, (num & 0x000000ff) >> 0
return "{}.{}.{}.{}".format(a, b, c, d)
点分十进制字符串形式和int形式,在表示IP地址时各有优缺。点分十进制字符串易读,日常使用的多半是是直接以这种格式存储。而什么时候使用int数存储IP?这里列出了常用的三种情况:
如果使用字符串来存储IP地址,使用ASCII编码,每个IP地址需要7到15个字节;使用utf-8或者gbk编码,则会更多。存储的IP地址很多时,这将是一笔庞大的存储开销。而如果将IP地址转化成INT(uint32)数存储,每个IP地址固定消耗一个字节的的空间,缩减存储空间可达50%以上。
ip1 = "1.1.1.1" # 7个字符,如使用ASCII编码,占用7个字节
ip2 = "111.111.111.111" # 15个字符,如使用ASCII编码,占用15个字节
ip1 = 16843009 # "1.1.1.1"的INT形式,32位INT数,占用4个字节
ip2 = 1869573999 # "111.111.111.111"的INT形式,32位INT数,占用4个字节
如果有查询整个网段IP地址的需求,比如说要查询1.1.4.0/24这个网段。IP字符串存储,返回结果的顺序会错位,如"1.1.4.1"后面的IP是"1.1.4.10",而非"1.1.4.2",需要重新排序。但如果是使用INT数的形式,范围查询和顺序查询都会变得比较简单,直接使用">" 、"<"这样的操作即可,匹配表项的速度会很快;同时,因为IP已经转化成数字了,分库分表也会很简单,直接用数字映射到对应的表即可,比如0-100000使用iptable表1,100000-200000使用iptable表2,可以有效将数据分开存储,支持更多的并发查询。
//ip使用字符串存储,查询1.1.4.0/24网段
mysql>SELECT ip,state FROM ipble where ip like "1.1.4.%";
//返回结果为字符串,按字符串排序方式进行排序
| 1.1.4.1 | 0 |
| 1.1.4.10 | 0 |
| 1.1.4.100 | 1 |
| 1.1.4.101 | 1 |
| 1.1.4.102 | 1 |
| 1.1.4.103 | 1 |
//ip使用INT数存储,查询1.1.4.0/24网段
mysql>SELECT ip,state FROM ipble where ip >=16843776 and ip <=16843776+256;
//返回结果为int数,不易人工读,按数字排序方式进行排序
| 16843777 | 0 |
| 16843778 | 0 |
| 16843779 | 1 |
| 16843780 | 1 |
| 16843781 | 1 |
| 16843782 | 1 |
IP网段可以理解为IP的复数形式,多个IP地址可以组成一个IP网段。为了存储IP网段,最通用的做法是,在IP的基础上引入了一个新的量也就是MASK(掩码),用SUBNET表示IP网段,SUBNET = (IP address, MASK)。MASK和IP地址一样,也可以进行 **1.1** 中的点分十进制形式和int形式互转。如果需要判断一个IP地址是否在一个网段中,可以直接通过如下的位运算进行判断,效率会非常高(更高效率可以用C代码)。但因必须是int形式才可以进行下面位运算,所以这种情况下也必须用int形式的IP地址。
#encoding=utf-8
ipaddress = 3232235777 #"192.168.1.1"的int形式
subnet = (3232235776,4294967040) #("192.168.1.0","255.255.255.0")的int形式
def isIpInSubnet(ipaddress,subnet):
"""快速判断一个int形式的ip是否在subnet中"""
base, mask = subnet
if ip&mask==base&mask:
return True
return False
print(isIpInSubnet(ipaddress,subnet))
在生产环境中,我们一般会有三种IP网段的表现形式,这些形式被广泛用于防火墙规则编写中。
这三种IP网段的表现形式在生产环境均有应用,但根据公司或组织的不同,约定的内部规范不同,采用的表现形式也不同。下文会对这三种表现形式逐一讲解。
在了解这三种表现形式之前,先了解一下子网掩码和通配符掩码的区别。
"""
子网掩码:
点分十进制形式:255.255.255.0
二进制形式:1111111111111111111111100000000
规律:二进制形式前面全部为1,后面全部为0
通配符掩码:
点分十进制形式:127.255.31.0
二进制形式:1111111111111110001111100000000
规律:1和0可以交替使用,不需要前面全部为1
tips:两种掩码的IP网段,都可以用1.2.3中的isIpInSubnet判断某个IP地址在不在这个网段中。
"""
接下来是这三种形式的说明,假设下面的base,subnet mask,wildcard mask,end都已经通过1.1中ip2num函数转化成了int数。
在思科ASA防火墙配置中,会有这样的定义语句
object network ALL_net
subnet 70.0.0.0 255.0.0.0
object network TB-SW
subnet 70.2.12.0 255.255.255.0
object network TS-SW
subnet 70.2.24.0 255.255.255.0
使用的便是初始IP地址+子网掩码的方式定义IP网段,这些IP网段存在以下转化:
#encoding=utf-8
#base_str为IP网段的开始,一般为形如"192.168.1.0"的字符串
base_str = "192.168.1.0"
#submask_str为IP网段的子网掩码,一般为形如"255.255.255.0"的字符串
submask_str = "255.255.255.0"
#base_str,submask_str的int形式,用到了1.1的ip2num
base = ip2num(base_str) #3232235776
submask = ip2num(submask_str) #4294967040
#IP网段包含的IP地址个数,以及反掩码的int形式
ipnum = (1<<32)-1-submask #256
#子网掩码的反掩码的字符串形式
revmask = num2ip(ipnum) #"0.0.0.255"
#判断某个ip是否在该IP网段中,可以使用1.2.3中的isIpInSubnet方法
isIpInSubnet("192.168.1.1",(base,widcmask)) #True
在H3C的防火墙配置中,会有这样的定义语句
object-group ip address dbserve
0 network subnet 40.10.21.0 255.255.255.0
10 network subnet 45.20.21.0 255.255.255.0
20 network subnet 45.0.16.0 wildcard 0.255.0.255
其中的“20 network subnet 45.0.16.0 wildcard 0.255.0.255”使用的便是通配符掩码定义IP网段,这些IP网段存在以下转化:
#encoding=utf-8
#base_str为IP网段的开始,一般为形如"192.168.1.0"的字符串
base_str = "192.168.1.0"
#submask_str为IP网段的通配符掩码,一般为形如"127.255.31.0"的字符串
widcmask_str = "127.255.31.0"
#base_str,submask_str的int形式,用到了1.1的ip2num
base = ip2num(base_str) #3232235776
widcmask = ip2num(widcmask_str) #2147426048
#IP网段包含的IP地址个数,以及反掩码的int形式
ipnum = (1<<32)-1-submask #2147541247
#子网掩码的反掩码的字符串形式
revmask = num2ip(ipnum) #"128.0.224.255"
#判断某个ip是否在该IP网段中,可以使用1.2.3中的isIpInSubnet方法
isIpInSubnet(ip2num("192.168.1.0"),(base,widcmask)) #True
在思科ASA防火墙中,存在这样的定义语句
object network IP_20.2.132.137_154
range 20.2.132.137 20.2.132.154
object network host_20.2.190.2-9
range 20.2.190.2 20.2.190.9
使用的初始IP地址+末尾IP地址的方式定义IP网段,这些IP网段存在以下转化:
#encoding=utf-8
#base_str为IP网段的开始,一般为形如"192.168.1.1"的字符串
base_str = "192.168.1.1"
#end_str为IP网段的结尾,一般为形如"192.168.1.136"的字符串
end_str = "192.168.1.101"
#base_str,submask_str的int形式,用到了1.1的ip2num
base = ip2num(base_str) #3232235777
end = ip2num(widcmask_str) #3232235877
#IP网段包含的IP地址个数,以及反掩码的int形式
ipnum = end-base+1 #101
#判断某个ip是否在该IP网段中,需要另外写方法
def isIpInSubnetRange(ip,ranges)
base,end = ranges
if ip>=base and ip <=end:
return True
return False
isIpInSubnetRange(ip2num("192.168.1.100"),(base,end)) #True
在1.3介绍的三种IP网段表现形式中,“初始IP地址+末尾IP地址”(后称“区间”)这种表现方式是最通用的。其他两种表现形式都能等价地转化成一个或多个区间的形式(转化成多个区间一般为使用通配符掩码),但任取一个区间,不一定能很好地转化成其它两种表现形式(可以实现但产生结果比较复杂)。
下面分别对“初始IP地址+子网掩码”、“初始IP地址+通配符掩码”转成“初始IP地址,末尾IP地址”做了代码示例,并提供了区间的合并函数。
def standard2Range(ipobject):
"""
(初始IP地址,子网掩码)->(初始IP地址,末尾IP地址)
输入输出全部为int数
"""
ip,submask = ipobject
ipnum = (1<<32)-1-submask
return ip,ip+ipnum
def wildcard2Ranges(ipobject):
"""
(初始IP地址,通配符掩码)->[(初始IP地址,末尾IP地址)...]
输入输出全部为int数
仅提供转化方法,实际转换出来的区间数根据通配符掩码会非常非常多,建议使用wildcard mask的地址不参与整体的合并。
"""
ip, wildcardmask = ipobject
wildcardmasksav,icount,nums = wildcardmask, 0,[]
while wildcardmask!=0:
if wildcardmask&1==1:
if icount<0:
nums.append(icount)
icount = 1
else:
icount+=1
else:
if icount>0:
nums.append(icount)
icount = -1
else:
icount -= 1
wildcardmask = wildcardmask >> 1
if icount!=0:
nums.append(icount)
baseRange = (0,(1<<nums[0])-1)
cnt = nums[0]
multiplyRanges = []
for it in nums[1:]:
if it>0:
rs = (cnt,(1<<it)-1)
multiplyRanges.append(rs)
cnt += abs(it)
lastresult = []
for rs in multiplyRanges:
cnt,base =rs
newresult = []
if not lastresult:
for i in range(base + 1):
lastresult.append([i << cnt])
else:
for i in range(base+1):
for item in lastresult:
nt = item+[i<<cnt]
newresult.append(nt)
lastresult = newresult
base = ip& ((1<<32)-1-wildcardmask)
res = []
for it in lastresult:
start = base + sum(it)
end = start + baseRange[1]
res.append((start,end))
return res
def sectionInsert(discreteOrNot,subs,*addsubs):
"""
用来合并subs和addsubs,subs是区间的集合,addsubs也是区间的集合
discreteOrNot用于区分是否是离散数据,比如 subs=[(1,3)] addsubs=[(4,5)],
在离散的做法中,3和4这两个数字是连续的,subs和addsubs可以合并成[(1,5)]
但在连续的做法中,3和4之间还有大量空白,比如3.1、3.2、3.3..,是不连续的,最终结果为[(1,3),(4,5)]
"""
for addsub in addsubs:
subs.append(addsub)
subs=list(set(subs)) #去重
subs = sorted(subs,key=lambda k:(k[0],k[1]))
subsextend = []
for i in range(len(subs)):
if discreteOrNot:
subsextend.append([subs[i][0]-0.6, 2 * i])
subsextend.append([subs[i][1]+0.6, 2 * i + 1])
else:
subsextend.append([subs[i][0],2*i])
subsextend.append([subs[i][1],2*i+1])
subsextend.sort(key=lambda k:k[0])
start,end= 0,0
final = []
for i in range(len(subsextend)):
target = subsextend[i]
if i%2==0 and target[1]==i:
start = target[0]
elif i%2==1 and target[1]==i:
end = target[0]
if discreteOrNot:
final.append((int(start+0.6),int(end-0.6)))
else:
final.append((start,end))
return final
sectionInsert(True,[(1,3)],(4,5))
以下为三层采用IP封装的,协议号不同的协议(靠IP头部中的协议字段区分),来自CISCO官网文档。
Literal | Value | Description |
---|---|---|
ah | 51 | Authentication Header for IPv6, RFC 1826. |
eigrp | 88 | Enhanced Interior Gateway Routing Protocol. |
esp | 50 | Encapsulated Security Payload for IPv6, RFC 1827. |
gre | 47 | Generic Routing Encapsulation. |
icmp | 1 | Internet Control Message Protocol, RFC 792. |
icmp6 | 58 | Internet Control Message Protocol for IPv6, RFC 2463. |
igmp | 2 | Internet Group Management Protocol, RFC 1112. |
igrp | 9 | Interior Gateway Routing Protocol. |
ip | 0 | Internet Protocol. |
ipinip | 4 | IP-in-IP encapsulation. |
ipsec | 50 | IP Security. Entering the ipsec protocol literal is equivalent to entering the esp protocol literal. |
nos | 94 | Network Operating System (Novell’s NetWare). |
ospf | 89 | Open Shortest Path First routing protocol, RFC 1247. |
pcp | 108 | Payload Compression Protocol. |
pim | 103 | Protocol Independent Multicast. |
pptp | 47 | Point-to-Point Tunneling Protocol. Entering the pptp protocol literal is equivalent to entering the gre protocol literal. |
snp | 109 | Sitara Networks Protocol. |
tcp | 6 | Transmission Control Protocol, RFC 793. |
udp | 17 | User Datagram Protocol, RFC 768. |
以下为运行在四层TCP/UDP的协议
Literal | TCP or UDP? | Value | Description |
---|---|---|---|
aol | TCP | 5190 | America Online |
bgp | TCP | 179 | Border Gateway Protocol, RFC 1163 |
biff | UDP | 512 | Used by mail system to notify users that new mail is received |
bootpc | UDP | 68 | Bootstrap Protocol Client |
bootps | UDP | 67 | Bootstrap Protocol Server |
chargen | TCP | 19 | Character Generator |
cifs | TCP, UDP | 3020 | Common Internet File System |
citrix-ica | TCP | 1494 | Citrix Independent Computing Architecture (ICA) protocol |
cmd | TCP | 514 | Similar to exec except that cmd has automatic authentication |
ctiqbe | TCP | 2748 | Computer Telephony Interface Quick Buffer Encoding |
daytime | TCP | 13 | Day time, RFC 867 |
discard | TCP, UDP | 9 | Discard |
dnsix | UDP | 195 | DNSIX Session Management Module Audit Redirector |
domain | TCP, UDP | 53 | DNS |
echo | TCP, UDP | 7 | Echo |
exec | TCP | 512 | Remote process execution |
finger | TCP | 79 | Finger |
ftp | TCP | 21 | File Transfer Protocol (control port) |
ftp-data | TCP | 20 | File Transfer Protocol (data port) |
gopher | TCP | 70 | Gopher |
h323 | TCP | 1720 | H.323 call signaling |
hostname | TCP | 101 | NIC Host Name Server |
http | TCP, UDP | 80 | World Wide Web HTTP |
https | TCP | 443 | HTTP over SSL |
ident | TCP | 113 | Ident authentication service |
imap4 | TCP | 143 | Internet Message Access Protocol, version 4 |
irc | TCP | 194 | Internet Relay Chat protocol |
isakmp | UDP | 500 | Internet Security Association and Key Management Protocol |
kerberos | TCP, UDP | 750 | Kerberos |
klogin | TCP | 543 | KLOGIN |
kshell | TCP | 544 | Korn Shell |
ldap | TCP | 389 | Lightweight Directory Access Protocol |
ldaps | TCP | 636 | Lightweight Directory Access Protocol (SSL) |
login | TCP | 513 | Remote login |
lotusnotes | TCP | 1352 | IBM Lotus Notes |
lpd | TCP | 515 | Line Printer Daemon - printer spooler |
mobile-ip | UDP | 434 | Mobile IP-Agent |
nameserver | UDP | 42 | Host Name Server |
netbios-dgm | UDP | 138 | NetBIOS Datagram Service |
netbios-ns | UDP | 137 | NetBIOS Name Service |
netbios-ssn | TCP | 139 | NetBIOS Session Service |
nfs | TCP, UDP | 2049 | Network File System - Sun Microsystems |
nntp | TCP | 119 | Network News Transfer Protocol |
ntp | UDP | 123 | Network Time Protocol |
pcanywhere-data | TCP | 5631 | pcAnywhere data |
pcanywhere-status | UDP | 5632 | pcAnywhere status |
pim-auto-rp | TCP, UDP | 496 | Protocol Independent Multicast, reverse path flooding, dense mode |
pop2 | TCP | 109 | Post Office Protocol - Version 2 |
pop3 | TCP | 110 | Post Office Protocol - Version 3 |
pptp | TCP | 1723 | Point-to-Point Tunneling Protocol |
radius | UDP | 1645 | Remote Authentication Dial-In User Service |
radius-acct | UDP | 1646 | Remote Authentication Dial-In User Service (accounting) |
rip | UDP | 520 | Routing Information Protocol |
rsh | TCP | 514 | Remote Shell |
rtsp | TCP | 554 | Real Time Streaming Protocol |
secureid-udp | UDP | 5510 | SecureID over UDP |
sip | TCP, UDP | 5060 | Session Initiation Protocol |
smtp | TCP | 25 | Simple Mail Transport Protocol |
snmp | UDP | 161 | Simple Network Management Protocol |
snmptrap | UDP | 162 | Simple Network Management Protocol - Trap |
sqlnet | TCP | 1521 | Structured Query Language Network |
ssh | TCP | 22 | Secure Shell |
sunrpc | TCP, UDP | 111 | Sun Remote Procedure Call |
syslog | UDP | 514 | System Log |
tacacs | TCP, UDP | 49 | Terminal Access Controller Access Control System Plus |
talk | TCP, UDP | 517 | Talk |
telnet | TCP | 23 | RFC 854 Telnet |
tftp | UDP | 69 | Trivial File Transfer Protocol |
time | UDP | 37 | Time |
uucp | TCP | 540 | UNIX-to-UNIX Copy Program |
vxlan | UDP | 4789 | Virtual eXtensible Local Area Network (VXLAN) |
who | UDP | 513 | Who |
whois | TCP | 43 | Who Is |
www | TCP, UDP | 80 | World Wide Web |
xdmcp | UDP | 177 | X Display Manager Control Protocol |
以下为采取树状层级对协议进行归类的一个python代码示例。
protoSet = {
"ah":{
"v":51,
"children":{}
},
"eigrp":{
"v":88,
"children":{}
},
"esp":{
"v":50,
"children":{}
},
"gre":{
"v":47,
"children":{}
},
"icmp":{
"v":1,
"children":{
"echo-reply": { "v":0},
"unreachable": { "v":3},
"source-quench": { "v":4},
"redirect": { "v":5},
"alternate-address": { "v":6},
"echo": { "v":8},
"router-advertisement": { "v":9},
"router-solicitation": { "v":10},
"time-exceeded": { "v":11},
"parameter-problem": { "v":12},
"timestamp-request": { "v":13},
"timestamp-reply": { "v":14},
"information-request": { "v":15},
"information-reply": { "v":16},
"mask-request": { "v":17},
"mask-reply": { "v":18},
"traceroute": { "v":30},
"conversion-error": { "v":31},
"mobile-redirect": { "v":32},
}
},
"icmp6":{
"v":58,
"children":{}
},
"igmp":{
"v":2,
"children":{}
},
"igrp":{
"v":9,
"children":{}
},
"ip":{
"v":0,
"children":{}
},
"ipinip":{
"v":4,
"children":{}
},
"ipsec":{
"v":50,
"children":{}
},
"nos":{
"v":94,
"children":{}
},
"ospf":{
"v":89,
"children":{}
},
"pcp":{
"v":108,
"children":{}
},
"pim":{
"v":103,
"children":{}
},
"pptp":{
"v":47,
"children":{}
},
"snp":{
"v":109,
"children":{}
},
"tcp":{
"v":6,
"children":{
"aol": { "v":5190},
"bgp": { "v":179},
"chargen": { "v":19},
"cifs": { "v":3020},
"citrix-ica": { "v":1494},
"cmd": { "v":514},
"ctiqbe": { "v":2748},
"daytime": { "v":13},
"discard": { "v":9},
"domain": { "v":53},
"echo": { "v":7},
"exec": { "v":512},
"finger": { "v":79},
"ftp": { "v":21},
"ftp-data": { "v":20},
"gopher": { "v":70},
"h323": { "v":1720},
"hostname": { "v":101},
"http": { "v":80},
"https": { "v":443},
"ident": { "v":113},
"imap4": { "v":143},
"irc": { "v":194},
"kerberos": { "v":750},
"klogin": { "v":543},
"kshell": { "v":544},
"ldap": { "v":389},
"ldaps": { "v":636},
"login": { "v":513},
"lotusnotes": { "v":1352},
"lpd": { "v":515},
"netbios-ssn": { "v":139},
"nfs": { "v":2049},
"nntp": { "v":119},
"pcanywhere-data": { "v":5631},
"pim-auto-rp": { "v":496},
"pop2": { "v":109},
"pop3": { "v":110},
"pptp": { "v":1723},
"rsh": { "v":514},
"rtsp": { "v":554},
"sip": { "v":5060},
"smtp": { "v":25},
"sqlnet": { "v":1521},
"ssh": { "v":22},
"sunrpc": { "v":111},
"tacacs": { "v":49},
"talk": { "v":517},
"telnet": { "v":23},
"uucp": { "v":540},
"whois": { "v":43},
"www": { "v":80}
}
},
"udp":{
"v":17,
"children":{
"biff": {"v":512},
"bootpc": {"v":68},
"bootps": {"v":67},
"cifs": {"v":3020},
"discard": {"v":9},
"dnsix": {"v":195},
"domain": {"v":53},
"echo": {"v":7},
"http": {"v":80},
"isakmp": {"v":500},
"kerberos": {"v":750},
"mobile-ip": {"v":434},
"nameserver": {"v":42},
"netbios-dgm": {"v":138},
"netbios-ns": {"v":137},
"nfs": {"v":2049},
"ntp": {"v":123},
"pcanywhere-status": {"v":5632},
"pim-auto-rp": {"v":496},
"radius": {"v":1645},
"radius-acct": {"v":1646},
"rip": {"v":520},
"secureid-udp": {"v":5510},
"sip": {"v":5060},
"snmp": {"v":161},
"snmptrap": {"v":162},
"sunrpc": {"v":111},
"syslog": {"v":514},
"tacacs": {"v":49},
"talk": {"v":517},
"tftp": {"v":69},
"time": {"v":37},
"vxlan": {"v":4789},
"who": {"v":513},
"www": {"v":80},
"xdmcp": {"v":177},
}
}
}
以下是ASA防火墙中object的定义方式的分类整理(暂不包含wildcardmask的情况)。
以下是ASA防火墙中object-group的定义方式的分类。
以下是ASA防火墙中access-list的定义方式的分类,共分为标准和拓展两种
因上图没有对acl规则进行详细描述,附上用于匹配的正则表达式作为补充。
# 对标准acl进行匹配,如access-list out permit tcp any host 192.168.0.10 eq http
aclstandard = re.compile(r'^access-list ([^\s]+) (permit|deny) ([^\s]+) '
r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+) '
r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+)'
r'( eq [^\s]+)?')
# 对拓展acl进行匹配,如access-list Web_access_in extended permit object TCP8888 host 10.2.101.13 object 10.2.94.13
aclextend = re.compile(r'^access-list ([\w\W]+) extended (permit|deny) '
r'(any|object [^\s]+|object-group [^\s]+|[^\s]+) '
r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+|object [^\s]+|object-group [^\s]+) '
r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+|object [^\s]+|object-group [^\s]+)'
r'( eq [^\s]+)?')
本文暂不对NAT配置进行分类,后续会有nat的专题进行讲解。
以下为Juniper防火墙中address-book的整理分类示例(暂不包含wildcardmask的情况)。
#初始IP地址/短子网掩码
set security zones security-zone INTERNET address-book address 135.224.178.133/32 35.224.178.133/32
set security zones security-zone INTERNET address-book address 121.7.106.0/24 111.7.106.0/24
#初始IP地址/末尾IP地址
set security zones security-zone INTERNET address-book DNS10 range-address 192.168.1.10 to 192.168.1.100
#地址集,用于嵌套多个地址
set security zones security-zone INTERNET address-book address-set DNSGROUP address DNS10
set security zones security-zone INTERNET address-book address-set DNSGROUP address 135.224.178.133/32
set security zones security-zone INTERNET address-book address-set DNSGROUP address 121.7.106.0/24
以下为juniper防火墙中application的整理分类示例。
#指定单个端口
set applications application TCP8102 protocol tcp
set applications application TCP8102 destination-port 8102
#指定多个端口
set applications application tcp12140-12144 protocol tcp
set applications application tcp12140-12144 destination-port 12140-12144
#指定应用集
set applications application-set ChinaPay-Port application tcp12140-12144
set applications application-set ChinaPay-Port application TCP8102
以下为juniper防火墙中policy的整理分类示例。
#通用的配置格式需要包含source-address,destination-address,application,action(then)四部分
set security policies from-zone exd to-zone exb policy 3771 match source-address 202.104.148.138/32
set security policies from-zone exd to-zone exb policy 3771 match destination-address 10.14.196.83/32
set security policies from-zone exd to-zone exb policy 3771 match application junos-icmp-all
set security policies from-zone exd to-zone exb policy 3771 match application tcp32003
set security policies from-zone exd to-zone exb policy 3771 then permit
本文暂不对NAT配置进行分类,后续会有nat的专题进行讲解。
以下为H3C防火墙中object-group的整理分类示例。
#以下演示了三种IP网段的定义形式,都可以放在object-group中
object-group ip address Yesnet
security-zone Untrust
0 network subnet 45.51.32.0 255.255.255.0
10 network range 70.67.45.11 70.67.45.12
20 network subnet 15.48.128.0 wildcard 0.15.126.255
30 network subnet 15.48.64.64 wildcard 0.15.31.63
#以下演示了两种服务的定义形式,也可以放在object-group中
object-group service 管理端口
0 service tcp destination eq 9300
10 service udp destination range 10162 10181
以下为H3C防火墙中rule的示例。
##通用的配置格式需要包含source-ip,destination-ip,service,action四部分
rule 149 name 某安一账通
action pass
source-zone Trust
destination-zone Untrust
source-ip 一账通测试机
destination-ip 一账通入口地址
service 管理端口
service 8006
service http
本文暂不对NAT配置进行分类,后续会有nat的专题进行讲解。
一般地,不考虑做NAT的情况,防火墙的配置会包含以下的信息。
输出项名称 | CISCO防火墙 | Juniper防火墙 | H3C防火墙 |
---|---|---|---|
action | √ | √ | √ |
源地址 | √ | √ | √ |
目地址 | √ | √ | √ |
源端口 | x | √ | x |
目端口 | √ | √ | √ |
策略名 | √ | √ | √ |
其中策略名、action、源地址、目标地址、目标端口/协议都会被包含,下面的解析代码也会输出这五项数据。IP地址全部用的是int数的形式
#输出项
#每一行的列表内容分别为策略名称、action、源地址、目标地址、目标端口/协议
#针对源目地址的ip网段,此处不考虑wildcard mask的情况,每一个都为 (开始地址,目标地址-开始地址)
#int数不易人工读取,可以自行将之转化成易读的形式,参考3.2的代码进行转化
['GLOBAL', 'permit', [(167945216, 255), (167945728, 255)], [(167940236, 2)], [('tcp', (24019, 24019))]]
['GLOBAL', 'permit', [(167945767, 1)], [(167927432, 0)], [('tcp', (9195, 9195))]]
['GLOBAL', 'permit', [(167945718, 0)], [(486610688, 255)], [('udp', (514, 514))]]
['GLOBAL', 'permit', [(167945728, 255)], [(487629436, 3)], [('tcp', (50883, 50883))]]
参考代码如下,该代码可以将一份ASA防火墙配置解析成3.1中给出的形式。
#encoding=utf-8
import re
import struct
import socket
import os
from collections import defaultdict
from functools import partial
#cisco官网搜集到的协议名到协议号的映射
protoSet={'ah':{'v':51,'children':{}},'eigrp':{'v':88,'children':{}},'esp':{'v':50,'children':{}},'gre':{'v':47,'children':{}},'icmp':{'v':1,'children':{'echo-reply':{'v':0},'unreachable':{'v':3},'source-quench':{'v':4},'redirect':{'v':5},'alternate-address':{'v':6},'echo':{'v':8},'router-advertisement':{'v':9},'router-solicitation':{'v':10},'time-exceeded':{'v':11},'parameter-problem':{'v':12},'timestamp-request':{'v':13},'timestamp-reply':{'v':14},'information-request':{'v':15},'information-reply':{'v':16},'mask-request':{'v':17},'mask-reply':{'v':18},'traceroute':{'v':30},'conversion-error':{'v':31},'mobile-redirect':{'v':32},}},'icmp6':{'v':58,'children':{}},'igmp':{'v':2,'children':{}},'igrp':{'v':9,'children':{}},'ip':{'v':0,'children':{}},'ipinip':{'v':4,'children':{}},'ipsec':{'v':50,'children':{}},'nos':{'v':94,'children':{}},'ospf':{'v':89,'children':{}},'pcp':{'v':108,'children':{}},'pim':{'v':103,'children':{}},'pptp':{'v':47,'children':{}},'snp':{'v':109,'children':{}},'tcp':{'v':6,'children':{'aol':{'v':5190},'bgp':{'v':179},'chargen':{'v':19},'cifs':{'v':3020},'citrix-ica':{'v':1494},'cmd':{'v':514},'ctiqbe':{'v':2748},'daytime':{'v':13},'discard':{'v':9},'domain':{'v':53},'echo':{'v':7},'exec':{'v':512},'finger':{'v':79},'ftp':{'v':21},'ftp-data':{'v':20},'gopher':{'v':70},'h323':{'v':1720},'hostname':{'v':101},'http':{'v':80},'https':{'v':443},'ident':{'v':113},'imap4':{'v':143},'irc':{'v':194},'kerberos':{'v':750},'klogin':{'v':543},'kshell':{'v':544},'ldap':{'v':389},'ldaps':{'v':636},'login':{'v':513},'lotusnotes':{'v':1352},'lpd':{'v':515},'netbios-ssn':{'v':139},'nfs':{'v':2049},'nntp':{'v':119},'pcanywhere-data':{'v':5631},'pim-auto-rp':{'v':496},'pop2':{'v':109},'pop3':{'v':110},'pptp':{'v':1723},'rsh':{'v':514},'rtsp':{'v':554},'sip':{'v':5060},'smtp':{'v':25},'sqlnet':{'v':1521},'ssh':{'v':22},'sunrpc':{'v':111},'tacacs':{'v':49},'talk':{'v':517},'telnet':{'v':23},'uucp':{'v':540},'whois':{'v':43},'www':{'v':80}}},'udp':{'v':17,'children':{'biff':{'v':512},'bootpc':{'v':68},'bootps':{'v':67},'cifs':{'v':3020},'discard':{'v':9},'dnsix':{'v':195},'domain':{'v':53},'echo':{'v':7},'http':{'v':80},'isakmp':{'v':500},'kerberos':{'v':750},'mobile-ip':{'v':434},'nameserver':{'v':42},'netbios-dgm':{'v':138},'netbios-ns':{'v':137},'nfs':{'v':2049},'ntp':{'v':123},'pcanywhere-status':{'v':5632},'pim-auto-rp':{'v':496},'radius':{'v':1645},'radius-acct':{'v':1646},'rip':{'v':520},'secureid-udp':{'v':5510},'sip':{'v':5060},'snmp':{'v':161},'snmptrap':{'v':162},'sunrpc':{'v':111},'syslog':{'v':514},'tacacs':{'v':49},'talk':{'v':517},'tftp':{'v':69},'time':{'v':37},'vxlan':{'v':4789},'who':{'v':513},'www':{'v':80},'xdmcp':{'v':177},}}}
# 简单对1.1.1.1这种ip做匹配
ip_host_regex =re.compile(r'\s*[0-9]+.[0-9]+.[0-9]+.[0-9]+\s*$')
# 简单对1.1.1.1 255.255.255.255这种ip做匹配
ip_subnet_regex =re.compile(r'\s*[0-9]+.[0-9]+.[0-9]+.[0-9]+\s*[0-9]+.[0-9]+.[0-9]+.[0-9]+\s*$')
# 简单对1.1.1.1/32这种ip做匹配
ip_short_regex =re.compile(r'\s*[0-9]+.[0-9]+.[0-9]+.[0-9]+/[0-9]+\s*$')
#建立短掩码到真实掩码的映射,如31->11111111111111111111111111111110
Short2num = {str(i):(1<<32)-(1<<(32-i)) for i in range(1,33)}
# 对标准acl进行匹配,如access-list out permit tcp any host 192.168.0.10 eq http
aclstandard = re.compile(r'^access-list ([^\s]+) (permit|deny) ([^\s]+) '
r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+) '
r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+)'
r'( eq [^\s]+)?')
# 对拓展acl进行匹配,如access-list Web_access_in extended permit object TCP8888 host 10.2.101.13 object 10.2.94.13
aclextend = re.compile(r'^access-list ([\w\W]+) extended (permit|deny) '
r'(any|object [^\s]+|object-group [^\s]+|[^\s]+) '
r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+|object [^\s]+|object-group [^\s]+) '
r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+|object [^\s]+|object-group [^\s]+)'
r'( eq [^\s]+)?')
#object语句的开头,用来判断是否是object/object-group语句的有效组成成分
ObjectConfigStart=["object", "host", "subnet", "range", "service", "object-group", "network-object", "protocol-object", "icmp-object",
"port-object", "service-object", "group-object"]
def ip2num(ip):
"""将点分十进制ip转化成int数"""
return struct.unpack("!L", socket.inet_aton(ip))[0]
def num2ip(num):
"""将int数转化成对应的点分十进制ip"""
a, c, b, d = (num & 0xff000000) >> 24, (num & 0x0000ff00) >> 8, (num & 0x00ff0000) >> 16, (num & 0x000000ff) >> 0
return "{}.{}.{}.{}".format(a, b, c, d)
def short2num(short):
"""短掩码转化成int格式的长掩码"""
short = str(short)
return Short2num[short]
def ipstr2num(ipstr):
"""将字符串形式的 ip、掩码 转化为两个int数 (开始,结束)"""
if ip_host_regex.match(ipstr): #形如"192.168.1.1"格式进行转化,因为配置中的ip一般都无误,故编写正则表达式较简单,如用于校验请另外编写正则
return ip2num(ipstr.strip()),0
elif ip_subnet_regex.match(ipstr): #形如"192.168.1.1 255.255.255.0"格式进行转化
ip, subnet = ipstr.strip().split()
ipn, shortn = ip2num(ip), ip2num(subnet)
return ipn&shortn,short2num(32)-shortn
elif ip_short_regex.match(ipstr): #形如"192.168.1.1/31"格式进行转化
ip, short = ipstr.split("/")
ipn, shortn = ip2num(ip),short2num(short)
return ipn&shortn,short2num(32)-shortn
print("ipstr2num: '{}' can't be trans".format(ipstr))
def protoTransGetNum(proto3layer,key):
"""获取某一协议名对应的协议号"""
if key in proto3layer["children"]:
num = proto3layer["children"][key]["v"]
else:
num = int(key)
return num
def protoTrans(cmdstr):
"""对形如以下的配置做解析:tcp destination range 6600 6699,tcp eq telnet,esp"""
if cmdstr=="any":
return "ip",(0,0)
info = cmdstr.strip().split()
if "destination" in info:
info.remove("destination")
proto3 = info[0]
proto3layer = protoSet[info[0]]
#protoNumIn3LayerHeader = proto3layer["v"]
if len(info) >= 3:
if info[1] == "eq":
num = protoTransGetNum(proto3layer,info[2])
return proto3,(num,num)
elif info[1]=="lt": #有些防火墙的lt是<=,有些防火墙的lt是<,此处默认为<
base = protoTransGetNum(proto3layer,info[2])
return proto3,(0,base-1)
elif info[1] == "lte":
base = protoTransGetNum(proto3layer,info[2])
return proto3,(0, base)
elif info[1] == "gt":
base = protoTransGetNum(proto3layer,info[2])
return proto3,(base+1, 65535)
elif info[1] == "gte":
base = protoTransGetNum(proto3layer,info[2])
return proto3,(base, 65535)
elif info[1] == "range":
if len(info)>=4:
base,end = protoTransGetNum(proto3layer,info[2]),protoTransGetNum(proto3layer,info[3])
return proto3, (base,end)
print("protoTrans: '{}' can't be trans".format(cmdstr))
return None
else:
if proto3=="tcp" or proto3=="udp":
return proto3, (0, 65535)
return proto3,(0,0)
def addressTrans(cmdstr):
"""对形如以下的配置做解析:host 202.100.1.1,subnet 202.100.1.0 255.255.255.0,range 202.100.2.10 202.100.2.20"""
if cmdstr=="any":
return 0,1<<32-1
info = cmdstr.strip().split()
if len(info)>=2:
if info[0]=="range":
if len(info)>=3:
base,_ = ipstr2num(info[1])
end, _ = ipstr2num(info[2])
return base,end-base
else:
cstr = cmdstr.replace("host","").replace("subnet","").strip()
return ipstr2num(cstr)
print("addressTrans: '{}' can't be trans".format(cmdstr))
def sectionInsert(discreteOrNot,subs,*addsubs):
"""
用来合并subs和addsubs,subs是区间的集合,addsubs也是区间的集合
discreteOrNot用于区分是否是离散数据,比如 subs=[(1,3)] addsubs=[(4,5)],
在离散的做法中,3和4这两个数字是连续的,subs和addsubs可以合并成[(1,5)]
但在连续的做法中,3和4之间还有大量空白,比如3.1、3.2、3.3..,是不连续的,最终结果为[(1,3),(4,5)]
"""
for addsub in addsubs:
subs.append(addsub)
subs=list(set(subs)) #去重
subs = sorted(subs,key=lambda k:(k[0],k[1]))
subsextend = []
for i in range(len(subs)):
if discreteOrNot:
subsextend.append([subs[i][0]-0.6, 2 * i])
subsextend.append([subs[i][1]+0.6, 2 * i + 1])
else:
subsextend.append([subs[i][0],2*i])
subsextend.append([subs[i][1],2*i+1])
subsextend.sort(key=lambda k:k[0])
start,end= 0,0
final = []
for i in range(len(subsextend)):
target = subsextend[i]
if i%2==0 and target[1]==i:
start = target[0]
elif i%2==1 and target[1]==i:
end = target[0]
if discreteOrNot:
final.append((int(start+0.6),int(end-0.6)))
else:
final.append((start,end))
return final
class ObjectBlock(object):
"""用来存放一个object/object-group配置块,并且对其解析,产生对应的数据,可以自行调用里面的属性做更多的操作(如压缩配置)"""
def __init__(self,firstline):
"""用object-group network DM (第一行)这样的配置来初始化配置块"""
self.config = [firstline] #用来存放一般的配置语句
self.quoteconfig = [] #用来存放引用到其他object/object-group的配置语句,完整的config=config+quoteconfig
self.fulldata = [] #用来存放完整的数据
self.simpleconfig = [] #用来存放简化的配置
firstSplit= firstline.strip().split()
self.FirstType = firstSplit[0] #"object"或"object-group"
self.SecondType = firstSplit[1] #"network","service","protocol"等
self.name = firstSplit[2] #配置块的名称,如"DM"
self.extra = "" #取出形似"object-group service udp.ser udp"中的"udp"
self.data = [] #本配置块的数据,如"network-object host 10.2.104.96"里的"host 10.2.104.96"会转换成(base,offset)这种形式存在此中。
self.quote = [] #本配置块引用到的其他的object/object-group名称
self.quotedata = [] #本配置块引用到的数据
if len(firstSplit)>3:
self.extra = firstSplit[3]
self.discreteInsert = partial(sectionInsert,True) #离散数据插入函数
self.continuousInsert = partial(sectionInsert, False) #连续数据插入函数
self.usedCount = 0 #本object/object-group被引用次数
def __repr__(self):
"""返回打印出来的值"""
return "{}/{}/{}".format(self.FirstType,self.SecondType,self.name)
def dataMerge(self,*datas):
"""本地数据的归并,可以输入的data有self.data,self.quotedata"""
originaldata = []
tmpdata = []
for data in datas:
originaldata.extend(data)
if self.SecondType=="network":
finaldata = []
for d in originaldata:
if isinstance(d,tuple):
tmpdata.append((d[0],d[0]+d[1]))
tmpdata = self.discreteInsert(tmpdata)
for i in range(len(tmpdata)):
d = tmpdata[i]
nd = (d[0],d[1]-d[0])
finaldata.append(nd)
else:
finaldata = []
tmpdict = defaultdict(list)
for d in originaldata:
if isinstance(d,tuple):
protoName,r = d
tmpdict[protoName] = self.discreteInsert(tmpdict[protoName],(r[0],r[1]))
for k in tmpdict:
for i in range(len(tmpdict[k])):
d = tmpdict[k][i]
nd = (d[0], d[1])
finaldata.append((k,nd))
return finaldata
def objectCheck(self,liner):
"""检查配置块下面有没有嵌套的object或object-group"""
if len(liner)>=2:
if liner[0]=="group-object":
return "/".join(("object-group",self.SecondType,liner[1]))
if len(liner)>=3:
if liner[1]=="object":
return "/".join(("object",self.SecondType,liner[2]))
return None
def addLine(self,line):
"""添加一行如network-object object APP到配置块,并计算这一行引入的数据,添加到self.data"""
liner = line.strip().split()
if not liner:
return
key = "{}|{}|{}".format(self.FirstType,self.SecondType,liner[0])
rets = self.objectCheck(liner)
if rets:
self.quote.append(rets)
self.quoteconfig.append(line.strip())
return
self.config.append(line.strip())
if key=="object-group|protocal|protocol-object":
cmdstr = "".join(liner[1:])
ret = protoTrans(cmdstr)
self.data.append(ret)
elif key=="object-group|service|port-object" or key=="object-group|service|service-object":
if self.extra:
cmdstr = "{} {}".format(self.extra," ".join(liner[1:]))
ret = protoTrans(cmdstr)
self.data.append(ret)
else:
cmdstr = " ".join(liner[1:])
ret = protoTrans(cmdstr)
self.data.append(ret)
elif key=="object-group|icmp-type|icmp-object":
cmdstr = "icmp eq {}".format(" ".join(liner[1:]))
ret = protoTrans(cmdstr)
self.data.append(ret)
elif key=="object-group|network|network-object":
cmdstr = " ".join(liner[1:])
ret = addressTrans(cmdstr)
self.data.append(ret)
elif "object|network|" in key:
if liner[0]=="host" or liner[0]=="subnet" or liner[0]=="range":
cmdstr = line.strip()
ret = addressTrans(cmdstr)
self.data.append(ret)
elif key=="object|service|service":
cmdstr = " ".join(liner[1:])
ret = protoTrans(cmdstr)
self.data.append(ret)
class ObjectBlockOperation(object):
"""ObjectBlock的操作函数,写到一起方便查阅"""
@classmethod
def objectTraverse(cls,nowblock, blocks):
"""用来递归获取quote数据,避免因嵌套使用object-group造成的数据遗漏"""
if not nowblock.quote:
return nowblock.data
retdata = []
if not nowblock.quotedata:
if nowblock.quote:
for quo in nowblock.quote:
data = cls.objectTraverse(blocks[quo], blocks)
if data:
retdata.extend(data)
return retdata
return []
return nowblock.quotedata
@classmethod
def readObjects(cls,lines):
"""用来读取所有objct/object-group配置,返回的是Block对象的集合"""
currentBlock = None #当前配置块
Blocks = {} #所有配置块的字典
Released = True
for l in lines: #对于每行配置
ler = l.split()
if not ler or ler[0] not in ObjectConfigStart: #如果不是ObjectConfigStart中的作为开头,说明不是目标配置,直接跳出
continue
if l.startswith("object"): #如果以"object"开头,说明是目标配置的第一行
if currentBlock: #如果已有上个配置块(吸收了足够的配置),就将这个代码块存入字典
Blocks[repr(currentBlock)] = currentBlock
Released = True
currentBlock = ObjectBlock(l) #用这行配置新建一个配置块
continue
if currentBlock:
currentBlock.addLine(l) #往当前配置块添加配置(这个操作会重复多次,直到下次遇到以"object"开头,再新建一个配置块)
Released = False
if not Released: #将最后一个配置块放入字典
Blocks[repr(currentBlock)] = currentBlock
names = [k for k in Blocks]
for name in names:
nowblock = Blocks[name]
if nowblock.quote:
for quo in nowblock.quote:
Blocks[quo].usedCount +=1
quetodata = cls.objectTraverse(nowblock,Blocks) #遍历获得引用的所有数据
Blocks[name].quotedata = quetodata #赋值给原本为空的quotedata
return Blocks
@classmethod
def mergeData(cls,blocks):
"""对block中的每一个block做数据合并"""
names = [k for k in Blocks]
for name in names:
block = blocks[name]
data =block.dataMerge(block.data,block.quotedata)
blocks[name].fulldata = data
return blocks
def aclAnalyze(lines,blocks):
"""拆解ACL语句,这段代码写得比较烂,因为对accest-list的所有组成情况有些不确定,如果有超出aclextend、aclstandard正则的情况请自行修改正则"""
names = [k for k in Blocks]
result = []
for l in lines:
ae = aclextend.findall(l.strip())
if ae:
pname,action,service,sour,dest,serviceadd = ae[0]
if serviceadd:
service = [protoTrans("{} {}".format(service, serviceadd))]
elif "object" in service or "object-group" in service:
se = service.split()
key = ""
for name in names:
if se[1] in name and "/network/" not in name:
key = name
break
service = blocks[key].fulldata
else:
s = protoTrans(service)
service = [s]
if "object" in sour or "object-group" in sour:
so = sour.split()
key = ""
for name in names:
if so[1] in name and "/network/" in name:
key = name
break
sour = blocks[key].fulldata
else:
s = addressTrans(sour)
sour = [s]
if "object" in dest or "object-group" in dest:
de = dest.split()
key = ""
for name in names:
if de[1] in name and "/network/" in name:
key = name
break
dest = blocks[key].fulldata
else:
s = addressTrans(dest)
dest = [s]
result.append([pname, action, sour, dest,service])
else:
ast = aclstandard.findall(l.strip())
if ast:
pname, action, service, sour, dest ,serviceadd= ast[0]
service=[protoTrans("{} {}".format(service,serviceadd))]
sour = [addressTrans(sour)]
dest = [addressTrans(dest)]
result.append([pname, action, sour, dest, service])
return result
if __name__ == '__main__':
with open('{}/asaconfcopy'.format(os.getcwd()),'r') as f: #请于此处修改配置文件名
ls=f.readlines()
OBO = ObjectBlockOperation() #创建操作类,后续可以调用里面的函数
Blocks = OBO.readObjects(ls) #读入object/object-group
Blocks = OBO.mergeData(Blocks) #对现有的数据进行整理合并(主要是有一些重合,将之消除)
Result = aclAnalyze(ls,Blocks) #用Blocks去对所有acl进行解析,得出解析结果
for s in Result:
print(s)
Juniper的策略结构比较简单易懂,可以参考2.7自行进行解析,参考代码后续补充。
H3C的策略结构在思科的基础上进行了简化,只使用了object-group,可以参考2.8自行进行解析。或者参考下面用python和c编写的防火墙命令行工具,因为篇幅限制,没办法直接把代码贴上来。代码已经打包放在下面的链接中了,里面包含1个python文件,1个c文件,均已详细注释出对应代码含义,并且有详细说明,有需要的同学可以自取。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。