ulgbird.py 14.4 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 25 26 27 28 29
#!/usr/bin/env python
#
# ULG - Universal Looking Glass
# by Tomas Hlavacek (tomas.hlavacek@nic.cz)
# last udate: June 21 2012
#
# 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 defaults

import ulgmodel

30
IPV46_SUBNET_REGEXP = '^[0-9a-fA-F:\.]+(/[0-9]{1,2}){0,1}$'
31 32
RTNAME_REGEXP = '^[a-zA-Z0-9]+$'
STRING_SYMBOL_ROUTING_TABLE = 'routing table'
33

34
BIRD_SOCK_HEADER_REGEXP='^([0-9]+)[-\s](.+)$'
35
BIRD_SOCK_REPLY_END_REGEXP='^([0-9]+)\s*(\s.*)?$'
36

37 38
BIRD_SHOW_PROTO_LINE_REGEXP='^\s*([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)(\s+([^\s].+)){0,1}\s*$'
BIRD_SHOW_PROTO_HEADER_REGEXP='^\s*(name)\s+(proto)\s+(table)\s+(state)\s+(since)\s+(info)\s*$'
39

40
BIRD_RT_LINE_REGEXP = '^([^\s]+)\s+via\s+([^\s]+)\s+on\s+([^\s]+)\s+(\[[^\]]+\])\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)'
41
BIRD_ASFIELD_REGEXP = '^\s*\[AS([0-9]+)(i|\?)\]\s*$'
42

43 44
BIRD_SHOW_SYMBOLS_LINE_REGEXP = '^([^\s]+)\s+([^\s].*)$'

45 46
bird_sock_header_regexp = re.compile(BIRD_SOCK_HEADER_REGEXP)
bird_sock_reply_end_regexp = re.compile(BIRD_SOCK_REPLY_END_REGEXP)
47
bird_rt_line_regexp = re.compile(BIRD_RT_LINE_REGEXP)
48
bird_asfield_regexp = re.compile(BIRD_ASFIELD_REGEXP)
49
bird_show_symbols_line_regexp = re.compile(BIRD_SHOW_SYMBOLS_LINE_REGEXP)
50

51
def parseBirdShowProtocols(text,resrange=None):
52 53 54 55 56 57 58
    def parseShowProtocolsLine(line):
        sh_proto_line_regexp = re.compile(BIRD_SHOW_PROTO_LINE_REGEXP)
        m = sh_proto_line_regexp.match(line)
        if(m):
            res = list(m.groups()[0:5])
            if(m.group(6)):
                res.append(m.group(6))
59

60 61
            return res
        else:
62
            ulgmodel.log("WARN: bird.parseShowProtocolsLine failed to match line: "+line)
63
            return None
64

65 66 67

    header = []
    table = []
68

69 70 71 72 73 74 75 76 77 78 79 80
    for l in str.splitlines(text):
        if(re.match('^\s*$',l)):
            continue
        
        hm = re.match(BIRD_SHOW_PROTO_HEADER_REGEXP,l)
        if(hm):
            header = hm.groups()
        else:
            pl = parseShowProtocolsLine(l)
            if(pl):
                table.append(pl)
            else:
81
                ulgmodel.log("ulgbird.parseBirdShowProtocols skipping unparsable line"+l)
82

83 84 85 86
    if(resrange):
        return (header,table[resrange:resrange+defaults.range_step],len(table))
    else:
        return (header,table,len(table))
87 88 89 90 91 92

# classes

class BirdShowProtocolsCommand(ulgmodel.TextCommand):
    COMMAND_TEXT = 'show protocols'

93
    def __init__(self,name=None,show_proto_all_command=None,proto_filter=None):
