Esempio n. 1
0
 def _load_config(self):
     config_file = os.environ.get('FBGP_CONFIG', '/etc/fbgp/fbgp.yaml')
     self.vlans = {}
     for dp in [valve.dp for valve in self.valves.values()]:
         self.vlans.update(dp.vlans)
     with open(config_file, 'r') as f:
         config = yaml.safe_load(f.read())
         self.import_policy = {}
         self.export_policy = {}
         self.routerid = ipaddress.ip_address(config['routerid'])
         self.peers = {}
         for peer_conf in config.pop('peers'):
             peer_ip = ipaddress.ip_address(peer_conf['peer_ip'])
             local_ip = peer_conf.get('local_ip')
             local_ip = ipaddress.ip_address(local_ip) if local_ip else None
             peer = BgpPeer(peer_ip=peer_ip,
                            peer_as=peer_conf['peer_as'],
                            local_ip=local_ip,
                            local_as=peer_conf.get('local_as', None),
                            peer_port=peer_conf.get('peer_port', 179))
             for vlan in self.vlans.values():
                 if vlan.ip_in_vip_subnet(peer_ip):
                     peer.vlan = vlan
                     faucet_vips = vlan.faucet_vips_by_ipv(peer_ip.version)
                     if faucet_vips:
                         peer.faucet_vip = list(faucet_vips)[0]
             self.peers[peer_ip] = peer
         self.borders = {}
         for border_conf in config.pop('borders'):
             routerid = ipaddress.ip_address(border_conf['routerid'])
             self.borders[routerid] = Border(
                     routerid=routerid, nexthop=ipaddress.ip_address(border_conf['nexthop']))
         self.bgp = BgpRouter(self.borders, self.peers, self.path_change_handler)
         self.logger.info('config loaded')
Esempio n. 2
0
 def setUp(self):
     peers = {}
     borders = {}
     for peerip, peeras in [('10.0.1.1', 6510), ('10.0.2.2', 4122),
                            ('10.10.10.1', 65000)]:
         peerip = ipaddress.ip_address(peerip)
         peers[peerip] = BgpPeer(peeras, peerip, 65000)
         peers[peerip].bgp_session_up()
     self.bgp = BgpRouter(borders, peers, self.smoke_path_change_handler)
     self.peer = peers[ipaddress.ip_address('10.0.1.1')]
Esempio n. 3
0
    def setUp(self):
        self.external_peers = []
        self.internal_peers = []

        borders = {}
        peers = {}
        for peerip, peeras in [('10.0.0.1', 1), ('10.0.0.2', 2), ('10.0.0.3', 3)]:
            peerip = ipaddress.ip_address(peerip)
            peers[peerip] = BgpPeer(peeras, peerip, 65000)
            peers[peerip].bgp_session_up()
            self.external_peers.append(peers[peerip])

        for peerip, peeras in [('10.0.10.1', 65000), ('10.0.10.2', 65000)]:
            peerip = ipaddress.ip_address(peerip)
            peers[peerip] = BgpPeer(peeras, peerip, 65000)
            peers[peerip].bgp_session_up()
            self.internal_peers.append(peers[peerip])

        self.mock_path_change_handler = Mock()
        self.bgp = BgpRouter(borders, peers, self.mock_path_change_handler)

        self.prefix = ipaddress.ip_network('1.0.0.0/24')
        self.as_path = [1, 2, 3]
