Created
June 13, 2018 19:15
-
-
Save apwiggins/747a9a14feff2ce3a3b8760406b825a7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# CORE | |
# Copyright (c)2010-2012 the Boeing Company. | |
# See the LICENSE file included in this distribution. | |
# | |
# author: Jeff Ahrenholz <[email protected]> | |
# | |
''' | |
frr.py: defines routing services provided by FRR. | |
''' | |
import os | |
if os.uname()[0] == "Linux": | |
from core.netns import nodes | |
elif os.uname()[0] == "FreeBSD": | |
from core.bsd import nodes | |
from core.service import CoreService, addservice | |
from core.misc.ipaddr import IPv4Prefix, isIPv4Address, isIPv6Address | |
from core.api import coreapi | |
from core.constants import * | |
FRR_USER="root" | |
FRR_GROUP="root" | |
FRR_STATE_DIR="/var/run/frr" | |
if os.uname()[0] == "FreeBSD": | |
FRR_GROUP="wheel" | |
class Zebra(CoreService): | |
''' | |
''' | |
_name = "zebra" | |
_group = "FRR" | |
_depends = ("vtysh", ) | |
_dirs = ("/usr/local/etc/frr", "/var/run/frr") | |
_configs = ("/usr/local/etc/frr/frr.conf", | |
"frrboot.sh","/usr/local/etc/frr/vtysh.conf") | |
_startindex = 35 | |
_startup = ("sh frrboot.sh zebra",) | |
_shutdown = ("killall zebra", ) | |
_validate = ("pidof zebra", ) | |
@classmethod | |
def generateconfig(cls, node, filename, services): | |
''' Return the frr.conf or frrboot.sh file contents. | |
''' | |
if filename == cls._configs[0]: | |
return cls.generateFrrConf(node, services) | |
elif filename == cls._configs[1]: | |
return cls.generateFrrBoot(node, services) | |
elif filename == cls._configs[2]: | |
return cls.generateVtyshConf(node, services) | |
else: | |
raise ValueError | |
@classmethod | |
def generateVtyshConf(cls, node, services): | |
''' Returns configuration file text. | |
''' | |
return "service integrated-vtysh-config" | |
@classmethod | |
def generateFrrConf(cls, node, services): | |
''' Returns configuration file text. Other services that depend on zebra | |
will have generatefrrifcconfig() and generatefrrconfig() | |
hooks that are invoked here. | |
''' | |
# we could verify here that filename == frr.conf | |
cfg = "" | |
for ifc in node.netifs(): | |
cfg += "interface %s\n" % ifc.name | |
# include control interfaces in addressing but not routing daemons | |
if hasattr(ifc, 'control') and ifc.control == True: | |
cfg += " " | |
cfg += "\n ".join(map(cls.addrstr, ifc.addrlist)) | |
cfg += "\n" | |
continue | |
cfgv4 = "" | |
cfgv6 = "" | |
want_ipv4 = False | |
want_ipv6 = False | |
for s in services: | |
if cls._name not in s._depends: | |
continue | |
ifccfg = s.generatefrrifcconfig(node, ifc) | |
if s._ipv4_routing: | |
want_ipv4 = True | |
if s._ipv6_routing: | |
want_ipv6 = True | |
cfgv6 += ifccfg | |
else: | |
cfgv4 += ifccfg | |
if want_ipv4: | |
ipv4list = filter(lambda x: isIPv4Address(x.split('/')[0]), | |
ifc.addrlist) | |
cfg += " " | |
cfg += "\n ".join(map(cls.addrstr, ipv4list)) | |
cfg += "\n" | |
cfg += cfgv4 | |
if want_ipv6: | |
ipv6list = filter(lambda x: isIPv6Address(x.split('/')[0]), | |
ifc.addrlist) | |
cfg += " " | |
cfg += "\n ".join(map(cls.addrstr, ipv6list)) | |
cfg += "\n" | |
cfg += cfgv6 | |
cfg += "!\n" | |
for s in services: | |
if cls._name not in s._depends: | |
continue | |
cfg += s.generatefrrconfig(node) | |
return cfg | |
@staticmethod | |
def addrstr(x): | |
''' helper for mapping IP addresses to zebra config statements | |
''' | |
if x.find(".") >= 0: | |
return "ip address %s" % x | |
elif x.find(":") >= 0: | |
return "ipv6 address %s" % x | |
else: | |
raise Value, "invalid address: %s", x | |
@classmethod | |
def generateFrrBoot(cls, node, services): | |
''' Generate a shell script used to boot the FRR daemons. | |
''' | |
try: | |
frr_bin_search = node.session.cfg['frr_bin_search'] | |
frr_sbin_search = node.session.cfg['frr_sbin_search'] | |
except KeyError: | |
frr_bin_search = '"/usr/local/bin /usr/bin /usr/lib/frr"' | |
frr_sbin_search = '"/usr/local/sbin /usr/sbin /usr/lib/frr"' | |
return """\ | |
#!/bin/sh | |
# auto-generated by zebra service (frr.py) | |
FRR_CONF=%s | |
FRR_SBIN_SEARCH=%s | |
FRR_BIN_SEARCH=%s | |
FRR_STATE_DIR=%s | |
FRR_USER=%s | |
FRR_GROUP=%s | |
searchforprog() | |
{ | |
prog=$1 | |
searchpath=$@ | |
ret= | |
for p in $searchpath; do | |
if [ -x $p/$prog ]; then | |
ret=$p | |
break | |
fi | |
done | |
echo $ret | |
} | |
confcheck() | |
{ | |
CONF_DIR=`dirname $FRR_CONF` | |
# if /etc/frr exists, point /etc/frr/frr.conf -> CONF_DIR | |
if [ "$CONF_DIR" != "/etc/frr" ] && [ -d /etc/frr ] && [ ! -e /etc/frr/frr.conf ]; then | |
ln -s $CONF_DIR/frr.conf /etc/frr/frr.conf | |
fi | |
# if /etc/frr exists, point /etc/frr/vtysh.conf -> CONF_DIR | |
if [ "$CONF_DIR" != "/etc/frr" ] && [ -d /etc/frr ] && [ ! -e /etc/frr/vtysh.conf ]; then | |
ln -s $CONF_DIR/vtysh.conf /etc/frr/vtysh.conf | |
fi | |
} | |
waitforvtyfiles() | |
{ | |
for f in "$@"; do | |
count=1 | |
until [ -e $FRR_STATE_DIR/$f ]; do | |
if [ $count -eq 10 ]; then | |
echo "ERROR: vty file not found: $FRR_STATE_DIR/$f" >&2 | |
return 1 | |
fi | |
sleep 0.1 | |
count=$(($count + 1)) | |
done | |
done | |
} | |
bootdaemon() | |
{ | |
FRR_SBIN_DIR=$(searchforprog $1 $FRR_SBIN_SEARCH) | |
if [ "z$FRR_SBIN_DIR" = "z" ]; then | |
echo "ERROR: FRR's '$1' daemon not found in search path:" | |
echo " $FRR_SBIN_SEARCH" | |
return 1 | |
fi | |
if [ "$1" != "zebra" ]; then | |
waitforvtyfiles zebra.vty | |
fi | |
$FRR_SBIN_DIR/$1 -u $FRR_USER -g $FRR_GROUP -d | |
} | |
bootvtysh() | |
{ | |
FRR_BIN_DIR=$(searchforprog $1 $FRR_BIN_SEARCH) | |
if [ "z$FRR_BIN_DIR" = "z" ]; then | |
echo "ERROR: FRR's '$1' daemon not found in search path:" | |
echo " $FRR_SBIN_SEARCH" | |
return 1 | |
fi | |
vtyfiles="zebra.vty" | |
for r in rip ripng ospf6 ospf bgp babel; do | |
if grep -q "^router \<${r}\>" $FRR_CONF; then | |
vtyfiles="$vtyfiles ${r}d.vty" | |
fi | |
done | |
# wait for FRR daemon vty files to appear before invoking vtysh | |
waitforvtyfiles $vtyfiles | |
$FRR_BIN_DIR/vtysh -b | |
} | |
confcheck | |
if [ "x$1" = "x" ]; then | |
echo "ERROR: missing the name of the FRR daemon to boot" | |
exit 1 | |
elif [ "$1" = "vtysh" ]; then | |
bootvtysh $1 | |
else | |
bootdaemon $1 | |
fi | |
""" % (cls._configs[0], frr_sbin_search, frr_bin_search, \ | |
FRR_STATE_DIR, FRR_USER, FRR_GROUP) | |
addservice(Zebra) | |
class FRRService(CoreService): | |
''' Parent class for FRR services. Defines properties and methods | |
common to FRR's routing daemons. | |
''' | |
_name = "FRRDaemon" | |
_group = "FRR" | |
_depends = ("zebra", ) | |
_dirs = () | |
_configs = () | |
_startindex = 40 | |
_startup = () | |
_shutdown = () | |
_meta = "The config file for this service can be found in the Zebra service." | |
_ipv4_routing = False | |
_ipv6_routing = False | |
@staticmethod | |
def routerid(node): | |
''' Helper to return the first IPv4 address of a node as its router ID. | |
''' | |
for ifc in node.netifs(): | |
if hasattr(ifc, 'control') and ifc.control == True: | |
continue | |
for a in ifc.addrlist: | |
if a.find(".") >= 0: | |
return a .split('/') [0] | |
#raise ValueError, "no IPv4 address found for router ID" | |
return "0.0.0.0" | |
@staticmethod | |
def rj45check(ifc): | |
''' Helper to detect whether interface is connected an external RJ45 | |
link. | |
''' | |
if ifc.net: | |
for peerifc in ifc.net.netifs(): | |
if peerifc == ifc: | |
continue | |
if isinstance(peerifc, nodes.RJ45Node): | |
return True | |
return False | |
@classmethod | |
def generateconfig(cls, node, filename, services): | |
return "" | |
@classmethod | |
def generatefrrifcconfig(cls, node, ifc): | |
return "" | |
@classmethod | |
def generatefrrconfig(cls, node): | |
return "" | |
class Ospfv2(FRRService): | |
''' The OSPFv2 service provides IPv4 routing for wired networks. It does | |
not build its own configuration file but has hooks for adding to the | |
unified frr.conf file. | |
''' | |
_name = "OSPFv2" | |
_startup = ("sh frrboot.sh ospfd",) | |
_shutdown = ("killall ospfd", ) | |
_validate = ("pidof ospfd", ) | |
_ipv4_routing = True | |
@staticmethod | |
def mtucheck(ifc): | |
''' Helper to detect MTU mismatch and add the appropriate OSPF | |
mtu-ignore command. This is needed when e.g. a node is linked via a | |
GreTap device. | |
''' | |
if ifc.mtu != 1500: | |
# a workaround for PhysicalNode GreTap, which has no knowledge of | |
# the other nodes/nets | |
return " ip ospf mtu-ignore\n" | |
if not ifc.net: | |
return "" | |
for i in ifc.net.netifs(): | |
if i.mtu != ifc.mtu: | |
return " ip ospf mtu-ignore\n" | |
return "" | |
@staticmethod | |
def ptpcheck(ifc): | |
''' Helper to detect whether interface is connected to a notional | |
point-to-point link. | |
''' | |
if isinstance(ifc.net, nodes.PtpNet): | |
return " ip ospf network point-to-point\n" | |
return "" | |
@classmethod | |
def generatefrrconfig(cls, node): | |
cfg = "router ospf\n" | |
rtrid = cls.routerid(node) | |
cfg += " router-id %s\n" % rtrid | |
# network 10.0.0.0/24 area 0 | |
for ifc in node.netifs(): | |
if hasattr(ifc, 'control') and ifc.control == True: | |
continue | |
for a in ifc.addrlist: | |
if a.find(".") < 0: | |
continue | |
net = IPv4Prefix(a) | |
cfg += " network %s area 0\n" % net | |
cfg += "!\n" | |
return cfg | |
@classmethod | |
def generatefrrifcconfig(cls, node, ifc): | |
return cls.mtucheck(ifc) | |
#cfg = cls.mtucheck(ifc) | |
# external RJ45 connections will use default OSPF timers | |
#if cls.rj45check(ifc): | |
# return cfg | |
#cfg += cls.ptpcheck(ifc) | |
#return cfg + """\ | |
# ip ospf hello-interval 2 | |
# ip ospf dead-interval 6 | |
# ip ospf retransmit-interval 5 | |
#""" | |
addservice(Ospfv2) | |
class Ospfv3(FRRService): | |
''' The OSPFv3 service provides IPv6 routing for wired networks. It does | |
not build its own configuration file but has hooks for adding to the | |
unified frr.conf file. | |
''' | |
_name = "OSPFv3" | |
_startup = ("sh frrboot.sh ospf6d",) | |
_shutdown = ("killall ospf6d", ) | |
_validate = ("pidof ospf6d", ) | |
_ipv4_routing = True | |
_ipv6_routing = True | |
@staticmethod | |
def minmtu(ifc): | |
''' Helper to discover the minimum MTU of interfaces linked with the | |
given interface. | |
''' | |
mtu = ifc.mtu | |
if not ifc.net: | |
return mtu | |
for i in ifc.net.netifs(): | |
if i.mtu < mtu: | |
mtu = i.mtu | |
return mtu | |
@classmethod | |
def mtucheck(cls, ifc): | |
''' Helper to detect MTU mismatch and add the appropriate OSPFv3 | |
ifmtu command. This is needed when e.g. a node is linked via a | |
GreTap device. | |
''' | |
minmtu = cls.minmtu(ifc) | |
if minmtu < ifc.mtu: | |
return " ipv6 ospf6 ifmtu %d\n" % minmtu | |
else: | |
return "" | |
@staticmethod | |
def ptpcheck(ifc): | |
''' Helper to detect whether interface is connected to a notional | |
point-to-point link. | |
''' | |
if isinstance(ifc.net, nodes.PtpNet): | |
return " ipv6 ospf6 network point-to-point\n" | |
return "" | |
@classmethod | |
def generatefrrconfig(cls, node): | |
cfg = "router ospf6\n" | |
rtrid = cls.routerid(node) | |
cfg += " router-id %s\n" % rtrid | |
for ifc in node.netifs(): | |
if hasattr(ifc, 'control') and ifc.control == True: | |
continue | |
cfg += " interface %s area 0.0.0.0\n" % ifc.name | |
cfg += "!\n" | |
return cfg | |
@classmethod | |
def generatefrrifcconfig(cls, node, ifc): | |
return cls.mtucheck(ifc) | |
#cfg = cls.mtucheck(ifc) | |
# external RJ45 connections will use default OSPF timers | |
#if cls.rj45check(ifc): | |
# return cfg | |
#cfg += cls.ptpcheck(ifc) | |
#return cfg + """\ | |
# ipv6 ospf6 hello-interval 2 | |
# ipv6 ospf6 dead-interval 6 | |
# ipv6 ospf6 retransmit-interval 5 | |
#""" | |
addservice(Ospfv3) | |
class Ospfv3mdr(Ospfv3): | |
''' The OSPFv3 MANET Designated Router (MDR) service provides IPv6 | |
routing for wireless networks. It does not build its own | |
configuration file but has hooks for adding to the | |
unified frr.conf file. | |
''' | |
_name = "OSPFv3MDR" | |
_ipv4_routing = True | |
@classmethod | |
def generatefrrifcconfig(cls, node, ifc): | |
cfg = cls.mtucheck(ifc) | |
cfg += " ipv6 ospf6 instance-id 65\n" | |
if ifc.net is not None and \ | |
isinstance(ifc.net, (nodes.WlanNode, nodes.EmaneNode)): | |
return cfg + """\ | |
ipv6 ospf6 hello-interval 2 | |
ipv6 ospf6 dead-interval 6 | |
ipv6 ospf6 retransmit-interval 5 | |
ipv6 ospf6 network manet-designated-router | |
ipv6 ospf6 diffhellos | |
ipv6 ospf6 adjacencyconnectivity uniconnected | |
ipv6 ospf6 lsafullness mincostlsa | |
""" | |
else: | |
return cfg | |
addservice(Ospfv3mdr) | |
class Bgp(FRRService): | |
'''' The BGP service provides interdomain routing. | |
Peers must be manually configured, with a full mesh for those | |
having the same AS number. | |
''' | |
_name = "BGP" | |
_startup = ("sh frrboot.sh bgpd",) | |
_shutdown = ("killall bgpd", ) | |
_validate = ("pidof bgpd", ) | |
_custom_needed = True | |
_ipv4_routing = True | |
_ipv6_routing = True | |
@classmethod | |
def generatefrrconfig(cls, node): | |
cfg = "!\n! BGP configuration\n!\n" | |
cfg += "! You should configure the AS number below,\n" | |
cfg += "! along with this router's peers.\n!\n" | |
cfg += "router bgp %s\n" % node.objid | |
rtrid = cls.routerid(node) | |
cfg += " bgp router-id %s\n" % rtrid | |
cfg += " redistribute connected\n" | |
cfg += "! neighbor 1.2.3.4 remote-as 555\n!\n" | |
return cfg | |
addservice(Bgp) | |
class Rip(FRRService): | |
''' The RIP service provides IPv4 routing for wired networks. | |
''' | |
_name = "RIP" | |
_startup = ("sh frrboot.sh ripd",) | |
_shutdown = ("killall ripd", ) | |
_validate = ("pidof ripd", ) | |
_ipv4_routing = True | |
@classmethod | |
def generatefrrconfig(cls, node): | |
cfg = """\ | |
router rip | |
redistribute static | |
redistribute connected | |
redistribute ospf | |
network 0.0.0.0/0 | |
! | |
""" | |
return cfg | |
addservice(Rip) | |
class Ripng(FRRService): | |
''' The RIP NG service provides IPv6 routing for wired networks. | |
''' | |
_name = "RIPNG" | |
_startup = ("sh frrboot.sh ripngd",) | |
_shutdown = ("killall ripngd", ) | |
_validate = ("pidof ripngd", ) | |
_ipv6_routing = True | |
@classmethod | |
def generatefrrconfig(cls, node): | |
cfg = """\ | |
router ripng | |
redistribute static | |
redistribute connected | |
redistribute ospf6 | |
network ::/0 | |
! | |
""" | |
return cfg | |
addservice(Ripng) | |
class Babel(FRRService): | |
''' The Babel service provides a loop-avoiding distance-vector routing | |
protocol for IPv6 and IPv4 with fast convergence properties. | |
''' | |
_name = "Babel" | |
_startup = ("sh frrboot.sh babeld",) | |
_shutdown = ("killall babeld", ) | |
_validate = ("pidof babeld", ) | |
_ipv6_routing = True | |
@classmethod | |
def generatefrrconfig(cls, node): | |
cfg = "router babel\n" | |
for ifc in node.netifs(): | |
if hasattr(ifc, 'control') and ifc.control == True: | |
continue | |
cfg += " network %s\n" % ifc.name | |
cfg += " redistribute static\n redistribute connected\n" | |
return cfg | |
@classmethod | |
def generatefrrifcconfig(cls, node, ifc): | |
type = "wired" | |
if ifc.net and ifc.net.linktype == coreapi.CORE_LINK_WIRELESS: | |
return " babel wireless\n no babel split-horizon\n" | |
else: | |
return " babel wired\n babel split-horizon\n" | |
addservice(Babel) | |
class Vtysh(CoreService): | |
''' Simple service to run vtysh -b (boot) after all FRR daemons have | |
started. | |
''' | |
_name = "vtysh" | |
_group = "FRR" | |
_startindex = 45 | |
_startup = ("sh frrboot.sh vtysh",) | |
_shutdown = () | |
@classmethod | |
def generateconfig(cls, node, filename, services): | |
return "" | |
addservice(Vtysh) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment