ulgbird.py 12.1 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 31
IPV46_SUBNET_REGEXP = '^[0-9a-fA-F:\.]+(/[0-9]{1,2}){0,1}$'

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

35 36
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*$'
37

38 39
BIRD_RT_LINE_REGEXP = '^([^\s]+)\s+via\s+([^\s]+)\s+on\s+([^\s]+)\s+(\[[^\]]+\])\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)'

40 41
bird_sock_header_regexp = re.compile(BIRD_SOCK_HEADER_REGEXP)
bird_sock_reply_end_regexp = re.compile(BIRD_SOCK_REPLY_END_REGEXP)
42
bird_rt_line_regexp = re.compile(BIRD_RT_LINE_REGEXP)
43 44


45
def parseBirdShowProtocols(text,resrange=None):
46 47 48 49 50 51 52
    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))
53

54 55
            return res
        else:
56
            ulgmodel.log("WARN: bird.parseShowProtocolsLine failed to match line: "+line)
57
            return None
58

59 60 61

    header = []
    table = []
62

63 64 65 66 67 68 69 70 71 72 73 74
    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:
75
                ulgmodel.log("ulgbird.parseBirdShowProtocols skipping unparsable line"+l)
76

77 78 79 80
    if(resrange):
        return (header,table[resrange:resrange+defaults.range_step],len(table))
    else:
        return (header,table,len(table))
81 82 83 84 85 86

# classes

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

87
    def __init__(self,name=None,show_proto_all_command=None,proto_filter=None):
88
        ulgmodel.TextCommand.__init__(self,self.COMMAND_TEXT,param_specs=[],name=name)
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
        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
106

107
    def _decorateTableLine(self,table_line,router,decorator_helper):
108 109 110 111 112 113 114 115 116
        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])
117 118 119 120 121 122 123 124 125 126
        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
127

128

129
    def decorateResult(self,result,router=None,decorator_helper=None,resrange=0):
130 131 132
        if((not router) or (not decorator_helper)):
            return "<pre>\n%s\n</pre>" % result
        else:
133
            pr = parseBirdShowProtocols(result,resrange)
134
            table_header = pr[0]
135 136
            table = []

137
            for tl in pr[1][resrange:resrange+defaults.range_step]:
138 139 140
                # 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
141
                table.append(self._decorateTableLine(tl,router,decorator_helper))
142

143
            return (ulgmodel.TableDecorator(table,table_header).decorate(),pr[2])
144 145


146 147
class BirdBGPPeerSelectCommand(ulgmodel.TextCommand):
    """ Abstract class for all BIRD BGP peer-specific commands """
148 149 150 151 152 153

    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)

154 155 156 157 158 159 160 161 162
class BirdShowProtocolsAllCommand(BirdBGPPeerSelectCommand):
    COMMAND_TEXT = 'show protocols all %s'

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

class BirdShowRouteProtocolCommand(BirdBGPPeerSelectCommand):
    COMMAND_TEXT = 'show route protocol %s'

163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
    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],),
                        (ml[6],),
                        ])
        return result


189
    def decorateResult(self,result,router=None,decorator_helper=None,resrange=0):
190 191 192 193 194 195 196 197 198 199 200 201
        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',]

202 203 204 205
        lines = str.splitlines(result)
        result_len = len(lines)
        lines = lines[resrange:resrange+defaults.range_step]
        table = self._genTable(lines,decorator_helper,router)
206

207
        return (ulgmodel.TableDecorator(table,table_header).decorate(),result_len)
208 209


210 211
class BirdShowRouteAllCommand(ulgmodel.TextCommand):
    COMMAND_TEXT = 'show route all %s'
212

213 214 215 216 217
    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)
218

219

220
class BirdRouterLocal(ulgmodel.LocalRouter):
221 222
    RESCAN_PEERS_COMMAND = 'show protocols'
    DEFAULT_PROTOCOL_FLTR = '^(Kernel|Device|Static|BGP).*$'
223

