github.com/osrg/gobgp@v2.0.0+incompatible/test/lib/exabgp.py (about) 1 # Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 # implied. 13 # See the License for the specific language governing permissions and 14 # limitations under the License. 15 16 from __future__ import absolute_import 17 18 from fabric import colors 19 20 from lib.base import ( 21 BGPContainer, 22 CmdBuffer, 23 try_several_times, 24 wait_for_completion, 25 ) 26 27 28 class ExaBGPContainer(BGPContainer): 29 30 SHARED_VOLUME = '/shared_volume' 31 PID_FILE = '/var/run/exabgp.pid' 32 33 def __init__(self, name, asn, router_id, ctn_image_name='osrg/exabgp:4.0.5'): 34 super(ExaBGPContainer, self).__init__(name, asn, router_id, ctn_image_name) 35 self.shared_volumes.append((self.config_dir, self.SHARED_VOLUME)) 36 37 def _pre_start_exabgp(self): 38 # Create named pipes for "exabgpcli" 39 named_pipes = '/run/exabgp.in /run/exabgp.out' 40 self.local('mkfifo {0}'.format(named_pipes), capture=True) 41 self.local('chmod 777 {0}'.format(named_pipes), capture=True) 42 43 def _start_exabgp(self): 44 cmd = CmdBuffer(' ') 45 cmd << 'env exabgp.log.destination={0}/exabgpd.log'.format(self.SHARED_VOLUME) 46 cmd << 'exabgp.daemon.user=root' 47 cmd << 'exabgp.daemon.pid={0}'.format(self.PID_FILE) 48 cmd << 'exabgp.tcp.bind="0.0.0.0" exabgp.tcp.port=179' 49 cmd << 'exabgp {0}/exabgpd.conf'.format(self.SHARED_VOLUME) 50 self.local(str(cmd), detach=True) 51 52 def _wait_for_boot(self): 53 def _f(): 54 ret = self.local('exabgpcli version > /dev/null 2>&1; echo $?', capture=True) 55 return ret == '0' 56 57 return wait_for_completion(_f) 58 59 def run(self): 60 super(ExaBGPContainer, self).run() 61 self._pre_start_exabgp() 62 # To start ExaBGP, it is required to configure neighbor settings, so 63 # here does not start ExaBGP yet. 64 # self._start_exabgp() 65 return self.WAIT_FOR_BOOT 66 67 def create_config(self): 68 # Manpage of exabgp.conf(5): 69 # https://github.com/Exa-Networks/exabgp/blob/master/doc/man/exabgp.conf.5 70 cmd = CmdBuffer('\n') 71 for peer, info in self.peers.iteritems(): 72 cmd << 'neighbor {0} {{'.format(info['neigh_addr'].split('/')[0]) 73 cmd << ' router-id {0};'.format(self.router_id) 74 cmd << ' local-address {0};'.format(info['local_addr'].split('/')[0]) 75 cmd << ' local-as {0};'.format(self.asn) 76 cmd << ' peer-as {0};'.format(peer.asn) 77 78 caps = [] 79 if info['as2']: 80 caps.append(' asn4 disable;') 81 if info['addpath']: 82 caps.append(' add-path send/receive;') 83 if caps: 84 cmd << ' capability {' 85 for cap in caps: 86 cmd << cap 87 cmd << ' }' 88 89 if info['passwd']: 90 cmd << ' md5-password "{0}";'.format(info['passwd']) 91 92 if info['passive']: 93 cmd << ' passive;' 94 cmd << '}' 95 96 with open('{0}/exabgpd.conf'.format(self.config_dir), 'w') as f: 97 print colors.yellow('[{0}\'s new exabgpd.conf]'.format(self.name)) 98 print colors.yellow(str(cmd)) 99 f.write(str(cmd)) 100 101 def _is_running(self): 102 ret = self.local("test -f {0}; echo $?".format(self.PID_FILE), capture=True) 103 return ret == '0' 104 105 def reload_config(self): 106 if not self.peers: 107 return 108 109 def _reload(): 110 if self._is_running(): 111 self.local('/usr/bin/pkill --pidfile {0} && rm -f {0}'.format(self.PID_FILE), capture=True) 112 else: 113 self._start_exabgp() 114 self._wait_for_boot() 115 116 if not self._is_running(): 117 raise RuntimeError('Could not start ExaBGP') 118 119 try_several_times(_reload) 120 121 def _construct_ip_unicast(self, path): 122 cmd = CmdBuffer(' ') 123 cmd << str(path['prefix']) 124 if path['next-hop']: 125 cmd << 'next-hop {0}'.format(path['next-hop']) 126 else: 127 cmd << 'next-hop self' 128 return str(cmd) 129 130 def _construct_flowspec(self, path): 131 cmd = CmdBuffer(' ') 132 cmd << '{ match {' 133 for match in path['matchs']: 134 cmd << '{0};'.format(match) 135 cmd << '} then {' 136 for then in path['thens']: 137 cmd << '{0};'.format(then) 138 cmd << '} }' 139 return str(cmd) 140 141 def _construct_path_attributes(self, path): 142 cmd = CmdBuffer(' ') 143 if path['as-path']: 144 cmd << 'as-path [{0}]'.format(' '.join(str(i) for i in path['as-path'])) 145 if path['med']: 146 cmd << 'med {0}'.format(path['med']) 147 if path['local-pref']: 148 cmd << 'local-preference {0}'.format(path['local-pref']) 149 if path['community']: 150 cmd << 'community [{0}]'.format(' '.join(c for c in path['community'])) 151 if path['extended-community']: 152 cmd << 'extended-community [{0}]'.format(path['extended-community']) 153 if path['attr']: 154 cmd << 'attribute [ {0} ]'.format(path['attr']) 155 return str(cmd) 156 157 def _construct_path(self, path, rf='ipv4', is_withdraw=False): 158 cmd = CmdBuffer(' ') 159 160 if rf in ['ipv4', 'ipv6']: 161 cmd << 'route' 162 cmd << self._construct_ip_unicast(path) 163 elif rf in ['ipv4-flowspec', 'ipv6-flowspec']: 164 cmd << 'flow route' 165 cmd << self._construct_flowspec(path) 166 else: 167 raise ValueError('unsupported address family: %s' % rf) 168 169 if path['identifier']: 170 cmd << 'path-information {0}'.format(path['identifier']) 171 172 if not is_withdraw: 173 # Withdrawal should not require path attributes 174 cmd << self._construct_path_attributes(path) 175 176 return str(cmd) 177 178 def add_route(self, route, rf='ipv4', attribute=None, aspath=None, 179 community=None, med=None, extendedcommunity=None, 180 nexthop=None, matchs=None, thens=None, 181 local_pref=None, identifier=None, reload_config=False): 182 if not self._is_running(): 183 raise RuntimeError('ExaBGP is not yet running') 184 185 self.routes.setdefault(route, []) 186 path = { 187 'prefix': route, 188 'rf': rf, 189 'attr': attribute, 190 'next-hop': nexthop, 191 'as-path': aspath, 192 'community': community, 193 'med': med, 194 'local-pref': local_pref, 195 'extended-community': extendedcommunity, 196 'identifier': identifier, 197 'matchs': matchs, 198 'thens': thens, 199 } 200 201 cmd = CmdBuffer(' ') 202 cmd << "exabgpcli 'announce" 203 cmd << self._construct_path(path, rf=rf) 204 cmd << "'" 205 self.local(str(cmd), capture=True) 206 207 self.routes[route].append(path) 208 209 def del_route(self, route, identifier=None, reload_config=False): 210 if not self._is_running(): 211 raise RuntimeError('ExaBGP is not yet running') 212 213 path = None 214 new_paths = [] 215 for p in self.routes.get(route, []): 216 if p['identifier'] != identifier: 217 new_paths.append(p) 218 else: 219 path = p 220 if not path: 221 return 222 223 rf = path['rf'] 224 cmd = CmdBuffer(' ') 225 cmd << "exabgpcli 'withdraw" 226 cmd << self._construct_path(path, rf=rf, is_withdraw=True) 227 cmd << "'" 228 self.local(str(cmd), capture=True) 229 230 self.routes[route] = new_paths 231 232 def _get_adj_rib(self, peer, rf, in_out='in'): 233 # IPv4 Unicast: 234 # neighbor 172.17.0.2 ipv4 unicast 192.168.100.0/24 path-information 0.0.0.20 next-hop self 235 # IPv6 FlowSpec: 236 # neighbor 172.17.0.2 ipv6 flow flow destination-ipv6 2002:1::/64/0 source-ipv6 2002:2::/64/0 next-header =udp flow-label >100 237 rf_map = { 238 'ipv4': ['ipv4', 'unicast'], 239 'ipv6': ['ipv6', 'unicast'], 240 'ipv4-flowspec': ['ipv4', 'flow'], 241 'ipv6-flowspec': ['ipv6', 'flow'], 242 } 243 assert rf in rf_map 244 assert in_out in ('in', 'out') 245 peer_addr = self.peer_name(peer) 246 lines = self.local('exabgpcli show adj-rib {0}'.format(in_out), capture=True).split('\n') 247 # rib = { 248 # <nlri>: [ 249 # { 250 # 'nlri': <nlri>, 251 # 'next-hop': <next-hop>, 252 # ... 253 # }, 254 # ... 255 # ], 256 # } 257 rib = {} 258 for line in lines: 259 if not line: 260 continue 261 values = line.split() 262 if peer_addr != values[1]: 263 continue 264 elif rf is not None and rf_map[rf] != values[2:4]: 265 continue 266 if rf in ('ipv4', 'ipv6'): 267 nlri = values[4] 268 rib.setdefault(nlri, []) 269 path = {k: v for k, v in zip(*[iter(values[5:])] * 2)} 270 path['nlri'] = nlri 271 rib[nlri].append(path) 272 elif rf in ('ipv4-flowspec', 'ipv6-flowspec'): 273 # XXX: Missing path attributes? 274 nlri = ' '.join(values[5:]) 275 rib.setdefault(nlri, []) 276 path = {'nlri': nlri} 277 rib[nlri].append(path) 278 return rib 279 280 def get_adj_rib_in(self, peer, rf='ipv4'): 281 return self._get_adj_rib(peer, rf, 'in') 282 283 def get_adj_rib_out(self, peer, rf='ipv4'): 284 return self._get_adj_rib(peer, rf, 'out') 285 286 287 class RawExaBGPContainer(ExaBGPContainer): 288 def __init__(self, name, config, ctn_image_name='osrg/exabgp', 289 exabgp_path=''): 290 asn = None 291 router_id = None 292 for line in config.split('\n'): 293 line = line.strip() 294 if line.startswith('local-as'): 295 asn = int(line[len('local-as'):].strip('; ')) 296 if line.startswith('router-id'): 297 router_id = line[len('router-id'):].strip('; ') 298 if not asn: 299 raise Exception('asn not in exabgp config') 300 if not router_id: 301 raise Exception('router-id not in exabgp config') 302 self.config = config 303 304 super(RawExaBGPContainer, self).__init__(name, asn, router_id, 305 ctn_image_name, exabgp_path) 306 307 def create_config(self): 308 with open('{0}/exabgpd.conf'.format(self.config_dir), 'w') as f: 309 print colors.yellow('[{0}\'s new exabgpd.conf]'.format(self.name)) 310 print colors.yellow(self.config) 311 f.write(self.config)