ulgjuniper.py 12.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
#!/usr/bin/env python
#
# ULG - Universal Looking Glass
# (C) 2012 CZ.NIC, z.s.p.o.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


# Imports
import os
import socket
import re
import pexpect
25
import hashlib
26 27 28 29 30 31

import defaults

import ulgmodel
import ulggraph

32
JUNIPER_GRAPH_SH_ROUTE = "Graph show route <IP subnet>"
33 34 35 36 37 38

IPV46_SUBNET_REGEXP = '^[0-9a-fA-F:\.]+(/[0-9]{1,2}){0,1}$'

STRING_EXPECT_SSH_NEWKEY='Are you sure you want to continue connecting'
STRING_EXPECT_LOGIN='login:'
STRING_EXPECT_PASSWORD='(P|p)assword:'
39
STRING_EXPECT_SHELL_PROMPT_REGEXP = '[^\s]+> '
40
STRING_LOGOUT_COMMAND = 'exit'
41
STRING_CLI_INFINITE_COMMAND = 'set cli screen-length 0'
42

43 44 45 46 47 48
TABLE_LINE_REGEX = "([0-9a-fA-F:\.]+)\s+([0-9]+)\s+.*"
table_line_regex = re.compile(TABLE_LINE_REGEX)

TABLE_HEADER_REGEX = "Peer\s+AS\s+InPkt\s+OutPkt\s+OutQ\s+Flaps\s+.*"
table_header_regex = re.compile(TABLE_HEADER_REGEX)

49 50 51 52 53 54 55 56 57 58
JUNIPER_BGP_PATH_ACTIVE_REGEX = ".*(\*|\+)\[BGP/[0-9]+\].*"
juniper_bgp_path_active_regex = re.compile(JUNIPER_BGP_PATH_ACTIVE_REGEX)

JUNIPER_BGP_PATH_REGEX = ".*(-|)\[BGP/[0-9]+\].*"
juniper_bgp_path_regex = re.compile(JUNIPER_BGP_PATH_REGEX)

JUNIPER_BGP_PATH_CONTENT = ".*AS path: ([0-9\s]+).*"
juniper_bgp_path_content = re.compile(JUNIPER_BGP_PATH_CONTENT)


59 60 61 62 63 64 65 66 67 68 69 70 71
def jun_parse_show_bgp_sum(lines):
    peers=[]

    table_started=False
    for l in str.splitlines(lines):
        if(table_started):
            m=table_line_regex.match(l)
            if(m):
                peers.append(m.group(1))

        if(table_header_regex.match(l)):
            table_started=True

72
#    ulgmodel.debug("DEBUG jun_parse_show_bgp_sum: peers="+str(peers))
73 74
    return peers

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
def juniper_parse_sh_route(text,prependas):
    def split_ases(ases):
        return str.split(ases)

    DEFAULT_PARAMS = {'recuse':False, 'reconly':False, 'aggr':None}

    res = []

    bgp_start = False
    params = dict(DEFAULT_PARAMS)
    for l in str.splitlines(text):
        ulgmodel.debug("JUNIPER PARSE SHOW ROUTE: l="+str(l))
        if(juniper_bgp_path_active_regex.match(l)):
            ulgmodel.debug("JUNIPER PARSE SHOW ROUTE: ACTIVE PATH")
            bgp_start = True
            params['recuse']=True
            continue

        if(juniper_bgp_path_regex.match(l)):
            ulgmodel.debug("JUNIPER PARSE SHOW ROUTE: NON-ACTIVE PATH")
            bgp_start = True
            continue

        m=juniper_bgp_path_content.match(l)
        if(m and bgp_start):
            ulgmodel.debug("JUNIPER PARSE SHOW ROUTE: path="+str(m.group(1)))
            ases = [ulgmodel.annotateAS("AS"+str(asn)) for asn in [prependas] + split_ases(m.group(1))]
            res.append((ases,params))
            params = dict(DEFAULT_PARAMS)
            bgp_start = False
            continue

    ulgmodel.debug("JUNIPER PARSE SHOW ROUTE: res="+str(res))
    return res

def juniper_reduce_paths(paths):
    def assign_value(path):
        if(path[1]['recuse']):
            return 1
        elif(path[1]['reconly']):
            return 100
        else:
            return 10

    return sorted(paths,key=assign_value)

121 122 123
class JuniperShowBgpNeigh(ulgmodel.TextCommand):
    COMMAND_TEXT='show bgp neighbor %s'

124 125
    def __init__(self,router,name=None):
        ulgmodel.TextCommand.__init__(self,self.COMMAND_TEXT,param_specs=[router.getBGPPeerSelect()],name=name)
126 127 128 129

class JuniperShowRouteBgpAdv(ulgmodel.TextCommand):
    COMMAND_TEXT='show route advertising-protocol bgp %s'

130 131
    def __init__(self,router,name=None):
        ulgmodel.TextCommand.__init__(self,self.COMMAND_TEXT,param_specs=[router.getBGPPeerSelect()],name=name)