Esempio n. 4
0
class FlowBasedBGP(app_manager.RyuApp):
    """An application runs on ExaBGP to process BGP routes received from peers."""

    _CONTEXTS = {
        'faucet_experimental_api':
        faucet_experimental_api.FaucetExperimentalAPI,
    }

    peers = None
    borders = None
    routerid = None
    faucet_connect = None  # interface to Faucet
    exabgp_connect = None  # interface to exabgp
    server_connect = None  # interface to the route controller
    current_pathid = 0
    path_mapping = None  # mapping between a peer and path, managed by the route server

    def __init__(self, *args, **kwargs):
        super(FlowBasedBGP, self).__init__(*args, **kwargs)
        self.logger = get_logger('fbgp', os.environ.get('FBGP_LOG', None),
                                 os.environ.get('FBGP_LOG_LEVEL', 'info'))
        self.faucet_api = kwargs['faucet_experimental_api']
        self.nexthop_to_pathid = {}
        self.path_mapping = collections.defaultdict(set)
        self.vip_assignment = {}
        self.rcv_msg_q = eventlet.Queue(256)

    def stop(self):
        self.logger.info('%s is stopping...' % self.__class__.__name__)
        super(FlowBasedBGP, self).stop()
        sys.exit()

    def _load_config(self):
        config_file = os.environ.get('FBGP_CONFIG', '/etc/fbgp/fbgp.yaml')
        self.vlans = {}
        for dp in [valve.dp for valve in self.valves.values()]:
            self.vlans.update(dp.vlans)
        with open(config_file, 'r') as f:
            config = yaml.safe_load(f.read())
            self.import_policy = {}
            self.export_policy = {}
            self.routerid = ipaddress.ip_address(config['routerid'])
            self.peers = {}
            for peer_conf in config.pop('peers'):
                peer_ip = ipaddress.ip_address(peer_conf['peer_ip'])
                local_ip = peer_conf.get('local_ip')
                local_ip = ipaddress.ip_address(local_ip) if local_ip else None
                peer = BgpPeer(peer_ip=peer_ip,
                               peer_as=peer_conf['peer_as'],
                               local_ip=local_ip,
                               local_as=peer_conf['local_as'],
                               peer_port=peer_conf.get('peer_port', 179))
                for vlan in self.vlans.values():
                    if vlan.ip_in_vip_subnet(peer_ip):
                        peer.vlan = vlan
                        faucet_vips = vlan.faucet_vips_by_ipv(peer_ip.version)
                        if faucet_vips:
                            peer.faucet_vip = list(faucet_vips)[0]
                self.peers[peer_ip] = peer
            self.borders = {}
            for border_conf in config.pop('borders'):
                routerid = ipaddress.ip_address(border_conf['routerid'])
                self.borders[routerid] = Border(routerid=routerid,
                                                nexthop=ipaddress.ip_address(
                                                    border_conf['nexthop']))
            self.bgp = BgpRouter(self.borders, self.peers,
                                 self.path_change_handler)
            self.logger.info('config loaded')

    @set_ev_cls(faucet.EventFaucetExperimentalAPIRegistered)
    def initialize(self, ev=None):
        self.logger.info('Initializing fBGP controller')
        self.valves = self.faucet_api.faucet.valves_manager.valves
        if not self.valves:
            self.logger.error(
                'Exitting...failed to get info from Faucet (Faucet probably has failed)'
            )
            self.stop()
        self._load_config()
        for name, connector_cls, kwargs in [('faucet_connect', FaucetConnect, {
                'handler':
                self._process_faucet_msg
        }),
                                            ('exabgp_connect', ExaBgpConnect, {
                                                'handler':
                                                self._process_exabgp_msg,
                                                'peers': self.peers,
                                                'routerid': self.routerid
                                            }),
                                            ('server_connect', ServerConnect, {
                                                'handler':
                                                self._process_server_msg
                                            })]:
            connector = connector_cls(**kwargs)
            setattr(self, name, connector)
            self.logger.info('Created connector: %s' % name)
        for name in ['faucet_connect', 'exabgp_connect', 'server_connect']:
            connector = getattr(self, name)
            t = connector.start()
            if t is not None:
                self.logger.info('Connector %s started' % name)
            else:
                self.logger.info('Connector %s failed to start' % name)
                self.stop()

    def _get_pathid(self, nexthop):
        """Return a unique pathid for a nexthop."""
        if nexthop in self.nexthop_to_pathid:
            return self.nexthop_to_pathid[nexthop]
        else:
            self.current_pathid += 1
            self.nexthop_to_pathid[nexthop] = self.current_pathid
            return self.current_pathid

    def _get_vip(self, nexthop, vlan):
        """return vip (extra) if we still have one."""
        if not (nexthop and vlan):
            return
        if (nexthop, vlan) in self.vip_assignment:
            return self.vip_assignment[(nexthop, vlan)]
        used_vips = set(self.vip_assignment.values())
        for vip in vlan.faucet_ext_vips:
            if vip not in used_vips:
                self.vip_assignment[(nexthop, vlan)] = vip
                return vip
        return None

    def _update_fib(self,
                    prefix,
                    nexthop,
                    dpid=None,
                    vid=None,
                    pathid=None,
                    add=True):
        if add:
            self.faucet_api.add_route(prefix,
                                      nexthop,
                                      dpid=dpid,
                                      vid=vid,
                                      pathid=pathid)
            self.logger.debug(
                'Added extended FIB rule to datapath: prefix=%s, nexthop=%s, pathid=%s, dpid=%s, vid=%s'
                % (str(prefix), str(nexthop), pathid, dpid, vid))

        else:  # consider if the route is still being used by some peers before deleteing
            #self.faucet_api.del_route(prefix, nexthop, dpid=dpid, vid=vid, pathid=pathid)
            pass

    def _update_mapping(self, vip, pathid, dpid, vid, add=True):
        if add:
            self.faucet_api.add_ext_vip(vip, pathid=pathid, dpid=dpid, vid=vid)
            self.logger.info(
                'Added mapping rule to datapath: vip=%s, pathid=%s, dpid=%s, vid=%s'
                % (str(vip), pathid, dpid, vid))
        else:
            #TODO: need to check if there is peer using this before deleting
            #self.faucet_api.del_ext_vip(vip, pathid=pathid, dpid=dpid, vid=vid)
            pass

    def path_change_handler(self, peer, route, withdraw=False):
        """handle route advertisement or withdrawal event."""
        msgs = []
        if not route:
            return []
        if withdraw:
            new_best, cur_best = self.bgp.del_route(route)
        else:
            new_best, cur_best = self.bgp.add_route(route)
        if new_best:
            nexthop = new_best.nexthop
            if peer.is_ibgp() and nexthop in self.borders:
                nexthop = self.borders[nexthop].nexthop
            self.logger.info('new best path for %s via %s: %s' %
                             (route.prefix, nexthop, new_best))
            self.logger.debug('previous best path for %s was %s' %
                              (route.prefix, cur_best))
            self._update_fib(new_best.prefix, nexthop, peer.dp_id,
                             peer.vlan_vid)

        for other_peer in self._other_peers(peer):
            if other_peer.state == 'down':
                continue
            if other_peer in self.path_mapping[route.prefix, route.nexthop]:
                gateway = self._get_vip(route.nexthop, other_peer.vlan)
                pathid = self._get_pathid(route.nexthop)
                if withdraw:
                    if new_best:
                        msgs.extend(self.bgp.announce(other_peer, new_best))
                    else:
                        msgs.extend(self.bgp.withdraw(other_peer, route))
                        self._update_mapping(gateway, pathid, other_peer.dp_id,
                                             other_peer.vlan_vid, False)
                        self._update_fib(route.prefix, route.nexthop,
                                         peer.dp_id, peer.vlan_vid, pathid,
                                         False)
                else:
                    msgs.extend(self.bgp.announce(other_peer, route, gateway))
                    self._update_mapping(gateway, pathid, other_peer.dp_id,
                                         other_peer.vlan_vid)
                    self._update_fib(route.prefix, route.nexthop, peer.dp_id,
                                     peer.vlan_vid, pathid)

            else:
                _route = None
                kwargs = {}
                if not new_best and cur_best and withdraw:
                    func = self.bgp.withdraw
                    _route = cur_best
                elif new_best and cur_best:
                    if new_best.from_peer == other_peer.peer_ip:
                        func = self.bgp.withdraw
                        _route = cur_best
                    else:
                        func = self.bgp.announce
                        _route = new_best
                        kwargs['gateway'] = None
                elif new_best and not cur_best:
                    func = self.bgp.announce
                    _route = new_best
                    kwargs['gateway'] = None

                if _route:
                    if other_peer.is_ibgp() and 'gateway' in kwargs:
                        kwargs['gateway'] = self.routerid
                    msgs.extend(func(other_peer, _route, **kwargs))
        return msgs

    def register(self):
        self._send_to_server({
            'msg_type': 'router_up',
            'routerid': str(self.routerid),
            'state': 'up'
        })

        for peer in self.peers.values():
            msg = {
                'msg_type': 'peer_up',
                'peer_ip': str(peer.peer_ip),
                'peer_as': peer.peer_as,
                'local_ip': str(self.routerid),
                'local_as': peer.local_as,
                'state': peer.state
            }
            self._send_to_server(msg)
            if peer.is_connected:
                msg = {
                    'msg_type': 'nexthop_up',
                    'routerid': str(self.routerid),
                    'nexthop': str(peer.peer_ip),
                    'pathid': self._get_pathid(peer.peer_ip),
                    'dp_id': peer.dp_id,
                    'port_no': peer.port_no,
                    'vlan_vid': peer.vlan_vid
                }
                self._send_to_server(msg)
            for route in peer.routes():
                self._notify_route_change(peer.peer_ip, route)
        for border in self.borders.values():
            if border.connected and border.dp_id and border.vlan_vid and border.port_no:
                attrs = {
                    'dp': border.dp_id,
                    'vlan': border.vlan_vid,
                    'port': border.port_no
                }
                src = str(self.routerid)
                dst = str(border.routerid)
                self._send_to_server({
                    'msg_type': 'link_up',
                    'src': src,
                    'dst': dst,
                    'attributes': attrs
                })

    def deregister(self):
        pass

    def _route_by_nexthop(self, prefix, nexthop):
        """return a route for a prefix by its nexthop."""
        for route in self.bgp.loc_rib.get(prefix, []):
            if route.nexthop == nexthop:
                return route
        return None

    def _add_mapping(self, peer_ip, prefix, nexthop, egress=None, pathid=None):
        """create a mapping between a peer and a route.
        egress is None assuming the nexthop is local"""
        if pathid is None and egress is None or self.routerid == egress:  # this is the local route
            peer = self.peers[peer_ip]
            self.path_mapping[prefix, nexthop].add(peer)
            vip = self._get_vip(nexthop, peer.vlan)

            if not vip:
                return []
            mypathid = self._get_pathid(nexthop)
            if mypathid != pathid:
                self.logger.error(
                    'There must be something wrong, pathids differ')
                return []
            route = self._route_by_nexthop(prefix, nexthop)
            if route:
                self._update_mapping(vip, pathid, peer.dp_id, peer.vlan_vid)
                learned_peer = self.peers[route.learned_from_peer]
                self._update_fib(prefix, nexthop, learned_peer.dp_id,
                                 learned_peer.vlan_vid, pathid)
            return self.bgp.announce(peer, route, gateway=vip)
        else:
            #TODO: handle the case when route is remote
            return []

    def _del_mapping(self, peer_ip, prefix, nexthop, egress=None, pathid=None):
        msgs = []
        if egress is None and pathid is None:
            peer = self.peers[peer_ip]
            if peer not in self.path_mapping[prefix, nexthop]:
                return []
            best_route = self.bgp.best_routes.get(prefix)
            if best_route:
                # advertise best route instead
                msgs = self.bgp.announce(peer, best_route)
            else:
                route = self._route_by_nexthop(prefix, nexthop)
                msgs = self.bgp.withdraw(peer, route)
        return msgs

    def _notify_route_change(self, peer_ip, route, withdraw=False):
        """notify the route server about a route."""
        if not route:
            return
        msg_type = 'route_down' if withdraw else 'route_up'
        msg = {
            'msg_type': msg_type,
            'peer_ip': str(peer_ip),
            'next_hop': str(route.nexthop),
            'prefix': str(route.prefix),
            'local_pref': route.local_pref,
            'med': route.med,
            'as_path': route.as_path
        }
        self._send_to_server(msg)

    def _send(self, connector, msg):
        if connector:
            connector.send(msg)
            self.logger.debug('sent a msg to %s: %s' %
                              (connector.__class__.__name__, msg))

    def _send_to_server(self, msg):
        self._send(self.server_connect, msg)

    def _send_to_exabgp(self, msg):
        self._send(self.exabgp_connect, msg)

    def _peer_bgp_up(self, peer):
        def non_best_route(peer, routes):
            for route in routes:
                if peer in self.path_mapping[route.prefix, route.nexthop]:
                    return route
            return None

        def best_route(prefix):
            if prefix in self.bgp.best_routes:
                return self.bgp.best_routes[prefix]
            return None

        if peer.state == 'up':
            return []
        self._send_to_server({
            'msg_type': 'peer_up',
            'peer_ip': str(peer.peer_ip),
            'peer_as': peer.peer_as,
            'local_ip': str(self.routerid),
            'local_as': peer.local_as,
            'state': 'up'
        })
        msgs = []
        peer.bgp_session_up()
        # advertise local subnets
        #prefixes = set([str(p.faucet_vip.network) for p in self.peers.values() if p.faucet_vip])
        #for prefix in prefixes:
        #    msgs.extend(self.bgp.announce_prefix(peer, prefix))
        # for each prefix, advertise non-best path if it is configured, otherwise advertise best path
        for prefix, routes in self.bgp.loc_rib.items():
            gateway = None
            pathid = None
            route = non_best_route(peer, routes)
            if route:
                gateway = self._get_vip(route.nexthop, peer.vlan)
                pathid = self._get_pathid(route.nexthop)
                self._update_mapping(gateway, pathid, peer.dp_id,
                                     peer.vlan_vid)
                learned_peer = self.peers[route.learned_from_peer]
                self._update_fib(prefix, route.nexthop, learned_peer.dp_id,
                                 learned_peer.vlan_vid, pathid)
            else:
                route = best_route(prefix)
            msgs.extend(self.bgp.announce(peer, route, gateway))
        return msgs

    def _border_connected(self, border, dpid, vid, port_no):
        attrs = {'dp': dpid, 'vlan': vid, 'port': port_no}
        src = str(self.routerid)
        dst = str(border.routerid)
        self._send_to_server({
            'msg_type': 'link_up',
            'src': src,
            'dst': dst,
            'attributes': attrs
        })
        if border.disconnected():
            border.connected(dpid, vid, port_no)
            self.logger.info('Border %s is connected' % border.routerid)

    def _border_disconnected(self, border):
        src = str(self.routerid)
        dst = str(border.routerid)
        self._send_to_server({'msg_type': 'link_down', 'src': src, 'dst': dst})
        border.disconnected()
        self.logger.info('Border %s is disconnected' % border.routerid)

    def _peer_bgp_down(self, peer):
        if peer.state == 'down':
            return []
        self._send_to_server({
            'msg_type': 'peer_down',
            'peer_ip': str(peer.peer_ip)
        })
        msgs = []
        for route in peer._rib_in.values():
            msgs.extend(self.path_change_handler(peer, route, True))
        peer.bgp_session_down()
        return msgs

    def _peer_connected(self, peer, dp_id, vlan_vid, port_no):
        if peer.is_connected:
            return []
        peer.connected(dp_id, vlan_vid, port_no)
        self._send_to_server({
            'msg_type': 'nexthop_up',
            'routerid': str(self.routerid),
            'nexthop': str(peer.peer_ip),
            'pathid': self._get_pathid(peer.peer_ip),
            'dp_id': dp_id,
            'port_no': port_no,
            'vlan_vid': vlan_vid
        })
        return []

    def _peer_disconnected(self, peer):
        if not peer.is_connected:
            return []
        peer.disconnected()
        self._send_to_server({
            'msg_type': 'nexthop_down',
            'routerid': str(self.routerid),
            'nexthop': str(peer.peer_ip)
        })
        return []

    def _peer_state_change(self, peer_ip, state, **kwargs):
        peer = self.peers[peer_ip]
        method = None
        kwargs['peer'] = peer
        if state == 'up':
            method = self._peer_bgp_up
        elif state == 'down':
            method = self._peer_bgp_down
        elif state == 'connected':
            method = self._peer_connected
        elif state == 'disconnected':
            method = self._peer_disconnected
        if method:
            return method(**kwargs)
        return []

    def _process_exabgp_msg(self, msg):
        """Process message received from ExaBGP."""
        if msg in ['done', 'error'] or not msg:
            return []
        self.logger.debug('processing msg from exabgp: %r' % msg)
        try:
            msg = json.loads(msg)
            if msg.get('type') == 'notification':
                #TODO: handle notification
                return []
            neighbor = msg.get('neighbor', {})
            if not neighbor or neighbor.get('direction') == 'send':
                return []
            local_ip = ipaddress.ip_address(neighbor['address']['local'])
            peer_ip = ipaddress.ip_address(neighbor['address']['peer'])
            local_as = neighbor['asn']['local']
            peer_as = neighbor['asn']['peer']
            msgs = []
            if msg.get('type') == 'update' and 'update' in neighbor['message']:
                update = neighbor['message']['update']
                msgs = self._process_bgp_update(peer_ip, update)
            elif msg.get('type') == 'state':
                state = 'up' if neighbor['state'] == 'up' else 'down'
                msgs = self._peer_state_change(peer_ip, state)
            for msg in msgs:
                self._send_to_exabgp(msg)
        except Exception as e:
            self.logger.error('Error when processing msg %s: %s' % (msg, e))
            traceback.print_exc()

    def _other_peers(self, peer):
        return [
            other_peer for other_peer in self.peers.values()
            if other_peer != peer
        ]

    def _process_bgp_update(self, peer_ip, update):
        """Process a BGP update received from ExaBGP."""
        self.logger.debug('processing update from %s: %s' % (peer_ip, update))
        try:
            msgs = []
            if peer_ip not in self.peers:
                return []
            peer = self.peers[peer_ip]
            if 'announce' in update and 'ipv4 unicast' in update['announce']:
                attributes = update['attribute']
                if 'as-path' not in attributes:
                    if peer.local_as != peer.peer_as:
                        self.logger.error('received malformed update')
                        return []
                    attributes['as-path'] = [peer.peer_as]
                elif peer.local_as in attributes['as-path']:  # loop avoidance
                    return []
                for name, attr in [('origin', 'origin'), ('igp', 'igp'),
                                   ('as_path', 'as-path'), ('med', 'med'),
                                   ('community', 'communities'),
                                   ('local_pref', 'local-preference')]:
                    attributes[name] = update['attribute'].get(attr)

                for nexthop, nlris in update['announce']['ipv4 unicast'].items(
                ):
                    nexthop = ipaddress.ip_address(nexthop)
                    if nexthop == peer.local_ip:
                        continue
                    for prefix in nlris:
                        prefix = ipaddress.ip_network(prefix['nlri'])
                        route = peer.rcv_announce(prefix, nexthop,
                                                  **attributes)
                        self._notify_route_change(peer_ip, route)
                        msgs.extend(self.path_change_handler(peer, route))
            if 'withdraw' in update and 'ipv4 unicast' in update['withdraw']:
                for prefix in update['withdraw']['ipv4 unicast']:
                    prefix = ipaddress.ip_network(prefix['nlri'])
                    route = peer.rcv_withdraw(prefix)
                    if route:
                        self._notify_route_change(peer_ip, route, True)
                        msgs.extend(self.path_change_handler(
                            peer, route, True))
            return msgs
        except Exception as e:
            self.logger.error('Error when processing update %s: %s' %
                              (update, e))
            traceback.print_exc()
        return []

    def _process_faucet_msg(self, msg):
        """Process message received from Faucet Controller."""
        dpid = msg['dp_id']
        if 'L2_LEARN' in msg and msg['L2_LEARN']['l3_src_ip'] != 'None':
            l2_learn = msg['L2_LEARN']
            ipa = ipaddress.ip_address(l2_learn['l3_src_ip'])
            vid = l2_learn['vid']
            port_no = l2_learn['port_no']
            if ipa in self.peers:
                peer = self.peers[ipa]
                if peer.is_connected:
                    return
                self._peer_state_change(ipa,
                                        'connected',
                                        dp_id=dpid,
                                        port_no=port_no,
                                        vlan_vid=vid)
                self.logger.info('Peer %s (ASN: %s) is connected' %
                                 (peer.peer_ip, peer.peer_as))
            else:
                for border in self.borders.values():
                    if border.nexthop == ipa:
                        self._border_connected(border, dpid, vid, port_no)
        elif 'L2_EXPIRE' in msg:
            #TODO: handle expire event
            pass

    def _process_server_msg(self, msg):
        """Process message received from Route Controller."""
        self.logger.debug('Process msg from server: %s' % msg)
        msgs = []
        msg_type = msg['msg_type']
        msg = msg['msg']
        try:
            if type(msg) == str:
                msg = json.loads(msg)
            if msg_type == 'server_connected':
                #TODO: process server connected event
                self.logger.info('Connected to server: %s' % msg)
                self.register()
            elif msg_type == 'server_disconnected':
                #TODO: process server disconnected event
                self.logger.info('Disconnected from server: %s' % msg)
                self.deregister()
            elif msg_type == 'server_command':
                #TODO: process server commands
                self.logger.info('Receive msg from server: %s' % msg)
                command = msg.get('command')
                if command in ['add_mapping', 'del_mapping']:
                    routerid = ipaddress.ip_address(msg['routerid'])
                    prefix = ipaddress.ip_network(msg['prefix'])
                    nexthop = ipaddress.ip_address(msg['nexthop'])
                    egress = ipaddress.ip_address(msg['egress'])
                    pathid = int(msg['pathid'])
                    for_peer = msg['for_peer']
                    if for_peer:
                        peer = self.peers[routerid]
                        if command == 'add_mapping':
                            msgs = self._add_mapping(routerid, prefix, nexthop,
                                                     egress, pathid)
                        elif command == 'del_mapping':
                            msgs = self._del_mapping(routerid, prefix, nexthop,
                                                     egress, pathid)
                    else:
                        best_route = self.bgp.best_routes.get(prefix)
                        if best_route and best_route.nexthop == nexthop:
                            return
                        route = self._route_by_nexthop(prefix, nexthop)
                        if not route:
                            return
                        self.bgp.best_routes[prefix] = route
                        learned_peer = self.peers[route.learned_from_peer]
                        self._update_fib(route.prefix, route.nexthop,
                                         learned_peer.dp_id,
                                         learned_peer.vlan_vid)
                        for peer in self.peers.values():
                            if peer.peer_ip == route.learned_from_peer:
                                continue
                            msgs.extend(self.bgp.announce(peer, route))
                elif command == 'add_tunnel':
                    pass
            for msg in msgs:
                self._send_to_exabgp(msg)
        except Exception as e:
            self.logger.error('Error when handling %s: %s' % (msg, e))
