1import logging
2from time import strftime
3
4from ryu.services.protocols.bgp.operator.command import Command
5from ryu.services.protocols.bgp.operator.command import CommandsResponse
6from ryu.services.protocols.bgp.operator.command import STATUS_ERROR
7from ryu.services.protocols.bgp.operator.command import STATUS_OK
8from ryu.services.protocols.bgp.operator.commands.responses import \
9    WrongParamResp
10from ryu.services.protocols.bgp.operator.views.bgp import CoreServiceDetailView
11from ryu.lib.packet.bgp import RF_IPv4_UC
12from ryu.lib.packet.bgp import RF_IPv6_UC
13from ryu.lib.packet.bgp import RF_IPv4_VPN
14from ryu.lib.packet.bgp import RF_IPv6_VPN
15from ryu.lib.packet.bgp import BGP_ATTR_ORIGIN_IGP
16from ryu.lib.packet.bgp import BGP_ATTR_ORIGIN_EGP
17from ryu.lib.packet.bgp import BGP_ATTR_ORIGIN_INCOMPLETE
18
19LOG = logging.getLogger('bgpspeaker.operator.commands.show.neighbor')
20
21
22class NeighborSummary(Command):
23    help_msg = 'show summarized neighbor information'
24    command = 'summary'
25
26    def action(self, params):
27        requested_peers = []
28        if len(params) > 0:
29            requested_peers = [str(p) for p in params]
30
31        core_service = self.api.get_core_service()
32        core_service_view = CoreServiceDetailView(core_service)
33        peers_view = core_service_view.rel('peer_manager').rel('peers_summary')
34
35        def filter_requested(peer_id, peer_obj):
36            return not requested_peers or peer_id in requested_peers
37
38        peers_view.apply_filter(filter_requested)
39        ret = peers_view.encode()
40        return CommandsResponse(STATUS_OK, ret)
41
42
43class SentRoutes(Command):
44    help_msg = 'paths sent and not withdrawn to given peer'
45    command = 'sent-routes'
46    param_help_msg = '<ip_addr> <addr_family>{vpnv4, vpnv6, ipv4, ipv6, all}'
47    fmtstr = ' {0:<2s} {1:<19s} {2:<32s} {3:<8s} {4:<20s} '\
48        '{5:<6s} {6:<6s} {7:<}\n'
49
50    def action(self, params):
51        if len(params) != 2:
52            return WrongParamResp()
53        ip_addr, addr_family = params
54
55        if addr_family == 'ipv4':
56            rf = RF_IPv4_UC
57        elif addr_family == 'ipv6':
58            rf = RF_IPv6_UC
59        elif addr_family == 'vpnv4':
60            rf = RF_IPv4_VPN
61        elif addr_family == 'vpnv6':
62            rf = RF_IPv6_VPN
63        elif addr_family == 'all':
64            rf = None
65        else:
66            return WrongParamResp('wrong addr_family name')
67
68        ret = self._retrieve_paths(addr_family, rf, ip_addr).encode()
69        return CommandsResponse(STATUS_OK, ret)
70
71    def _retrieve_paths(self, addr_family, route_family, ip_addr):
72        peer_view = self._retrieve_peer_view(ip_addr)
73        adj_rib_out = peer_view.c_rel('adj_rib_out')
74        adj_rib_out.apply_filter(lambda k, v: addr_family == 'all' or
75                                 v.path.route_family == route_family)
76        return adj_rib_out
77
78    def _retrieve_peer_view(self, ip_addr):
79        core_service = self.api.get_core_service()
80        core_sv = CoreServiceDetailView(core_service)
81        peers_view = core_sv.rel('peer_manager').rel('peers')
82        peers_view.apply_filter(lambda k, v: v.ip_address == ip_addr)
83        return peers_view
84
85    @classmethod
86    def cli_resp_formatter(cls, resp):
87        if resp.status == STATUS_ERROR:
88            return Command.cli_resp_formatter(resp)
89        return cls._format_header() + cls._format_value(resp.value)
90
91    @classmethod
92    def _format_header(cls):
93        ret = ''
94        ret += ('Status codes: x filtered\n')
95        ret += ('Origin codes: i - IGP, e - EGP, ? - incomplete\n')
96        ret += cls.fmtstr.format('', 'Timestamp', 'Network', 'Labels',
97                                 'Next Hop', 'Metric', 'LocPrf', 'Path')
98        return ret
99
100    @classmethod
101    def _format_value(cls, value):
102        ret = ''
103        for v in value:
104            path = v.get('path')
105            aspath = path.get('as_path')
106            origin = path.get('origin')
107
108            if origin == BGP_ATTR_ORIGIN_IGP:
109                origin = 'i'
110            elif origin == BGP_ATTR_ORIGIN_EGP:
111                origin = 'e'
112            elif origin == BGP_ATTR_ORIGIN_INCOMPLETE:
113                origin = '?'
114
115            if origin:
116                aspath = aspath + [origin]
117
118            next_hop = path.get('nexthop')
119            med = path.get('metric')
120            labels = path.get('labels')
121            localpref = path.get('local_pref')
122            prefix = path.get('nlri').get('prefix')
123
124            path_status = ''
125            if v.get('filtered'):
126                path_status = 'x'
127            time = 'N/A'
128            if v.get('timestamp'):
129                time = strftime("%Y/%m/%d %H:%M:%S", v.get('timestamp'))
130            ret += cls.fmtstr.format(path_status, time, prefix, labels,
131                                     str(next_hop), str(med), str(localpref),
132                                     ' '.join(map(str, aspath)))
133        return ret
134
135
136class ReceivedRoutes(SentRoutes):
137    help_msg = 'paths received and not withdrawn by given peer'
138    command = 'received-routes'
139
140    def _retrieve_paths(self, addr_family, route_family, ip_addr):
141        peer_view = self._retrieve_peer_view(ip_addr)
142        adj_rib_in = peer_view.c_rel('adj_rib_in')
143        adj_rib_in.apply_filter(lambda k, v: addr_family == 'all' or
144                                v.path.route_family == route_family)
145        return adj_rib_in
146
147
148class Neighbor(Command):
149    help_msg = 'show neighbor information'
150    command = 'neighbor'
151    subcommands = {
152        'summary': NeighborSummary,
153        'sent-routes': SentRoutes,
154        'received-routes': ReceivedRoutes
155    }
156
157    fmtstr = ' {0:<12s} {1:<12s} {2:<}\n'
158
159    def action(self, params):
160        core_service = self.api.get_core_service()
161        core_service_view = CoreServiceDetailView(core_service)
162        peers_view = core_service_view.rel('peer_manager').rel('peers')
163
164        ret = peers_view.encode()
165        return CommandsResponse(STATUS_OK,
166                                [{'ip_addr': k,
167                                  'as_num': str(v['remote_as']),
168                                  'bgp_state': v['stats']['bgp_state']}
169                                 for k, v in ret.items()])
170
171    @classmethod
172    def cli_resp_formatter(cls, resp):
173        if resp.status == STATUS_ERROR:
174            return Command.cli_resp_formatter(resp)
175        return cls._format_header() + cls._format_value(resp.value)
176
177    @classmethod
178    def _format_header(cls):
179        return cls.fmtstr.format('IP Address', 'AS Number', 'BGP State')
180
181    @classmethod
182    def _format_value(cls, value):
183        ret = ''
184        for v in value:
185            ret += cls.fmtstr.format(v['ip_addr'], v['as_num'], v['bgp_state'])
186        return ret
187