netmmiko + ntc-template自动巡检,国际BGP骨干,BGP路由发布状态

工作中往往会因为各种原因导致bgp路由发布异常,如RPKI信息异常,路由IRR信息异常等

当我们AS下前缀太多时经常不能及时发现其中某段ip的发布异常,从故障到等待用户反馈,手动定位故障点到修复,这期间所损耗的SLA无法估计
基于以上考虑,想着写一个巡检脚本定期巡检BGP 路由状态

以下脚本依赖于AT&T运营商骨干路由器的数据

脚本环境
python3.8 以上
netmmiko ntc-template

目录
.
├── bgproute_status.py
├── conf.json
├── log
└── textfsm
  ├── index
  └── show_route.textfsm

conf.json

{
    "ip":[
        "183.91.33.0","183.91.34.0","183.91.35.0",
        "183.91.36.0","183.91.37.0","183.91.38.0",
        "183.91.39.0","183.91.40.0","183.91.41.0",
        "183.91.42.0","183.91.43.0","183.91.44.0"
        ],
    "mail":{
        "mail_username":"976584601@qq.com",
        "mail_passwd":"xxxx",
        "smtp_server":"smtp.qq.com",
        "sender":"xxx",
        "from_addr":"976584601@qq.com",
        "mail_list":["xxxx@xxxx.com","cs11241991@163.com"]
    }
}

textfms.index

Template, Hostname, Platform, Command
show_route.textfsm, .*, juniper_junos_telnet, sh[[ow]] route .*

textfms.show_route.textfsm

Value List prefix (\S+)
Value List aspath (.*)
Value List uptime (.*)
Value List validation (\S+)


Start
  ^.* -> Fan

Fan
  ^${prefix}\s+\S+\s+${uptime}, localpref 
  ^\s+AS path:${aspath}, validation-state: ${validation} -> Record EOF

bgproute_status.py

#! /usr/bin/env python
#-*-coding:utf-8-*-
from netmiko import ConnectHandler
import json,os,sys,logging
import time
import smtplib
from email.utils import formataddr
from email.mime.text import MIMEText
import socket
from email.mime.multipart import MIMEMultipart
def logger():
    log_name = BaseDir+'/log'
    logger = logging.getLogger()
    fh = logging.FileHandler(log_name)
    formater = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
    fh.setFormatter(formater)
    logger.setLevel(logging.WARNING)
    logger.addHandler(fh)
    return logger
def connect(hosts):
       dic={}
       problem = {}
       count = 0
       sendmailflag = False
       attip = socket.gethostbyname('route-server.ip.att.net')
       dev = {'device_type': 'juniper_junos_telnet',
              'host': attip,
              'username': 'rviews',
              'password':'rviews',
              'timeout':180}
       while count<10:
            try:
                with ConnectHandler(**dev) as conn:
                    for host in hosts:
                        command = 'show route {}'.format(host)
                        ret=conn.send_command(command,use_textfsm=True,read_timeout=180)[0]
                        try:
                            uptime = ret['uptime'][0]
                            h,m,s = uptime[-8:].split(':')
                            if int(m)<50 and len(uptime)<9 and int(h)<1:
                                sendmailflag = True
                                problem[host] = ret
                        except:
                            ret = {'error':'无路由信息'}
                            problem[host] = ret
                            sendmailflag = True
                        dic[host] = ret
                    return problem,dic,sendmailflag
            except Exception as e:
                count+=1
                log.warning(e)
def sendmail(mail_conf,content):
       try:
           subject = '%sBGP路由信息异常'%(time.strftime('%Y-%m-%d',time.localtime()))
           username,passwd = mail_conf["mail_username"],mail_conf["mail_passwd"]
           smtp_server,sender = mail_conf["smtp_server"],mail_conf["sender"]
           from_addr,mail_list = mail_conf["from_addr"],mail_conf["mail_list"]
           msg = MIMEMultipart('alternative')
           msg.attach(MIMEText(content, 'html'))
           msg['from'] = formataddr([sender,from_addr])
           msg['to'] = ','.join(mail_list)
           msg['subject'] = subject
           service = smtplib.SMTP(smtp_server,timeout=5)
           service.login(username,passwd)
           service.sendmail(from_addr,mail_list,msg.as_string())
           service.quit()
       except Exception as mail_error:
           log.warning(mail_error)
def tohtml(content):
        html = '<table>'
        th = '<tr style="text-align:left;"><th>{}</th></tr>'
        td = '<tr ><td>{}</td><td>{}</td></tr>'
        for host in content:
            html+=th.format(host)
            for module in content[host]:
                if  isinstance(content[host][module],list):
                    for tag in content[host][module]:
                        html+=td.format(module,tag)
                else:
                    html+= td.format(module,content[host][module])
        html+='</table>'
        return html
if __name__ == '__main__':
    BaseDir = os.path.dirname(os.path.realpath(sys.argv[0]))
    os.environ['NET_TEXTFSM'] = BaseDir+'/textfsm'
    log = logger()
    with open(BaseDir+"/conf.json","r") as a:
       conf =json.loads(a.read())
    problem,ret,sendmailflag = connect(conf['ip'])
    log.warning(str(ret))
    if sendmailflag:
        content = tohtml(problem)
        mail_conf = conf['mail']
        sendmail(mail_conf,content)

如果路由信息消失或发布时间小于50分钟会通过邮件告警

查看运行log

2023-01-31 16:17:20,190 - bgproute_status.py[line:70] - WARNING: {'183.91.33.0': '无路由信息', '183.91.34.0': '无路由信息', '183.91
.35.0': '无路由信息', '183.91.36.0': '无路由信息', '183.91.37.0': '无路由信息', '183.91.38.0': '无路由信息', '183.91.39.0': '无路由
信息', '183.91.40.0': {'prefix': ['183.91.40.0/21'], 'aspath': [' 7018 4134 I'], 'uptime': ['2w5d 22:56:46'], 'validation': ['valid
']}, '183.91.41.0': {'prefix': ['183.91.40.0/21'], 'aspath': [' 7018 4134 I'], 'uptime': ['2w5d 22:56:47'], 'validation': ['valid']
}, '183.91.42.0': {'prefix': ['183.91.40.0/21'], 'aspath': [' 7018 4134 I'], 'uptime': ['2w5d 22:56:49'], 'validation': ['valid']},
 '183.91.43.0': {'prefix': ['183.91.40.0/21'], 'aspath': [' 7018 4134 I'], 'uptime': ['2w5d 22:56:50'], 'validation': ['valid']}, '
183.91.44.0': {'prefix': ['183.91.44.0/24'], 'aspath': [' 7018 4134 I'], 'uptime': ['2w5d 22:56:51'], 'validation': ['valid']}}

Leave a Reply

Your email address will not be published. Required fields are marked *

X