132 133 134 135

class JuniperShowRouteBgpRecv(ulgmodel.TextCommand):
    COMMAND_TEXT='show route receive-protocol bgp %s'

136 137
    def __init__(self,router,name=None):
        ulgmodel.TextCommand.__init__(self,self.COMMAND_TEXT,param_specs=[router.getBGPPeerSelect()],name=name)
138

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
class JuniperShowRoute(ulgmodel.TextCommand):
    COMMAND_TEXT = 'show route %s'

    def __init__(self,name=None):
        ulgmodel.TextCommand.__init__(self,self.COMMAND_TEXT,param_specs=[
                ulgmodel.TextParameter(pattern=IPV46_SUBNET_REGEXP,name=defaults.STRING_IPSUBNET),
                ],
                                      name=name)


class JuniperGraphShowRoute(ulgmodel.TextCommand):
    COMMAND_TEXT = 'show route %s'

    def __init__(self,name=JUNIPER_GRAPH_SH_ROUTE):
        ulgmodel.TextCommand.__init__(self,self.COMMAND_TEXT,param_specs=[
                ulgmodel.TextParameter(pattern=IPV46_SUBNET_REGEXP,name=defaults.STRING_IPSUBNET),
                ],
                                      name=name)

    def finishHook(self,session):
        session.setData(juniper_parse_sh_route(session.getResult(),str(session.getRouter().getASN())))

    def decorateResult(self,session,decorator_helper=None):
        if(session.isFinished()):
            if(session.getData() != None) and (session.getData() != []):
164
                return (decorator_helper.img(decorator_helper.getSpecialContentURL(session.getSessionId()),defaults.STRING_BGP_GRAPH),1)
165
            else:
166
                return (decorator_helper.pre(defaults.STRING_BGP_GRAPH_ERROR), 1)
167 168 169 170 171 172 173 174 175 176 177 178 179 180
        else:
            return ('',0)

    def getSpecialContent(self,session,**params):
        paths = session.getData()
        print "Content-type: image/png\n"
        ulggraph.bgp_graph_gen(juniper_reduce_paths(paths),start=session.getRouter().getName(),
			       end=session.getParameters()[0])

    def showRange(self):
        return False



181

182 183
# ABSTRACT
class JuniperRouter(ulgmodel.RemoteRouter):
184
    RESCAN_PEERS_COMMAND = 'show bgp summary'
185 186 187 188 189 190

    PS_KEY_BGP = '-bgppeers'

    def __init__(self,host,user,password='',port=22,commands=None,asn='My ASN',name=None):
        ulgmodel.RemoteRouter.__init__(self)

191
        self.bgp_peer_select = None
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
        self.setHost(host)
        self.setUser(user)
        self.setPassword(password)
        self.setPort(port)
        if(name):
            self.setName(name)
        else:
            self.setName(host)
        self.setASN(asn)

        if(defaults.rescan_on_display):
            self.rescanHook()
        else:
            self.loadPersistentInfo()

        # command autoconfiguration might run only after other parameters are set
        if(commands):
            self.setCommands(commands)
        else:
            self.setCommands(self._getDefaultCommands())


    def getForkNeeded(self):
        return True

    def _getDefaultCommands(self):
        return [ulgmodel.TextCommand('show version'),
219
                ulgmodel.TextCommand('show bgp summary'),
220
                JuniperShowRoute(),
221 222 223
                JuniperShowBgpNeigh(self),
                JuniperShowRouteBgpRecv(self),
                JuniperShowRouteBgpAdv(self),
224
                JuniperGraphShowRoute(),
225 226 227 228 229
                ]

    def rescanPeers(self):
        res = self.runRawSyncCommand(self.RESCAN_PEERS_COMMAND)

230
        peers = jun_parse_show_bgp_sum(res)
231 232 233 234 235 236

        self.bgp_peers = peers

    def getBGPPeers(self):
        return self.bgp_peers

237 238 239 240 241 242 243 244 245 246 247 248
    def initBGPPeerSelect(self,peers):
        rid = hashlib.md5(self.getName()).hexdigest()
        self.bgp_peer_select = ulgmodel.CommonSelectionParameter(rid+"bgp",[tuple((p,p,)) for p in peers],
                                                                  name=defaults.STRING_PEERID)

    def getBGPPeerSelect(self):
        if(not self.bgp_peer_select):
            self.initBGPPeerSelect(self.getBGPPeers())

        return self.bgp_peer_select


249 250 251
    def savePersistentInfo(self):
        key_bgp = self.getHost() + self.getName() + self.PS_KEY_BGP
        
252
        ps = ulgmodel.loadPersistentStorage()
253 254 255 256 257 258
        ps.set(key_bgp,self.getBGPPeers())
        ps.save()
               
    def loadPersistentInfo(self):
        key_bgp = self.getHost() + self.getName() + self.PS_KEY_BGP