94
        ulgmodel.TextCommand.__init__(self,self.COMMAND_TEXT,param_specs=[],name=name)
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
        self.show_proto_all_command = show_proto_all_command
        self.fltr = proto_filter

    def _getPeerURL(self,decorator_helper,router,peer_id):
        if decorator_helper and self.show_proto_all_command:
            return decorator_helper.getRuncommandURL({'routerid':str(decorator_helper.getRouterID(router)),
                                                      'commandid':str(decorator_helper.getCommandID(router,self.show_proto_all_command)),
                                                      'param0':peer_id})
        else:
            return None

    def _getPeerTableCell(self,decorator_helper,router,peer_id):
        url = self._getPeerURL(decorator_helper,router,peer_id)
        if(url):
            return decorator_helper.ahref(url,peer_id)
        else:
            return peer_id
112

113
    def _decorateTableLine(self,table_line,router,decorator_helper):
114 115 116 117 118 119 120 121 122
        def _getTableLineColor(state):
            if(state == 'up'):
                return ulgmodel.TableDecorator.GREEN
            elif(state == 'start'):
                return ulgmodel.TableDecorator.YELLOW
            else:
                return ulgmodel.TableDecorator.RED

        color = _getTableLineColor(table_line[3])
123 124 125 126 127 128 129 130 131 132
        tl = [(self._getPeerTableCell(decorator_helper,router,table_line[0]),color),
              (table_line[1],color),
              (table_line[2],color),
              (table_line[3],color),
              (table_line[4],color),
              ]
        if(len(table_line)>5):
            tl.append((table_line[5],color))

        return tl
133

134

135
    def decorateResult(self,result,router=None,decorator_helper=None,resrange=0):
136 137 138
        if((not router) or (not decorator_helper)):
            return "<pre>\n%s\n</pre>" % result
        else:
139
            pr = parseBirdShowProtocols(result,resrange)
140
            table_header = pr[0]
141 142
            table = []

143
            for tl in pr[1][resrange:resrange+defaults.range_step]:
144 145 146
                # skip when there is a filter and it does not match the protocol type
                if(self.fltr) and (not re.match(self.fltr,tl[1])):
                    continue
147
                table.append(self._decorateTableLine(tl,router,decorator_helper))
148

149
            return (ulgmodel.TableDecorator(table,table_header).decorate(),pr[2])
150 151


152 153
class BirdBGPPeerSelectCommand(ulgmodel.TextCommand):
    """ Abstract class for all BIRD BGP peer-specific commands """
154 155 156 157 158 159

    def __init__(self,peers,name=None):
        peer_param = ulgmodel.SelectionParameter([tuple((p,p,)) for p in peers],
                                                 name=defaults.STRING_PEERID)
        ulgmodel.TextCommand.__init__(self,self.COMMAND_TEXT,param_specs=[peer_param],name=name)

160 161 162 163 164 165
class BirdShowProtocolsAllCommand(BirdBGPPeerSelectCommand):
    COMMAND_TEXT = 'show protocols all %s'

class BirdShowRouteExportCommand(BirdBGPPeerSelectCommand):
    COMMAND_TEXT = 'show route export %s'

166
class BirdShowRouteCommand(ulgmodel.TextCommand):
167 168 169 170 171
    COMMAND_TEXT = 'show route table %s for %s'

    def __init__(self,tables,name=None):
        table_param = ulgmodel.SelectionParameter([tuple((t,t,)) for t in tables],
                                                  name=defaults.STRING_RTABLE)
172 173

        ulgmodel.TextCommand.__init__(self,self.COMMAND_TEXT,param_specs=[
174 175
                table_param,
                ulgmodel.TextParameter(pattern=IPV46_SUBNET_REGEXP,name=defaults.STRING_IPSUBNET),
176 177
                ],name=name)

178

179 180 181 182
    def _decorateOriginAS(self,asfield,decorator_helper):
        # expected input is "[AS28171i]"
        m = bird_asfield_regexp.match(asfield)
        if(m):
183
            return '['+decorator_helper.ahref(defaults.getASNURL(m.group(1)),'AS'+m.group(1))+m.group(2)+']'
184 185 186
        else:
            return asfield

187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
    def _genTable(self,table_lines,decorator_helper,router):
        def matchBIRDBGPRTLine(line):
            m = bird_rt_line_regexp.match(line)
            if(m):
                return m.groups()
            else:
                ulgmodel.debug("BirdShowRouteProtocolCommand: Can not parse line: "+line)
                return None

        result = []
        for tl in table_lines:
            ml=matchBIRDBGPRTLine(tl)
            if(ml):
                # generate table content
                result.append([
                        (decorator_helper.ahref(defaults.getIPPrefixURL(ml[0]),ml[0]),),
                        (ml[1],),
                        (ml[2],),
                        (ml[3],),
                        (ml[4],),
                        (ml[5],),
208
                        (self._decorateOriginAS(ml[6],decorator_helper),),
209 210 211 212
                        ])
        return result


213
    def decorateResult(self,result,router=None,decorator_helper=None,resrange=0):
214 215 216 217 218 219 220 221 222 223 224 225
        if((not router) or (not decorator_helper)):
            return "<pre>\n%s\n</pre>" % result

        table=[]
        table_header=['Prefix',
                      'Next-hop',
                      'Interface',
                      'Since',
                      'Status',
                      'Metric',
                      'Info',]

226 227 228 229
        lines = str.splitlines(result)
        result_len = len(lines)
        lines = lines[resrange:resrange+defaults.range_step]
        table = self._genTable(lines,decorator_helper,router)
230

231
        return (ulgmodel.TableDecorator(table,table_header).decorate(),result_len)
232

233 234 235 236 237 238 239 240 241 242
class BirdShowRouteProtocolCommand(BirdShowRouteCommand):
    COMMAND_TEXT = 'show route table %s protocol %s'

    def __init__(self,peers,tables,name=None):
        peer_param = ulgmodel.SelectionParameter([tuple((p,p,)) for p in peers],
                                                 name=defaults.STRING_PEERID)
        table_param = ulgmodel.SelectionParameter([tuple((t,t,)) for t in tables],
                                                  name=defaults.STRING_RTABLE)

        ulgmodel.TextCommand.__init__(self,self.COMMAND_TEXT,param_specs=[table_param, peer_param],name=name)
243

244

245
class BirdShowRouteAllCommand(ulgmodel.TextCommand):
246 247 248 249 250
    COMMAND_TEXT = 'show route table %s all for %s'

    def __init__(self,tables,name=None):
        table_param = ulgmodel.SelectionParameter([tuple((t,t,)) for t in tables],
                                                  name=defaults.STRING_RTABLE)
251

252
        ulgmodel.TextCommand.__init__(self,self.COMMAND_TEXT,param_specs=[
253 254
                table_param,
                ulgmodel.TextParameter(pattern=IPV46_SUBNET_REGEXP,name=defaults.STRING_IPSUBNET),
255 256
                ],
                                      name=name)
257

258

259
class BirdRouterLocal(ulgmodel.LocalRouter):
260
    RESCAN_PEERS_COMMAND = 'show protocols'
261
    RESCAN_TABLES_COMMAND = 'show symbols'
262
    DEFAULT_PROTOCOL_FLTR = '^(Kernel|Device|Static|BGP).*$'
263

264
    def __init__(self,sock=defaults.default_bird_sock,commands=None,proto_fltr=None):
265
        super(self.__class__,self).__init__()
266 267
        self.sock = sock
        self.setName('localhost')
268 269 270 271
        if(proto_fltr):
            self.proto_fltr = proto_fltr
        else:
            self.proto_fltr = self.DEFAULT_PROTOCOL_FLTR
272

273
        # command autoconfiguration might run only after other parameters are set
274 275 276
        if(commands):
            self.setCommands(commands)
        else:
277 278 279
            self.setCommands(self._getDefaultCommands())

    def _getDefaultCommands(self):
280
        sh_proto_all = BirdShowProtocolsAllCommand(self.getBGPPeers())
281
        sh_proto_route = BirdShowRouteProtocolCommand(self.getBGPPeers(),self.getRoutingTables())