224
    def __init__(self,sock=defaults.default_bird_sock,commands=None,proto_fltr=None):
225
        super(self.__class__,self).__init__()
226 227
        self.sock = sock
        self.setName('localhost')
228 229 230 231
        if(proto_fltr):
            self.proto_fltr = proto_fltr
        else:
            self.proto_fltr = self.DEFAULT_PROTOCOL_FLTR
232

233
        # command autoconfiguration might run only after other parameters are set
234 235 236
        if(commands):
            self.setCommands(commands)
        else:
237 238 239
            self.setCommands(self._getDefaultCommands())

    def _getDefaultCommands(self):
240 241 242 243 244 245 246
        sh_proto_all = BirdShowProtocolsAllCommand(self.getBGPPeers())
        sh_proto_route = BirdShowRouteProtocolCommand(self.getBGPPeers())
        sh_proto_export = BirdShowRouteExportCommand(self.getBGPPeers())
        return [BirdShowProtocolsCommand(show_proto_all_command=sh_proto_all, proto_filter = self.proto_fltr),
                sh_proto_all,
                sh_proto_route,
                sh_proto_export,
247 248 249
                BirdShowRouteAllCommand(),
                ulgmodel.TextCommand('show status'),
                ulgmodel.TextCommand('show memory')
250
                ]
251

252
    def runRawCommand(self,command,outfile):
253 254 255 256 257
        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))
258

259 260 261 262
            em = bird_sock_reply_end_regexp.match(line)
            if(em):
                # most likely the last line of the reply
                return (int(em.group(1)),None)
263

264 265 266
            if(line[0] == '+'):
                # ignore async reply
                return (None,None)
267

268 269 270
            if(line[0] == ' '):
                # return reply line as it is (remove padding)
                return (None,line[1:])
271

272
            raise Exception("Can not parse BIRD output line: "+line)
273

274 275
        def isBirdSockReplyEnd(code):
            if(code==None):
276 277
                return False

278 279 280 281 282
            if(code == 0):
                # end of reply
                return True
            elif(code == 13):
                # show status last line
283
                return True
284 285 286
            elif(code >= 9000):
                # probably error
                return True
287 288 289
            else:
                return False

290 291 292 293 294
#        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)
295

296 297
        # cretate FD for the socket
        sf=s.makefile()
298

299 300
        # wait for initial header
        l = sf.readline()
301

302 303 304
        # send the command string
        sf.write(command+"\n")
        sf.flush()
305

306 307 308
        # read and capture lines until the output delimiter string is hit
        while(True):
            l = sf.readline()
309

310
            ulgmodel.debug("Raw line read: " + l)
311

312 313 314 315 316
            # 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]))
317

318 319
                if(lp[1]):
                    ulgmodel.debug("Last read line after normalize: " + lp[1])
320
                    outfile.write(lp[1].rstrip()+"\n")
321 322 323 324
                break
            else:
                if(lp[1]):
                    ulgmodel.debug("Read line after normalize: " + lp[1])
325
                    outfile.write(lp[1].rstrip()+"\n")
326
                else:
327
                    ulgmodel.debug("Read line was empty after normalize.")
328

329 330
        # close the socket and return captured result
        s.close()
331

332 333 334
#        except socket.timeout as e:
#            # catch only timeout exception, while letting other exceptions pass
#            outfile.result(defaults.STRING_SOCKET_TIMEOUT)
335 336 337

    def getForkNeeded(self):
        return False
338 339


340
    def rescanPeers(self):
341
        res = self.runRawSyncCommand(self.RESCAN_PEERS_COMMAND)
342
        psp = parseBirdShowProtocols(res)
343 344

        peers = []
345
        for pspl in psp[1]:
346
            if(re.match(self.proto_fltr,pspl[1])):
347
                peers.append(pspl[0])
348 349 350 351

        return peers


352
    def getBGPPeers(self):
353
        return self.rescanPeers()
354 355 356 357

    def getBGPIPv6Peers(self):
        return self.bgp_ipv6_peers