259
        ps = ulgmodel.loadPersistentStorage()
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
        self.bgp_peers = ps.get(key_bgp)

        if(not self.getBGPPeers()):
            self.rescanHook()

    def rescanHook(self):
        self.rescanPeers()
        self.savePersistentInfo()


class JuniperRouterRemoteTelnet(JuniperRouter):
    def __init__(self,host,user,password='',port=23,commands=None,asn='My ASN',name=None):
        JuniperRouter.__init__(self,host=host,user=user,password=password,port=port,commands=commands,asn=asn,name=name)


    def runRawCommand(self,command,outfile):
        skiplines=1
        c = defaults.bin_telnet+' '+self.getHost()+' '+str(self.getPort())
        s=pexpect.spawn(c,timeout=defaults.timeout)

280
#        s.logfile = open('/tmp/ulgjuni.log', 'w')
281 282 283 284 285 286 287 288 289 290 291 292 293

        p=0
        while True:
            i=s.expect([STRING_EXPECT_LOGIN,STRING_EXPECT_PASSWORD,
                        STRING_EXPECT_SHELL_PROMPT_REGEXP,pexpect.EOF,pexpect.TIMEOUT])
            if(i==0):
                s.sendline(self.user)
            elif(i==1):
                if(p>1):
                    raise Exception("pexpect session failed: Password not accepted.")

                s.sendline(self.password)
                p+=1
294
            elif(i==2): # shell
295 296
                break
            elif(i==3): # EOF -> process output
297
                raise Exception("pexpect session failed to authenticate, remote server disconnected.")
298 299 300 301 302
            elif(i==4):
                raise Exception("pexpect session timed out. last output: "+s.before)
            else:
                raise Exception("pexpect session failed: Unknown error. last output: "+s.before)

303
        s.sendline(STRING_CLI_INFINITE_COMMAND)
304 305 306 307 308 309 310 311 312 313 314
        while True:
            i=s.expect([STRING_EXPECT_SHELL_PROMPT_REGEXP,'\n',pexpect.EOF,pexpect.TIMEOUT])
            if(i==0): # shell prompt -> proceed
                break
            if(i==1):
                pass # anything else -> ignore
            elif(i==2 or i==3):
                raise Exception("pexpect session timed out. last output: "+s.before)
            else:
                raise Exception("pexpect session failed: Unknown error. last output: "+s.before)

315 316 317 318 319
        s.sendline(command)
        capture=True
        line=0
        while True:
            i=s.expect([STRING_EXPECT_SHELL_PROMPT_REGEXP,'\n',pexpect.EOF,pexpect.TIMEOUT])
320
            if(i==0): # shell prompt -> logout
321 322 323 324 325
                capture=False
                s.sendline(STRING_LOGOUT_COMMAND)
            elif(i==1):
                if(capture and line >= skiplines):
                    outfile.write(s.before + "\n")
326
                line+=1
327 328 329 330 331 332 333
            elif(i==2): # EOF -> process output
                break
            elif(i==3):
                raise Exception("pexpect session timed out. last output: "+s.before)
            else:
                raise Exception("pexpect session failed: Unknown error. last output: "+s.before)

334
        s.expect([pexpect.EOF])
335

336 337 338 339 340 341 342 343 344 345

class JuniperRouterRemoteSSH(JuniperRouter):
    def __init__(self,host,user,password='',port=22,commands=None,asn='My ASN',name=None):
        JuniperRouter.__init__(self,host=host,user=user,password=password,commands=commands,asn=asn,name=name)


    def runRawCommand(self,command,outfile):
        c = defaults.bin_ssh+' -p'+str(self.getPort())+' '+str(self.getUser())+'@'+self.getHost()
        s=pexpect.spawn(c,timeout=defaults.timeout)

346
#        s.logfile = open('/tmp/ulgjuni.log', 'w')
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383

        # handle ssh
        y=0
        p=0
        while True:
            i=s.expect([STRING_EXPECT_SSH_NEWKEY,STRING_EXPECT_PASSWORD,pexpect.EOF,pexpect.TIMEOUT])
            if(i==0):
                if(y>1):
                    raise Exception("pexpect session failed: Can not save SSH key.")

                s.sendline('yes')
                y+=1
            elif(i==1):
                if(p>1):
                    raise Exception("pexpect session failed: Password not accepted.")

                s.sendline(self.password)
                p+=1
            elif(i==2): # EOF -> process output
                break
            elif(i==3):
                raise Exception("pexpect session timed out. last output: "+s.before)
            else:
                raise Exception("pexpect session failed: Unknown error. last output: "+s.before)


        def stripFirstLines(string):
            lines = str.splitlines(string)
            r = re.sub(BIRD_CONSOLE_PROMPT_REGEXP,'',lines[2]) + '\n'
            for l in lines[3:]:
                r = r + l + '\n'
            return r

        out = s.before
#        ulgmodel.debug("BIRD OUT: "+out)
        outfile.write(stripFirstLines(out))