Esempio n. 5
0
class TestBGP(unittest.TestCase):

    def setUp(self):
        self.external_peers = []
        self.internal_peers = []

        borders = {}
        peers = {}
        for peerip, peeras in [('10.0.0.1', 1), ('10.0.0.2', 2), ('10.0.0.3', 3)]:
            peerip = ipaddress.ip_address(peerip)
            peers[peerip] = BgpPeer(peeras, peerip, 65000)
            peers[peerip].bgp_session_up()
            self.external_peers.append(peers[peerip])

        for peerip, peeras in [('10.0.10.1', 65000), ('10.0.10.2', 65000)]:
            peerip = ipaddress.ip_address(peerip)
            peers[peerip] = BgpPeer(peeras, peerip, 65000)
            peers[peerip].bgp_session_up()
            self.internal_peers.append(peers[peerip])

        self.mock_path_change_handler = Mock()
        self.bgp = BgpRouter(borders, peers, self.mock_path_change_handler)

        self.prefix = ipaddress.ip_network('1.0.0.0/24')
        self.as_path = [1, 2, 3]

    def tearDown(self):
        pass

    def test_add_route(self):
        as_path = list(self.as_path)
        for peer in self.external_peers:
            route = peer.rcv_announce(self.prefix, peer.peer_ip, list(as_path), origin=2)
            new_best, cur_best = self.bgp.add_route(route)
            self.assertTrue(new_best)
            self.assertEqual(new_best.nexthop, peer.peer_ip)
            self.assertEqual(new_best.as_path, as_path)
            self.assertEqual(new_best.from_as, peer.peer_as)
            as_path.pop(0)

        self.assertEqual(len(self.bgp.loc_rib), 1)
        self.assertEqual(len(self.bgp.loc_rib[self.prefix]), 3)

    def test_del_route(self):
        as_path = list(self.as_path)
        for peer in self.external_peers:
            route = peer.rcv_announce(self.prefix, peer.peer_ip, list(as_path), origin=2)
            new_best, cur_best = self.bgp.add_route(route)
            as_path.pop(0)

        for idx, peer in enumerate(self.external_peers[1:][::-1]):
            route = peer.rcv_withdraw(self.prefix)
            new_best, cur_best = self.bgp.del_route(route)
            self.assertTrue(len(peer._rib_in) == 0)
            self.assertTrue(new_best)
            next_peer = self.external_peers[len(self.external_peers) - idx - 2]
            self.assertEqual(new_best.nexthop, next_peer.peer_ip)
            self.assertEqual(new_best.from_as, next_peer.peer_as)

        route = self.external_peers[0].rcv_withdraw(self.prefix)
        new_best, cur_best = self.bgp.del_route(route)
        self.assertFalse(new_best)

        self.assertEqual(len(self.bgp.loc_rib), 0)

    def test_del_route_no_exist(self):
        route = self.external_peers[0].rcv_withdraw(self.prefix)
        self.assertFalse(route)

    def test_duplicate_announce(self):
        peer = self.external_peers[0]
        route = peer.rcv_announce(self.prefix, peer.peer_ip, [1], origin=2)
        new_best, cur_best = self.bgp.add_route(route)
        self.assertTrue(new_best)
        self.assertFalse(cur_best)

        route = peer.rcv_announce(self.prefix, peer.peer_ip, [1], origin=2)
        self.assertTrue(route is None)

    def test_bgp_best_path_no_change(self):
        peer = self.external_peers[0]
        route = peer.rcv_announce(self.prefix, peer.peer_ip, [1], 1)
        new_best, cur_best = self.bgp.add_route(route)
        self.assertTrue(new_best)
        self.assertFalse(cur_best)

        peer = self.external_peers[1]
        route = peer.rcv_announce(self.prefix, peer.peer_ip, [2,1], 1)
        new_best, cur_best = self.bgp.add_route(route)
        self.assertFalse(new_best)
        self.assertTrue(cur_best)

    def test_bgp_best_path_local_pref(self):
        pref = 100
        for peer in self.internal_peers:
            route = peer.rcv_announce(self.prefix, peer.peer_ip, [6500, 1], 1, local_pref=pref)
            new_best, cur_best = self.bgp.add_route(route)
            self.assertTrue(new_best)
            self.assertTrue(new_best.local_pref == pref)
            pref += 1