282 283
        sh_proto_export = BirdShowRouteExportCommand(self.getBGPPeers())
        return [BirdShowProtocolsCommand(show_proto_all_command=sh_proto_all, proto_filter = self.proto_fltr),
284
                BirdShowRouteCommand(self.getRoutingTables()),
285 286 287
                sh_proto_all,
                sh_proto_route,
                sh_proto_export,
288
                BirdShowRouteAllCommand(self.getRoutingTables()),
289 290
                ulgmodel.TextCommand('show status'),
                ulgmodel.TextCommand('show memory')
291
                ]
292

293
    def runRawCommand(self,command,outfile):
294 295 296 297 298
        def parseBirdSockLine(line):
            hm = bird_sock_header_regexp.match(line)
            if(hm):
                # first line of the reply
                return (int(hm.group(1)),hm.group(2))
299

300 301 302 303
            em = bird_sock_reply_end_regexp.match(line)
            if(em):
                # most likely the last line of the reply
                return (int(em.group(1)),None)
304

305 306 307
            if(line[0] == '+'):
                # ignore async reply
                return (None,None)
308

309 310 311
            if(line[0] == ' '):
                # return reply line as it is (remove padding)
                return (None,line[1:])
312

313
            raise Exception("Can not parse BIRD output line: "+line)
314

315 316
        def isBirdSockReplyEnd(code):
            if(code==None):
317 318
                return False

319 320 321 322 323
            if(code == 0):
                # end of reply
                return True
            elif(code == 13):
                # show status last line
324
                return True
325 326 327
            elif(code >= 9000):
                # probably error
                return True
328 329 330
            else:
                return False

331 332 333 334 335
#        try:
        # open socket to BIRD
        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        s.settimeout(defaults.default_bird_sock_timeout)
        s.connect(self.sock)
336

337 338
        # cretate FD for the socket
        sf=s.makefile()
339

340 341
        # wait for initial header
        l = sf.readline()
342

343 344 345
        # send the command string
        sf.write(command+"\n")
        sf.flush()
346

347 348 349
        # read and capture lines until the output delimiter string is hit
        while(True):
            l = sf.readline()
350

351
            ulgmodel.debug("Raw line read: " + l)
352

353 354 355 356 357
            # process line according to rules take out from the C code
            lp = parseBirdSockLine(l)
            if(isBirdSockReplyEnd(lp[0])):
                # End of reply (0000 or similar code)
                ulgmodel.debug("End of reply. Code="+str(lp[0]))
358

359 360
                if(lp[1]):
                    ulgmodel.debug("Last read line after normalize: " + lp[1])
361
                    outfile.write(lp[1].rstrip()+"\n")
362 363 364 365
                break
            else:
                if(lp[1]):
                    ulgmodel.debug("Read line after normalize: " + lp[1])
366
                    outfile.write(lp[1].rstrip()+"\n")
367
                else:
368
                    ulgmodel.debug("Read line was empty after normalize.")
369

370 371
        # close the socket and return captured result
        s.close()
372

373 374 375
#        except socket.timeout as e:
#            # catch only timeout exception, while letting other exceptions pass
#            outfile.result(defaults.STRING_SOCKET_TIMEOUT)
376 377 378

    def getForkNeeded(self):
        return False
379 380


381
    def rescanPeers(self):
382
        res = self.runRawSyncCommand(self.RESCAN_PEERS_COMMAND)
383
        psp = parseBirdShowProtocols(res)
384 385

        peers = []
386
        for pspl in psp[1]:
387
            if(re.match(self.proto_fltr,pspl[1])):
388
                peers.append(pspl[0])
389 390 391

        return peers

392 393 394 395 396 397 398 399 400
    def rescanRoutingTables(self):
        res = self.runRawSyncCommand(self.RESCAN_TABLES_COMMAND)

        tables = []
        for l in str.splitlines(res):
            m = bird_show_symbols_line_regexp.match(l)
            if(m and m.group(2).lstrip().rstrip() == STRING_SYMBOL_ROUTING_TABLE):
                tables.append(m.group(1))
        return tables
401

402
    def getBGPPeers(self):
403
        return self.rescanPeers()
404

405 406
    def getRoutingTables(self):
        return self.rescanRoutingTables()