Esempio n. 6
0
class TestBGP(unittest.TestCase):
    def setUp(self):
        peers = {}
        borders = {}
        for peerip, peeras in [('10.0.1.1', 6510), ('10.0.2.2', 4122),
                               ('10.10.10.1', 65000)]:
            peerip = ipaddress.ip_address(peerip)
            peers[peerip] = BgpPeer(peeras, peerip, 65000)
            peers[peerip].bgp_session_up()
        self.bgp = BgpRouter(borders, peers, self.smoke_path_change_handler)
        self.peer = peers[ipaddress.ip_address('10.0.1.1')]

    def smoke_path_change_handler(self, peer, route, is_withdraw=False):
        pass

    def tearDown(self):
        pass

    def test_rcv_route(self):
        as_path = [1, 2, 3, 4]
        for i, prefix in enumerate(['1.0.0.0/20', '120.0.0.0/20']):
            as_path = as_path[:len(as_path) - i]
            recv_route = self.peer.rcv_announce(prefix,
                                                '10.0.1.1',
                                                as_path=as_path)
            best_route, _ = self.bgp.add_route(recv_route)
            self.assertEqual(recv_route.as_path, best_route.as_path)

        recv_route2 = self.peer.rcv_announce('1.0.0.0/20',
                                             '10.0.2.2',
                                             as_path=[1, 2, 3, 4])
        # this should make no best path change
        best_route, _ = self.bgp.add_route(recv_route2)
        self.assertTrue(best_route is None)
        self.assertEqual(len(self.bgp.best_routes), 2)
        self.assertEqual(len(self.bgp.loc_rib['1.0.0.0/20']), 2)
        self.assertEqual(len(self.bgp.loc_rib['120.0.0.0/20']), 1)

        route = self.peer.rcv_withdraw('1.0.0.0/20')

    def send_update(self, announce=None, withdraw=None, attribute=None):
        if announce is None:
            announce = {}
        if withdraw is None:
            withdraw = {}
        if attribute is None:
            attribute = {}
        return self.bgp.process_update(self.peerip, {
            'announce': announce,
            'withdraw': withdraw,
            'attribute': attribute
        })

    def send_announce(self, announce=None, attribute=None):
        if announce is None:
            announce = {
                'ipv4 unicast': {
                    '10.0.1.1': [{
                        'nlri': '1.0.0.0/20'
                    }, {
                        'nlri': '2.0.0.0/21'
                    }]
                }
            }
        if attribute is None:
            attribute = {'local-pref': 100, 'as-path': [1, 2, 3]}
        return self.send_update(announce=announce, attribute=attribute)

    def send_withdraw(self, withdraw=None):
        if withdraw is None:
            withdraw = {
                'ipv4 unicast': [{
                    'nlri': '1.0.0.0/20'
                }, {
                    'nlri': '2.0.0.0/21'
                }]
            }
        return self.send_update(withdraw=withdraw)