├── scripts ├── var │ └── README.md ├── gobgp_api_gen.sh ├── tegadb ├── protoc_nlan.sh ├── ssh.sh ├── gobgp.sh └── README.md ├── doc ├── jupyter │ ├── output_1_0.png │ ├── output_2_0.png │ ├── output_3_0.png │ ├── topo.md │ ├── server_client.md │ └── BGP_route_graph.md ├── misc │ ├── etcd_test.go │ ├── etcd.go │ └── 20160117.nlan.yang ├── COORDIANTION.md ├── FINDINGS.md ├── RPI.md └── GOBGP.md ├── docker ├── build.sh ├── restart.sh ├── restart_rpi.sh ├── Dockerfile ├── Dockerfile_rpi ├── docker_mng.py ├── README.md └── SETUP.md ├── util ├── tega_test.go ├── netstat.go ├── zfill.go ├── quagga.go ├── netlink.go ├── cmd.go ├── ovsdb.go └── tega.go ├── env └── const.go ├── agent ├── context │ └── con.go ├── README.md ├── config │ ├── interfaces │ │ └── interfaces.go │ ├── ptn │ │ ├── README.md │ │ ├── links.go │ │ ├── ptn.go │ │ ├── l2vpn.go │ │ └── nodes.go │ ├── vhosts │ │ └── vhosts.go │ └── router │ │ └── router.go └── main.go ├── .gitignore ├── setup.sh ├── setup_rpi.sh ├── plugins ├── README.md └── nlan │ └── plugins │ ├── workflows.py │ ├── ipam.py │ ├── deploy.py │ ├── hook.py │ ├── server_client.py │ ├── template.py │ ├── subnets.py │ └── topo.py ├── etc ├── gobgpd.conf ├── plugins │ ├── fabric2.py │ ├── fabric.py │ └── ptnbgp.py ├── ptn-ospf.yaml └── ptn-bgp.yaml ├── model └── nlan │ ├── nlan.proto │ └── nlan.pb.go ├── README.md └── ipynb └── topo.ipynb /scripts/var/README.md: -------------------------------------------------------------------------------- 1 | Directory for commit-log files 2 | -------------------------------------------------------------------------------- /doc/jupyter/output_1_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/araobp/nlan/HEAD/doc/jupyter/output_1_0.png -------------------------------------------------------------------------------- /doc/jupyter/output_2_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/araobp/nlan/HEAD/doc/jupyter/output_2_0.png -------------------------------------------------------------------------------- /doc/jupyter/output_3_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/araobp/nlan/HEAD/doc/jupyter/output_3_0.png -------------------------------------------------------------------------------- /docker/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cp `which agent` . 4 | docker build -f $1 -t nlan/agent:ver0.1 . 5 | 6 | -------------------------------------------------------------------------------- /scripts/gobgp_api_gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $GOPATH/src/github.com/osrg/gobgp 4 | 5 | protoc -I ./api ./api/gobgp.proto --go_out=plugins=grpc:./api 6 | -------------------------------------------------------------------------------- /scripts/tegadb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | tega-server -d ./var -t global -e $GOPATH/src/github.com/araobp/nlan/plugins/nlan $GOPATH/src/github.com/araobp/nlan/etc 4 | -------------------------------------------------------------------------------- /scripts/protoc_nlan.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MODELDIR=$GOPATH/src/github.com/araobp/nlan/model/nlan 4 | protoc -I $MODELDIR $MODELDIR/nlan.proto --go_out=plugins=grpc:$MODELDIR 5 | -------------------------------------------------------------------------------- /scripts/ssh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | HOST="localhost" 4 | PORT="8739" 5 | 6 | ip=`curl http://$HOST:$PORT/hosts/$1?tega_id=ssh.sh | tr -d \" | cut -d '/' -f1` 7 | ssh root@$ip 8 | 9 | -------------------------------------------------------------------------------- /docker/restart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | go install github.com/araobp/nlan/agent 4 | ./docker_mng.py stop $@ 5 | ./docker_mng.py rm $@ 6 | ./build.sh Dockerfile 7 | ./docker_mng.py run $@ 8 | -------------------------------------------------------------------------------- /docker/restart_rpi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | go install github.com/araobp/nlan/agent 4 | ./docker_mng.py stop $@ 5 | ./docker_mng.py rm $@ 6 | ./build.sh Dockerfile_rpi 7 | ./docker_mng.py run $@ 8 | -------------------------------------------------------------------------------- /util/tega_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func TestOnInit(t *testing.T) { 9 | hosts := ListHosts() 10 | log.Printf("%v", hosts) 11 | } 12 | -------------------------------------------------------------------------------- /scripts/gobgp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | HOST=localhost 4 | PORT=8739 5 | ROUTER=rr 6 | 7 | ip=`curl http://$HOST:$PORT/hosts/$ROUTER?tega_id=gobgp.sh | tr -d \" | cut -d '/' -f1` 8 | gobgp -u $ip $@ 9 | 10 | -------------------------------------------------------------------------------- /env/const.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | const PORT = ":8282" 4 | 5 | const ( 6 | ADD = iota 7 | UPDATE 8 | DELETE 9 | CLEAR 10 | ) 11 | 12 | const ( 13 | DVR = iota 14 | PTN 15 | VHOSTS 16 | ROUTER 17 | ) 18 | -------------------------------------------------------------------------------- /agent/context/con.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | type Context struct { 4 | Cmd func(string, ...string) error 5 | CmdP func(string, ...string) error 6 | } 7 | 8 | func (c *Context) GetCmd() (func(string, ...string) error, func(string, ...string) error) { 9 | return c.Cmd, c.CmdP 10 | } 11 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | #Tega 2 | 3 | ##Installation 4 | ``` 5 | $ go get github.com/araobp/tega/driver 6 | $ cd $GOPATH/src/github.com/araobp/tega 7 | $ python3.5 setup.py install 8 | ``` 9 | 10 | ##Starting tega db 11 | ``` 12 | $ ./tegadb 13 | ``` 14 | 15 | ##Using tega cli 16 | ``` 17 | $ tega-cli 18 | ``` 19 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile to create NLAN agent image 2 | # 3 | # Original image "router" have already openvswitch 4 | # preinstalled. 5 | 6 | FROM router 7 | ENV TEGA_ADDRESS 172.17.42.1 8 | ENV NO_PROXY 172.17.42.1,localhost 9 | ADD agent /root/bin/agent 10 | CMD service openvswitch-switch start && service quagga start && service ssh start && /root/bin/agent 11 | 12 | -------------------------------------------------------------------------------- /docker/Dockerfile_rpi: -------------------------------------------------------------------------------- 1 | # Dockerfile to create NLAN agent image 2 | # 3 | # Original image "router" have already openvswitch 4 | # preinstalled. 5 | 6 | FROM router 7 | ENV TEGA_ADDRESS 172.17.0.1 8 | ENV NO_PROXY 172.17.0.1,localhost 9 | ADD agent /root/bin/agent 10 | CMD service openvswitch-switch start && service quagga start && service ssh start && /root/bin/agent 11 | 12 | -------------------------------------------------------------------------------- /util/netstat.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | netstat "github.com/drael/GOnetstat" 5 | ) 6 | 7 | const ( 8 | TCP = "TCP" 9 | UDP = "UDP" 10 | ) 11 | 12 | func Netstat(t string) *[]netstat.Process { 13 | var data []netstat.Process 14 | switch t { 15 | case TCP: 16 | data = netstat.Tcp() 17 | case UDP: 18 | data = netstat.Udp() 19 | } 20 | return &data 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /doc/jupyter/topo.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```python 4 | import networkx as nx 5 | %matplotlib inline 6 | ``` 7 | 8 | 9 | ```python 10 | import tega.driver 11 | d = tega.driver.Driver() 12 | edges = d.get('topo.edges') 13 | g = nx.Graph([[edge['source'], edge['target']] for edge in edges]) 14 | nx.draw(g, node_size=700, with_labels=True) 15 | ``` 16 | 17 | 18 | ![png](output_1_0.png) 19 | 20 | -------------------------------------------------------------------------------- /agent/README.md: -------------------------------------------------------------------------------- 1 | #NLAN Agent 2 | 3 | NLAN agent runs on each Linxu container. 4 | 5 | ##Persistency 6 | 7 | |command | persistent | database |util| 8 | |---------|------------|-----------|----| 9 | |ip | N | |cmd | 10 | |brctl | N | |cmd | 11 | |ovs-vsctl| Y | ovsdb |cmdp| 12 | |ovs-ofctl| N | |cmd | 13 | |vtysh | Y |/etc/quagga|cmdp| 14 | 15 | 16 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | HOMEDIR=$GOPATH/src/github.com/araobp/nlan 4 | 5 | echo "Compiling tega driver..." 6 | go get github.com/araobp/tega/driver 7 | 8 | echo "Compiling NLAN model..." 9 | MODELDIR=$HOMEDIR/model/nlan 10 | protoc -I $MODELDIR $MODELDIR/nlan.proto --go_out=plugins=grpc:$MODELDIR 11 | 12 | echo "Building containers with NLAN agent embedded..." 13 | cd docker 14 | ./restart.sh $@ 15 | cd $HOMEDIR 16 | 17 | echo "Done!" 18 | echo "" 19 | -------------------------------------------------------------------------------- /setup_rpi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | HOMEDIR=$GOPATH/src/github.com/araobp/nlan 4 | 5 | echo "Compiling tega driver..." 6 | go get github.com/araobp/tega/driver 7 | 8 | echo "Compiling NLAN model..." 9 | MODELDIR=$HOMEDIR/model/nlan 10 | protoc -I $MODELDIR $MODELDIR/nlan.proto --go_out=plugins=grpc:$MODELDIR 11 | 12 | echo "Building containers with NLAN agent embedded..." 13 | cd docker 14 | ./restart_rpi.sh $@ 15 | cd $HOMEDIR 16 | 17 | echo "Done!" 18 | echo "" 19 | -------------------------------------------------------------------------------- /doc/jupyter/server_client.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```python 4 | import networkx as nx 5 | get_ipython().magic('matplotlib inline') 6 | ``` 7 | 8 | 9 | ```python 10 | import tega.driver 11 | d = tega.driver.Driver(host='192.168.57.133') 12 | server_client = d.get(path='graph.server_client.8888') 13 | ``` 14 | 15 | 16 | ```python 17 | g = nx.DiGraph(server_client) 18 | nx.draw_spring(g, node_size=1000, with_labels=True, arrows=True, alpha=0.8) 19 | ``` 20 | 21 | 22 | ![png](output_3_0.png) 23 | 24 | -------------------------------------------------------------------------------- /util/zfill.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Python zfill-like function 8 | func Zfill(s string, pad string, overall int) string { 9 | l := overall - len(s) 10 | return strings.Repeat(pad, l) + s 11 | } 12 | 13 | // Converts IP address into zero-padded string. 14 | // For example, "10.1.2.3" => "010001002003" 15 | func ZfillIp(ip string) string { 16 | splited := strings.Split(ip, ".") 17 | for i, s := range splited { 18 | splited[i] = Zfill(s, "0", 3) 19 | } 20 | return strings.Join(splited, "") 21 | } 22 | -------------------------------------------------------------------------------- /agent/config/interfaces/interfaces.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "github.com/araobp/nlan/agent/context" 5 | "github.com/araobp/nlan/model/nlan" 6 | 7 | "log" 8 | ) 9 | 10 | func Crud(crud int, in map[string]*nlan.Interface, con *context.Context) { 11 | // TODO: CRUD operatoins 12 | cmd, _ := con.GetCmd() 13 | for dev, params := range in { // map key is not used. 14 | log.Println(dev) 15 | cmd("ip", "tunnel", "add", dev, "mode", params.Mode, "local", params.Local, "remote", params.Remote) 16 | cmd("ip", "addr", "add", params.Address, "dev", dev) 17 | cmd("ip", "link", "set", "dev", dev, "up") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /agent/config/ptn/README.md: -------------------------------------------------------------------------------- 1 | ##Do I have to use OpenFlow controller for NLAN? 2 | 3 | I would say, "No", because all the OF flows are rather static and do not require dynamic control. 4 | 5 | OF flows are ephemeral, but I would rather want a capability to add persistent flows that survives over reboots. 6 | 7 | In case of NLAN, all the flows will be resumed by referring to NLAN state on a local database. neutron-lan used ovsdb as a general-purpose local database, but nlan may use another data base (e.g., tega) or a flat file. I have not decided yet. 8 | 9 | See also this: http://openvswitch.org/pipermail/dev/2015-January/050380.html 10 | 11 | -------------------------------------------------------------------------------- /util/quagga.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "strings" 4 | 5 | const C = "-c" // vtysh -c option 6 | 7 | var CONF_T = "configure terminal" 8 | var END = "end" 9 | var WRITE_FILE = "write file" 10 | 11 | func appendScript(args *[]string, arg string) { 12 | *args = append(*args, C) 13 | *args = append(*args, arg) 14 | } 15 | 16 | func VtyshBatch(script [][]string) []string { 17 | var args []string 18 | if len(script) > 0 { 19 | appendScript(&args, CONF_T) 20 | for _, arg := range script { 21 | appendScript(&args, strings.Join(arg, " ")) 22 | } 23 | appendScript(&args, END) 24 | appendScript(&args, WRITE_FILE) 25 | } 26 | return args 27 | } 28 | -------------------------------------------------------------------------------- /doc/misc/etcd_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | 7 | "github.com/araobp/nlan/model/nlan" 8 | ) 9 | 10 | func TestRegisterAndListState(t *testing.T) { 11 | RegisterHost() 12 | hosts := ListHosts(true) 13 | log.Printf("%v", hosts) 14 | hosts = ListHosts(false) 15 | log.Printf("%v", hosts) 16 | } 17 | 18 | func TestSetAndGetState(t *testing.T) { 19 | props := nlan.VhostProps{ 20 | Network: "10.10.10.10/24", 21 | Vhosts: 2, 22 | } 23 | vhosts := nlan.Vhosts{ 24 | VhostProps: []*nlan.VhostProps{&props}, 25 | } 26 | SetState("rrr", &vhosts) 27 | pb := new(nlan.Vhosts) 28 | GetState("rrr", pb) 29 | log.Print(pb) 30 | log.Print(pb.VhostProps) 31 | } 32 | 33 | func TestReset(t *testing.T) { 34 | ResetState() 35 | } 36 | -------------------------------------------------------------------------------- /doc/jupyter/BGP_route_graph.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ```python 4 | import networkx as nx 5 | from IPython.display import display 6 | from ipywidgets import Dropdown 7 | get_ipython().magic('matplotlib inline') 8 | ``` 9 | 10 | 11 | ```python 12 | import tega.driver 13 | d = tega.driver.Driver(host='192.168.57.133') 14 | d.rpc('plugins.hook') 15 | d.rpc('plugins.subnets') 16 | subnets = d.get(path='graph.subnets') 17 | ``` 18 | 19 | 20 | ```python 21 | def on_value_change(name, value): 22 | g = nx.DiGraph(subnets[value]) 23 | nx.draw_spring(g, node_size=1000, with_labels=True, arrows=True, alpha=0.8) 24 | ``` 25 | 26 | 27 | ```python 28 | subnet_keys = [k for k in subnets.keys() if k.startswith('172.')] 29 | subnet_keys.insert(0, '---subnet---') 30 | dw = Dropdown(description='subnets', options=subnet_keys) 31 | display(dw) 32 | dw.on_trait_change(on_value_change, 'value') 33 | ``` 34 | 35 | 36 | ![png](output_2_0.png) 37 | 38 | -------------------------------------------------------------------------------- /plugins/README.md: -------------------------------------------------------------------------------- 1 | ##Tega plugins for NLAN 2 | 3 | ###IP Address Management 4 | ``` 5 | Function Name: nlan.ipam 6 | Arguments: ip, *routers 7 | Usage example: 8 | [tega: 1] nlan.ipam('10.10.10.1','pe1','pe2','pe3','pe4','rr','ce1','ce2','ce3','ce4') 9 | [tega: 2] get nlan.ip 10 | {ce1: 10.10.10.6/24, ce2: 10.10.10.7/24, ce3: 10.10.10.8/24, ce4: 10.10.10.9/24, pe1: 10.10.10.1/24, 11 | pe2: 10.10.10.2/24, pe3: 10.10.10.3/24, pe4: 10.10.10.4/24, rr: 10.10.10.5/24} 12 | 13 | ``` 14 | ###Template 15 | ``` 16 | Function Name: nlan.template 17 | Arguments: filename 18 | Usage example: 19 | [tega: 1] nlan.template('ptn-bgp.yaml') 20 | ``` 21 | 22 | ###Topo (topology processing) 23 | This plugin subscribes nlan.state and converts nlan.state into network graph (vertexes/edges). 24 | ``` 25 | 'nlan.state' data change notification --> transformation -> vertexes/edges 26 | ``` 27 | 28 | ###Deploy (deployment of NLAN services) 29 | ``` 30 | Function Name: nlan.deploy 31 | Arguments: (None) 32 | Usage example: 33 | [tega: 1] nlan.deploy() 34 | ``` 35 | 36 | -------------------------------------------------------------------------------- /plugins/nlan/plugins/workflows.py: -------------------------------------------------------------------------------- 1 | import tega.tree 2 | import tega.subscriber 3 | 4 | class Workflow(tega.subscriber.PlugIn): 5 | ''' 6 | Manages IP addresses of Linux containers 7 | ''' 8 | 9 | def __init__(self): 10 | super().__init__() 11 | workflows = tega.tree.Cont('workflows') 12 | with self.tx() as t: 13 | workflows.ptnbgp = self.func(self._ptnbgp) 14 | workflows.ptnospf = self.func(self._ptnospf) 15 | t.put(workflows, ephemeral=True) 16 | 17 | def on_notify(self, notifications): 18 | pass 19 | 20 | def on_message(self, channel, tega_id, message): 21 | pass 22 | 23 | def _ipam(self): 24 | ipam_args = ['10.10.10.1','pe1','pe2','pe3','pe4','rr','ce1','ce2','ce3','ce4'] 25 | self.get('plugins.ipam')(*ipam_args) 26 | 27 | def _ptnbgp(self): 28 | self._ipam() 29 | self.get('plugins.template')('ptn-bgp.yaml') 30 | self.get('plugins.deploy')() 31 | 32 | def _ptnospf(self): 33 | self._ipam() 34 | self.get('plugins.template')('ptn-ospf.yaml') 35 | self.get('plugins.deploy')() 36 | -------------------------------------------------------------------------------- /docker/docker_mng.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.5 2 | 3 | import os 4 | from subprocess import run, PIPE 5 | import sys 6 | 7 | IMAGE = 'nlan/agent:ver0.1' # Docker image name 8 | 9 | def _set_symbolic_link(node): 10 | run(['mkdir', '-p', '/var/run/netns']) 11 | pid = run(['docker', 'inspect', '--format', '{{.State.Pid}}', node], stdout=PIPE, universal_newlines=True).stdout.rstrip() 12 | target = '/var/run/netns/' + node 13 | if os.path.islink(target): 14 | os.remove(target) 15 | run(['ln', '-s', '/proc/{}/ns/net'.format(pid), target]) 16 | print('pid ' + pid) 17 | 18 | def _run(node): 19 | run(['docker', 'run', '-i', '-t', '-d', '-v', '/tmp:/var/volume:rw', '--privileged', '--name', node, '--env', 'HOSTNAME='+node, IMAGE]) 20 | _set_symbolic_link(node) 21 | 22 | def _rm(node): 23 | run(['docker', 'rm', node]) 24 | 25 | def _start(node): 26 | run(['docker', 'start', node]) 27 | _set_symbolic_link(node) 28 | 29 | def _stop(node): 30 | run(['docker', 'stop', node]) 31 | 32 | if __name__ == '__main__': 33 | ope = sys.argv[1] 34 | nodes = sys.argv[2:] 35 | for node in nodes: 36 | globals()['_'+ope](node) 37 | 38 | -------------------------------------------------------------------------------- /plugins/nlan/plugins/ipam.py: -------------------------------------------------------------------------------- 1 | import tega.tree 2 | import tega.subscriber 3 | 4 | class IpAddressManagement(tega.subscriber.PlugIn): 5 | ''' 6 | Manages IP addresses of Linux containers 7 | ''' 8 | 9 | def __init__(self): 10 | super().__init__() 11 | plugins = tega.tree.Cont('plugins') 12 | with self.tx() as t: 13 | plugins.ipam = self.func(self._gen) # Attached to plugins.ipam 14 | t.put(plugins.ipam, ephemeral=True) 15 | 16 | def on_notify(self, notifications): 17 | pass 18 | 19 | def on_message(self, channel, tega_id, message): 20 | pass 21 | 22 | MASK = '24' 23 | 24 | def _gen(self, addr, *routers): 25 | ''' 26 | IP address generation 27 | ''' 28 | ip = tega.tree.Cont('ip') 29 | with self.tx() as t: 30 | abcd = addr.split('.') 31 | d = abcd[3] 32 | abc = abcd[:2] 33 | for r in routers: 34 | nextip = abcd[:3] 35 | nextip.append(d) 36 | ip[r] = '{}/{}'.format('.'.join(nextip), self.MASK) 37 | d = str(int(d) + 1) 38 | t.put(ip) 39 | 40 | -------------------------------------------------------------------------------- /agent/config/ptn/links.go: -------------------------------------------------------------------------------- 1 | package ptn 2 | 3 | import ( 4 | "github.com/araobp/nlan/agent/context" 5 | "github.com/araobp/nlan/model/nlan" 6 | "github.com/araobp/nlan/util" 7 | 8 | "log" 9 | "strconv" 10 | ) 11 | 12 | func AddLinks(links *nlan.Links, con *context.Context, brTun string, brInt string) { 13 | cmd, cmdp := con.GetCmd() 14 | localIp := links.LocalIp 15 | remoteIps := links.RemoteIps 16 | for _, remoteIp := range remoteIps { 17 | inf := util.ZfillIp(remoteIp) 18 | log.Printf("Adding a VXLAN tunnel: %s", inf) 19 | cmdp("ovs-vsctl", "add-port", brTun, inf, "--", "set", "interface", inf, "type=vxlan", "options:in_key=flow", "options:local_ip="+localIp, "options:out_key=flow", "options:remote_ip="+remoteIp) 20 | infNum := strconv.Itoa(util.GetOfport(inf)) 21 | cmd("ovs-ofctl", "add-flow", brTun, "table=0,priority=1,in_port="+infNum+",actions=resubmit(,2)") 22 | cmd("ip", "link", "set", "dev", brTun, "up") 23 | cmd("ip", "link", "set", "dev", brInt, "up") 24 | } 25 | } 26 | 27 | func UpdateLinks(links *nlan.Links, con *context.Context, brTun string, brInt string) { 28 | } 29 | 30 | func DeleteLinks(links *nlan.Links, con *context.Context, brTun string, brInt string) { 31 | } 32 | -------------------------------------------------------------------------------- /plugins/nlan/plugins/deploy.py: -------------------------------------------------------------------------------- 1 | import tega.tree 2 | import tega.subscriber 3 | 4 | import os 5 | import subprocess 6 | 7 | class Deployment(tega.subscriber.PlugIn): 8 | ''' 9 | Deployment of NLAN services 10 | ''' 11 | 12 | 13 | def __init__(self): 14 | super().__init__() 15 | try: 16 | self.script = os.environ['SETUP_SCRIPT'] 17 | except: 18 | self.script = 'setup.sh' 19 | self.scriptdir = os.path.join(os.environ['GOPATH'], 'src/github.com/araobp/nlan/') 20 | plugins = tega.tree.Cont('plugins') 21 | with self.tx() as t: 22 | plugins.deploy = self.func(self._deploy) # Attached to plugins.deploy 23 | t.put(plugins.deploy, ephemeral=True) 24 | 25 | def on_notify(self, notifications): 26 | pass 27 | 28 | def on_message(self, channel, tega_id, message): 29 | pass 30 | 31 | def _deploy(self): 32 | ''' 33 | Deployment 34 | ''' 35 | os.chdir(self.scriptdir) 36 | routers = self.get("ip").keys() 37 | args = [os.path.join(self.scriptdir, self.script)] 38 | args.extend(routers) 39 | self.process = subprocess.Popen(args, preexec_fn=os.setsid) 40 | -------------------------------------------------------------------------------- /etc/gobgpd.conf: -------------------------------------------------------------------------------- 1 | [Global] 2 | [Global.GlobalConfig] 3 | As = 100 4 | RouterId = "10.1.1.5" 5 | 6 | [Neighbors] 7 | [[Neighbors.NeighborList]] 8 | [Neighbors.NeighborList.NeighborConfig] 9 | NeighborAddress = "10.200.1.101" 10 | PeerAs = 100 11 | [Neighbors.NeighborList.RouteReflector.RouteReflectorConfig] 12 | RouteReflectorClient = true 13 | RouteReflectorClusterId = "10.1.1.5" 14 | [[Neighbors.NeighborList]] 15 | [Neighbors.NeighborList.NeighborConfig] 16 | NeighborAddress = "10.200.1.102" 17 | PeerAs = 100 18 | [Neighbors.NeighborList.RouteReflector.RouteReflectorConfig] 19 | RouteReflectorClient = true 20 | RouteReflectorClusterId = "10.1.1.5" 21 | [[Neighbors.NeighborList]] 22 | [Neighbors.NeighborList.NeighborConfig] 23 | NeighborAddress = "10.200.1.103" 24 | PeerAs = 100 25 | [Neighbors.NeighborList.RouteReflector.RouteReflectorConfig] 26 | RouteReflectorClient = true 27 | RouteReflectorClusterId = "10.1.1.5" 28 | [[Neighbors.NeighborList]] 29 | [Neighbors.NeighborList.NeighborConfig] 30 | NeighborAddress = "10.200.1.104" 31 | PeerAs = 100 32 | [Neighbors.NeighborList.RouteReflector.RouteReflectorConfig] 33 | RouteReflectorClient = true 34 | RouteReflectorClusterId = "10.1.1.5" 35 | 36 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | #Creating a Docker image for NLAN 2 | 3 | ##Open vSwitch installation 4 | 5 | ``` 6 | $wget http://openvswitch.org/releases/openvswitch-2.4.0.tar.gz 7 | ``` 8 | Follow the instructions included in the archive. 9 | 10 | NLAN requires "dkms", "common" and "switch" only. Use dpkg command (dpkg -i) to install the deb packages. 11 | 12 | ##Image creation 13 | 14 | ![working_with_docker](https://docs.google.com/drawings/d/161Bn80w8JZKQ7BXmIo0br7xQ4kqEdBc_XZ254zuORSU/pub?w=680&h=400) 15 | 16 | [Step1] Create an image of Debian/Ubuntu with Open vSwitch installed 17 | 18 | You need to copy the following deb packages to the Docker containers: 19 | - openvswitch-switch_*.deb 20 | - openvswitch-common_*.deb 21 | 22 | Then "dpkg -i" to install them. 23 | 24 | [Step2] Allow ssh root login to the Docker container 25 | ``` 26 | /etc/ssh/ssh_config 27 | 28 | #PermitRootLogin wihtout-password 29 | PermitRootLogin yes 30 | ``` 31 | 32 | #Scripts for managing docker image/containers and etcd 33 | 34 | ##Rebuilding the docker image of NLAN agent 35 | You need to rebuild nlan/agent:ver0.1 image everytime you modify github.com/araobp/go-nlan/nlan/agent. 36 | 37 | Just execute the following shell script to rebuild the image and run Linux containers from the image: 38 | ``` 39 | $ ./restart.sh 40 | ``` 41 | 42 | ##Starting etcd 43 | Make etcd bind 0.0.0.0: 44 | ``` 45 | $ ./etcd.sh 46 | ``` 47 | 48 | -------------------------------------------------------------------------------- /plugins/nlan/plugins/hook.py: -------------------------------------------------------------------------------- 1 | import tega.tree 2 | import tega.subscriber 3 | 4 | HOOK_PATH_REGEX = r'operational-(\w*)\.ip\.hook' 5 | HOOK_ROUTE_PATH = 'operational-{}.ip.hook.route' 6 | HOOK_ADDR_PATH = 'operational-{}.ip.hook.addr' 7 | 8 | HOOK_STATS_REGEX = r'stats-(\w*)\.hook' 9 | HOOK_NETSTAT_PATH = 'stats-{}.hook.netstat' 10 | 11 | class Hook(tega.subscriber.PlugIn): 12 | ''' 13 | Calls hook functions to reflesh operational trees 14 | ''' 15 | 16 | def __init__(self): 17 | super().__init__() 18 | plugins = tega.tree.Cont('plugins') 19 | with self.tx() as t: 20 | plugins.hook = self.func(self._hook) # Attached to plugins.hook 21 | t.put(plugins.hook, ephemeral=True) 22 | 23 | def on_notify(self, notifications): 24 | pass 25 | 26 | def on_message(self, channel, tega_id, message): 27 | pass 28 | 29 | def _hook(self): 30 | ''' 31 | Kicks off hook functions in a batch 32 | ''' 33 | hooks = self.get(path=HOOK_PATH_REGEX, regex_flag=True) 34 | for l in hooks: 35 | router = l[2][0][0] 36 | self.rpc(path=HOOK_ROUTE_PATH.format(router)) 37 | self.rpc(path=HOOK_ADDR_PATH.format(router)) 38 | 39 | hooks = self.get(path=HOOK_STATS_REGEX, regex_flag=True) 40 | for l in hooks: 41 | router = l[2][0][0] 42 | self.rpc(path=HOOK_NETSTAT_PATH.format(router)) 43 | -------------------------------------------------------------------------------- /docker/SETUP.md: -------------------------------------------------------------------------------- 1 | #Prerequisites 2 | 3 | ##Open vSwitch installation 4 | 5 | ``` 6 | $wget http://openvswitch.org/releases/openvswitch-2.4.0.tar.gz 7 | ``` 8 | Follow the instructions included in the archive. 9 | 10 | NLAN requires "dkms", "common" and "switch" only. Use dpkg command (dpkg -i) to install the deb packages. 11 | 12 | ##Working with Docker 13 | 14 | ![working_with_docker](https://docs.google.com/drawings/d/161Bn80w8JZKQ7BXmIo0br7xQ4kqEdBc_XZ254zuORSU/pub?w=680&h=400) 15 | 16 | [Step1] Create an image of Debian/Ubuntu with Open vSwitch installed 17 | 18 | You need to copy the following deb packages to the Docker containers: 19 | - openvswitch-switch_*.deb 20 | - openvswitch-common_*.deb 21 | 22 | Then "dpkg -i" to install them. 23 | 24 | [Step2] Allow ssh root login to the Docker container 25 | ``` 26 | /etc/ssh/ssh_config 27 | 28 | #PermitRootLogin wihtout-password 29 | PermitRootLogin yes 30 | ``` 31 | 32 | [Step3] Allow containers to run tcpdump 33 | ``` 34 | $ mv /usr/sbin/tcpdump /usr/bin/tcpdump 35 | ``` 36 | (to avoid Permission Denied error) 37 | 38 | [Step4] Install Quagga 39 | 40 | Install Quagga and enable zebra, ospfd and bgpd. 41 | 42 | Dont't forget to append the following to ~/.bashrc: 43 | ``` 44 | export VTYSH_PAGER=more 45 | ``` 46 | 47 | Also, change owner.group of /etc/quagga/*.conf 48 | ``` 49 | $ chown quagga.quagga /etc/quagga/*.conf 50 | ``` 51 | [Step5] Commit the image 52 | ``` 53 | $ docker commit image 54 | ``` 55 | 56 | -------------------------------------------------------------------------------- /util/netlink.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/vishvananda/netlink" 5 | 6 | "net" 7 | ) 8 | 9 | type Route struct { 10 | Via net.IP 11 | Dev string 12 | Src net.IP 13 | } 14 | 15 | // Returns a list of devices and addresses ("ip addr show") 16 | func AddrMap() (*map[string][]string, *map[string]string) { 17 | addrs, _ := netlink.AddrList(nil, netlink.FAMILY_V4) 18 | devMap := make(map[string][]string) 19 | addrMap := make(map[string]string) 20 | for _, a := range addrs { 21 | addr := a.IP.String() 22 | dev := a.Label 23 | l, ok := devMap[dev] 24 | if ok { 25 | devMap[dev] = append(l, addr) 26 | } else { 27 | devMap[dev] = []string{addr} 28 | } 29 | addrMap[addr] = dev 30 | } 31 | return &devMap, &addrMap 32 | } 33 | 34 | // Returns a list of routes 35 | func RouteMap() *map[string]Route { 36 | links, _ := netlink.LinkList() 37 | linksMap := make(map[int]string) 38 | for _, l := range links { 39 | attrs := *l.Attrs() 40 | linksMap[attrs.Index] = attrs.Name 41 | } 42 | 43 | routes := make(map[string]Route) 44 | routeList, _ := netlink.RouteList(nil, netlink.FAMILY_V4) 45 | for _, r := range routeList { 46 | if_ := linksMap[r.LinkIndex] 47 | rdst := r.Dst 48 | var dst string 49 | if rdst != nil { 50 | dst = rdst.String() 51 | } else { 52 | dst = "default" 53 | } 54 | 55 | route := Route{ 56 | Via: r.Gw, 57 | Dev: if_, 58 | Src: r.Src, 59 | } 60 | routes[dst] = route 61 | } 62 | return &routes 63 | } 64 | -------------------------------------------------------------------------------- /plugins/nlan/plugins/server_client.py: -------------------------------------------------------------------------------- 1 | import tega.tree 2 | import tega.subscriber 3 | 4 | PATH_REGEX = r'stats-(\w*)\.netstat' 5 | GRAPH_SERVERS_CLIENTS_PATH = 'graph.server_client.{}' 6 | 7 | class ServerClient(tega.subscriber.PlugIn): 8 | ''' 9 | Graph of servers-clients 10 | ''' 11 | 12 | def __init__(self): 13 | super().__init__() 14 | plugins = tega.tree.Cont('plugins') 15 | with self.tx() as t: 16 | plugins.server_client = self.func(self._server_client) 17 | t.put(plugins.server_client, ephemeral=True) 18 | 19 | def on_notify(self, notifications): 20 | pass 21 | 22 | def on_message(self, channel, tega_id, message): 23 | pass 24 | 25 | def _server_client(self, port): 26 | ''' 27 | Collects data from every stats tree and put graph of servers-clientss 28 | onto tega db. 29 | ''' 30 | data = self.get(PATH_REGEX, regex_flag=True) 31 | server_client = [] 32 | 33 | for v in data: 34 | router = v[2][0][0] 35 | netstat = v[1] 36 | for elm in netstat.tcp: 37 | port_ = elm['ForeignPort'] 38 | if port == port_: 39 | server_client.append((elm['Ip'], elm['ForeignIp'])) 40 | 41 | with self.tx() as t: 42 | graph = tega.idb.Cont('graph') 43 | subtree = graph.server_client 44 | subtree[str(port)] = server_client 45 | t.put(subtree[str(port)]) 46 | 47 | -------------------------------------------------------------------------------- /plugins/nlan/plugins/template.py: -------------------------------------------------------------------------------- 1 | import tega.tree 2 | import tega.subscriber 3 | from tega.util import dict2cont 4 | 5 | import mako.template 6 | import os 7 | import yaml 8 | 9 | IP_PATH = 'ip' 10 | PLUGINS_PATH = 'plugins' 11 | CONFIG_PATH = 'config-{}' 12 | 13 | class Template(tega.subscriber.PlugIn): 14 | ''' 15 | State renderer 16 | ''' 17 | def __init__(self): 18 | super().__init__() 19 | self.etcdir = os.path.join(os.environ['GOPATH'], 'src/github.com/araobp/nlan/etc/') 20 | plugins = tega.tree.Cont('plugins') 21 | with self.tx() as t: 22 | plugins.template = self.func(self._render) # Attached to nlan.template 23 | t.put(plugins.template, ephemeral=True) 24 | 25 | def on_notify(self, notifications): 26 | pass 27 | 28 | def on_message(self, channel, tega_id, message): 29 | pass 30 | 31 | def _render(self, filename): 32 | ''' 33 | NLAN state registartion with tega db 34 | ''' 35 | with open(os.path.join(self.etcdir, filename)) as f: 36 | temp = f.read() 37 | 38 | with self.tx() as t: 39 | routers = self.get(IP_PATH) 40 | r = {k: v.split('/')[0] for k, v in routers.items()} 41 | state_yaml = mako.template.Template(temp).render(**r) 42 | state = yaml.load(state_yaml) 43 | config = {CONFIG_PATH.format(r): v for r, v in state.items()} 44 | for root_oid, c in config.items(): 45 | t.put(dict2cont({root_oid: c})) 46 | 47 | -------------------------------------------------------------------------------- /doc/COORDIANTION.md: -------------------------------------------------------------------------------- 1 | # Tunnel setup sequence diagram 2 | 3 | Issue#16: https://github.com/araobp/nlan/issues/16 4 | 5 | This sequence is to set up tunnels among containers via macvlan interfaces. 6 | 7 | This uses Tega's pubsub feature for coordination among plugin.deploy and NLAN agents. 8 | 9 | ``` 10 | 11 | plugin.deploy tega CLL or Jupyter notebook 12 | | | | 13 | |<- REQ RPC ----|<-- REQ RPC -------------------------| 14 | |-------------->|-- RES RPC ------------------------->| 15 | | | 16 | |---- put ----->|(LAUNCHED) 17 | | state.c1 | 18 | | | 19 | launch containers | container w/ NLAN agent 20 | | | | 21 | netns symbolic | | 22 | link | | 23 | | | | 24 | | (STARTING)|<-- put state.c1 ------| 25 | |<-- notify ----| w/ command | 26 | | state.c1 | | 27 | | command | | 28 | macvlan setup | | 29 | | | | 30 | |---- put ----->|(CONFIG) | 31 | | |--- notify state.c1 -->| 32 | | | V 33 | | | start config 34 | | | | 35 | | | ip add tunnel tun0 mode gre ... 36 | | | | 37 | ``` 38 | -------------------------------------------------------------------------------- /util/cmd.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "log" 5 | "os/exec" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | CONFIG = iota 11 | RESTART 12 | DEBUG 13 | ) 14 | 15 | // This function executes the command and returns the output. 16 | func OutputCmd(name string, args ...string) (string, error) { 17 | out, err := exec.Command(name, args...).CombinedOutput() 18 | strout := string(out) 19 | if len(out) > 0 { 20 | log.Println(strout) 21 | } 22 | if err != nil { 23 | log.Println(err) 24 | } 25 | return strout, err 26 | } 27 | 28 | // This function executes the command. 29 | func Cmd(name string, args ...string) error { 30 | _, err := OutputCmd(name, args...) 31 | return err 32 | } 33 | 34 | // This function just skips executing the command. 35 | func CmdSkip(name string, args ...string) error { 36 | log.Printf("cmd skipped: %s %s", name, strings.Join(args, " ")) 37 | return nil 38 | } 39 | 40 | // This function returns Cmd or CmdSkip function. 41 | // When restarting NLAN agent, restart must be true. 42 | func GetCmd(panicMode bool) (func(string, ...string) error, func(string, ...string) error) { 43 | 44 | var f1 func(string, ...string) error 45 | f1 = func(name string, args ...string) error { 46 | err := Cmd(name, args...) 47 | if panicMode == true && err != nil { 48 | panic(err) 49 | } else { 50 | return err 51 | } 52 | } 53 | 54 | var f2 func(string, ...string) error 55 | f2 = func(name string, args ...string) error { 56 | // TODO: Replace Cmd() with CmdSkip() 57 | err := Cmd(name, args...) 58 | if panicMode == true && err != nil { 59 | panic(err) 60 | } else { 61 | return err 62 | } 63 | } 64 | 65 | return f1, f2 66 | } 67 | -------------------------------------------------------------------------------- /agent/config/ptn/ptn.go: -------------------------------------------------------------------------------- 1 | package ptn 2 | 3 | import ( 4 | "github.com/araobp/nlan/agent/context" 5 | "github.com/araobp/nlan/env" 6 | "github.com/araobp/nlan/model/nlan" 7 | 8 | "log" 9 | ) 10 | 11 | func Crud(crud int, in map[string]*nlan.Network, con *context.Context) { 12 | var crudNodes func(*nlan.Nodes, *context.Context) (string, string) 13 | var crudLinks func(*nlan.Links, *context.Context, string, string) 14 | var crudL2Vpn func(*nlan.L2Vpn, *context.Context, string, string) (string, string, string) 15 | switch crud { 16 | case env.ADD: 17 | crudNodes = AddNodes 18 | crudLinks = AddLinks 19 | crudL2Vpn = AddL2Vpn 20 | case env.UPDATE: 21 | crudNodes = UpdateNodes 22 | crudLinks = UpdateLinks 23 | crudL2Vpn = UpdateL2Vpn 24 | case env.DELETE: 25 | crudNodes = DeleteNodes 26 | crudLinks = DeleteLinks 27 | crudL2Vpn = DeleteL2Vpn 28 | default: 29 | log.Fatal("CRUD unidentified") 30 | } 31 | for _, net := range in { // map key is not used. 32 | log.Println(net) 33 | nodes := net.GetNodes() 34 | if nodes == nil { 35 | log.Fatal("nodes required") 36 | } 37 | links := net.GetLinks() 38 | if links == nil { 39 | log.Fatal("links required") 40 | } 41 | l2vpn := net.GetL2Vpn() 42 | if l2vpn == nil { 43 | log.Fatal("l2vpn required") 44 | } 45 | 46 | log.Printf("L2Sw: %s, Ptn: %s", nodes.L2Sw, nodes.Ptn) 47 | brTun, brInt := crudNodes(nodes, con) 48 | log.Printf("crudNodes() completed") 49 | 50 | crudLinks(links, con, brTun, brInt) 51 | for _, vpn := range l2vpn { 52 | ip, sVid, sVni := crudL2Vpn(vpn, con, brTun, brInt) 53 | log.Printf("crudL2Vpn() completed: %s, %s, %s", ip, sVid, sVni) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /plugins/nlan/plugins/subnets.py: -------------------------------------------------------------------------------- 1 | import tega.tree 2 | import tega.subscriber 3 | 4 | PATH_REGEX = r'operational-(\w*)\.ip' 5 | GRAPH_SUBNETS_PATH = 'graph.subnets' 6 | 7 | class Subnets(tega.subscriber.PlugIn): 8 | ''' 9 | Graph of subnets 10 | ''' 11 | 12 | def __init__(self): 13 | super().__init__() 14 | plugins = tega.tree.Cont('plugins') 15 | with self.tx() as t: 16 | plugins.subnets = self.func(self._subnets) 17 | t.put(plugins.subnets, ephemeral=True) 18 | 19 | def on_notify(self, notifications): 20 | pass 21 | 22 | def on_message(self, channel, tega_id, message): 23 | pass 24 | 25 | def _subnets(self): 26 | ''' 27 | Collects data from every operational tree and put graph of subnets 28 | onto tega db. 29 | ''' 30 | data = self.get(PATH_REGEX, regex_flag=True) 31 | addrs = {} 32 | subnets = {} 33 | 34 | for v in data: 35 | router = v[2][0][0] 36 | ip = v[1] 37 | for addr in ip.addr: 38 | addrs[addr] = router 39 | 40 | for v in data: 41 | router = v[2][0][0] 42 | ip = v[1] 43 | for subnet, route in ip.route.items(): 44 | if subnet == 'default': 45 | continue 46 | next_hop = route.Via 47 | if not next_hop: 48 | continue 49 | edge = (router, addrs[next_hop]) 50 | if not subnet in subnets: 51 | subnets[subnet] = [edge] 52 | else: 53 | subnets[subnet].append(edge) 54 | 55 | with self.tx() as t: 56 | t.put(path=GRAPH_SUBNETS_PATH, instance=subnets) 57 | 58 | -------------------------------------------------------------------------------- /doc/FINDINGS.md: -------------------------------------------------------------------------------- 1 | ## Findings so far (2016/01/10) 2 | 3 | In this project, I originally used etcd, goyang and gRPC, but they have some issues. 4 | 5 | In summary: 6 | - etcd does not support "ephemeral nodes". 7 | - etcd consumes a certain ammount of CPU time and memory. 8 | - etcd's APIs are not easy-to-use. 9 | - etcd is not easy to manage. 10 | - goyang does not support part of YANG spec, such as list. 11 | - YANG is incompatible with JSON, especially YANG's list type. 12 | - YANG is not for DevOps: most of DevOps frameworks use JSON/YAML format. 13 | - hard to debug gRPC messaging, since it's encoded in a binary format. 14 | 15 | So I gave up using those. 16 | 17 | Other findings: 18 | - [I have got that etcd (schema-less, KVS) is not suitable for this project](https://github.com/araobp/nlan/issues/12), so I am going to use [tega](https://github.com/araobp/tega) instead -- Tornado/Python is very good for a single-core 32bit CPU, and hash-table-based python dict is useful for data manipulation in some cases. 19 | - The combination of Docker and Golang is OK. 20 | - protobuf and gRPC are useful. But I would use pubsub (subscribe/notify) rather than gRPC, for managing containers: something like ZooKeeper. 21 | - YANG is not compatible with JSON/YAML. 22 | - OVSDB is just a read-only database in my project. 23 | - Processing overhead issues (Linux/OVS-bridges and VXLAN): I need to consider using macvlan-vepa with a smart physical switch with VLAN and SNMP support. 24 | - Cumulus Linux uses [netlink](https://tools.ietf.org/html/rfc3549) for controlling/managing both Linux switching/routing tables and hardware(ASIC), which seems very interesting: I have also tried out [tenus](https://github.com/milosgajdos83/tenus). 25 | 26 | ## YANG-JSON mapping(IETF) 27 | 28 | [Reference] https://tools.ietf.org/html/draft-ietf-netmod-yang-json 29 | 30 | -------------------------------------------------------------------------------- /plugins/nlan/plugins/topo.py: -------------------------------------------------------------------------------- 1 | import tega.tree 2 | import tega.util 3 | import tega.subscriber 4 | from tega.subscriber import SCOPE 5 | from tega.idb import OPE 6 | 7 | import json 8 | import logging 9 | 10 | CONFIG_PATH = r'config-.*' 11 | 12 | class Topo(tega.subscriber.PlugIn): 13 | ''' 14 | "nlan.state to network graph" transformation. 15 | ''' 16 | def __init__(self): 17 | super().__init__() 18 | self.plugins = tega.tree.Cont('plugins') 19 | self.subscribe(CONFIG_PATH, SCOPE.GLOBAL, regex_flag=True) 20 | 21 | def on_notify(self, notifications): 22 | ''' 23 | nlan.state --> vertexs/edges transformation 24 | ''' 25 | n_put = [] 26 | n_delete = [] 27 | logging.debug(notifications) 28 | for n in notifications: 29 | state = n['instance'] 30 | ope = n['ope'] 31 | path = n['path'] 32 | if ope == 'PUT': 33 | n_put.append([path, state]) 34 | elif ope == 'DELETE': 35 | # TODO: implementation 36 | pass 37 | 38 | nodes = [] 39 | edges = [] 40 | 41 | for router, model in n_put: 42 | ptn = model['Ptn'] 43 | seq = 0 44 | local_ip='' 45 | for id_, net in ptn.items(): 46 | links = net['Links'] 47 | local_ip = links['LocalIp'] 48 | remote_ips = links['RemoteIps'] 49 | for remote_ip in remote_ips: 50 | edges.append(dict(id='n'+str(seq), source=local_ip, target=remote_ip)) 51 | seq += 1 52 | nodes.append(dict(id=local_ip)) 53 | 54 | # Puts the transformed data on tega db 55 | with self.tx() as t: 56 | t.put(path='topo', instance=dict(nodes=nodes, edges=edges)) 57 | logging.info('topo put') 58 | 59 | def on_message(self, channel, tega_id, message): 60 | pass 61 | 62 | -------------------------------------------------------------------------------- /model/nlan/nlan.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package nlan; 4 | 5 | message State { 6 | map Router = 1; 7 | } 8 | 9 | message Model { 10 | Dvr Dvr = 1; 11 | map Ptn = 2; 12 | Router Router = 3; 13 | Vhosts Vhosts = 4; 14 | map Interfaces = 5; 15 | } 16 | 17 | message Dvr { 18 | bool OvsBridges = 1; 19 | repeated Subnets Subnets = 2; 20 | repeated Vxlan Vxlan = 3; 21 | } 22 | 23 | message Subnets { 24 | repeated IpDvr IpDvr = 1; 25 | repeated string Peers = 2; 26 | repeated string Ports = 3; 27 | uint32 Vid = 4; 28 | uint32 Vni = 5; 29 | } 30 | 31 | message IpDvr { 32 | string Addr = 1; 33 | string Dhcp = 2; 34 | string Mode = 3; 35 | } 36 | 37 | message Vxlan { 38 | string LocalIp = 1; 39 | repeated string RemoteIps = 2; 40 | } 41 | 42 | message Network { 43 | repeated L2Vpn L2Vpn = 2; 44 | Links Links = 3; 45 | Nodes Nodes = 4; 46 | } 47 | 48 | message L2Vpn { 49 | string Ip = 1; 50 | repeated string Peers = 2; 51 | uint32 Vid = 3; 52 | uint32 Vni = 4; 53 | } 54 | 55 | message Links { 56 | string LocalIp = 1; 57 | repeated string RemoteIps = 2; 58 | } 59 | 60 | message Nodes { 61 | string L2Sw = 1; 62 | string Ptn = 2; 63 | } 64 | 65 | message Router { 66 | map Bgp = 1; 67 | bool EmbeddedBgp = 2; 68 | string Loopback = 3; 69 | repeated Ospf Ospf = 4; 70 | } 71 | 72 | message Attrs { 73 | repeated Neighbor Neighbors = 1; 74 | } 75 | 76 | message Neighbor { 77 | bool NextHopSelf = 1; 78 | string Peer = 2; 79 | uint32 RemoteAs = 3; 80 | bool RouteReflectorClient = 4; 81 | } 82 | 83 | message Ospf { 84 | string Area = 1; 85 | repeated string Networks = 2; 86 | } 87 | 88 | message Vhosts { 89 | repeated VhostProps VhostProps = 1; 90 | } 91 | 92 | message VhostProps { 93 | string Network = 1; 94 | uint32 Vhosts = 2; 95 | } 96 | 97 | message Interface { 98 | string mode = 1; 99 | string local = 2; 100 | string remote = 3; 101 | string address = 4; 102 | } 103 | -------------------------------------------------------------------------------- /agent/config/ptn/l2vpn.go: -------------------------------------------------------------------------------- 1 | package ptn 2 | 3 | import ( 4 | "bytes" 5 | "github.com/araobp/nlan/agent/context" 6 | "github.com/araobp/nlan/model/nlan" 7 | "github.com/araobp/nlan/util" 8 | 9 | "log" 10 | "strconv" 11 | ) 12 | 13 | func addVpls(sVid string, sVni string, ip string, brInt string, con *context.Context) { 14 | cmd, cmdp := con.GetCmd() 15 | intBr := "int_br" + sVni 16 | cmdp("ovs-vsctl", "add-port", brInt, intBr, "tag="+sVid, "--", "set", "interface", intBr, "type=internal") 17 | cmd("ip", "link", "set", "dev", intBr, "up") 18 | cmd("ip", "addr", "add", "dev", intBr, ip) 19 | } 20 | 21 | func updateVpls(vni int, vid int, ip string, brInt string, con *context.Context) { 22 | // 23 | } 24 | 25 | func deleteVpls(vni int, vid int, ip string, brInt string, con *context.Context) { 26 | // 27 | } 28 | 29 | func addFlowEntries(sVid string, sVni string, peers *[]string, brTun string, con *context.Context) { 30 | cmd, _ := con.GetCmd() 31 | _ = "int_br" + sVni 32 | cmd("ovs-ofctl", "add-flow", brTun, "table=2,priority=1,tun_id="+sVni+",actions=mod_vlan_vid:"+sVid+",resubmit(,10)") 33 | // Broadcast tree for each vni 34 | l := util.GetVxlanPorts(peers) 35 | var buff bytes.Buffer 36 | for e := l.Front(); e != nil; e = e.Next() { 37 | v := e.Value 38 | outPort := strconv.Itoa(v.(int)) 39 | buff.Write([]byte(",output:")) 40 | buff.Write([]byte(outPort)) 41 | } 42 | outputPorts := buff.String() 43 | cmd("ovs-ofctl", "add-flow", brTun, "table=21,priority=1,dl_vlan="+sVid+",actions=strip_vlan,set_tunnel:"+sVni+outputPorts) 44 | } 45 | 46 | func AddL2Vpn(l2vpn *nlan.L2Vpn, con *context.Context, brTun string, brInt string) (string, string, string) { 47 | ip := l2vpn.Ip 48 | peers := l2vpn.Peers 49 | vid := l2vpn.Vid 50 | vni := l2vpn.Vni 51 | sVid := strconv.FormatUint(uint64(vid), 10) 52 | sVni := strconv.FormatUint(uint64(vni), 10) 53 | log.Printf("Adding vlan: %s", sVid) 54 | addVpls(sVid, sVni, ip, brInt, con) 55 | addFlowEntries(sVid, sVni, &peers, brTun, con) 56 | return ip, sVid, sVni 57 | } 58 | 59 | func UpdateL2Vpn(l2vpn *nlan.L2Vpn, con *context.Context, brTun string, brInt string) (string, string, string) { 60 | return "", "", "" 61 | } 62 | 63 | func DeleteL2Vpn(l2vpn *nlan.L2Vpn, con *context.Context, brTun string, brInt string) (string, string, string) { 64 | return "", "", "" 65 | } 66 | -------------------------------------------------------------------------------- /agent/config/ptn/nodes.go: -------------------------------------------------------------------------------- 1 | package ptn 2 | 3 | import ( 4 | "github.com/araobp/nlan/agent/context" 5 | "github.com/araobp/nlan/model/nlan" 6 | "github.com/araobp/nlan/util" 7 | 8 | "log" 9 | "strconv" 10 | ) 11 | 12 | func AddNodes(nodes *nlan.Nodes, con *context.Context) (string, string) { 13 | cmd, cmdp := con.GetCmd() 14 | brTun := nodes.Ptn 15 | brInt := nodes.L2Sw 16 | patchTun := "patch-tun_" + brTun 17 | patchInt := "patch-int_" + brInt 18 | log.Printf("Adding bridges: %s and %s\n", brTun, brInt) 19 | // Adds br-int and br-tun and connects them to each other 20 | cmdp("ovs-vsctl", "add-br", brInt) 21 | cmdp("ovs-vsctl", "add-br", brTun) 22 | cmd("ovs-ofctl", "del-flows", brTun) 23 | cmdp("ovs-vsctl", "add-port", brInt, patchInt, "--", "set", "interface", patchInt, "type=patch", "options:peer="+patchTun) 24 | cmdp("ovs-vsctl", "add-port", brTun, patchTun, "--", "set", "interface", patchTun, "type=patch", "options:peer="+patchInt) 25 | cmdp("ovs-vsctl", "set-fail-mode", brTun, "secure") 26 | // Obtains ofport for 'patch-tun' port 27 | patchTunNum := strconv.Itoa(util.GetOfport(patchTun)) 28 | log.Printf("patchTunNum(ofport): %s\n", patchTunNum) 29 | // Adds flow entries onto br-tun 30 | cmd("ovs-ofctl", "add-flow", brTun, "table=0,priority=1,in_port="+patchTunNum+",actions=resubmit(,1)") 31 | cmd("ovs-ofctl", "add-flow", brTun, "table=0,priority=0,actions=drop") 32 | cmd("ovs-ofctl", "add-flow", brTun, "table=1,priority=0,dl_dst=01:00:00:00:00:00/01:00:00:00:00:00,actions=resubmit(,19)") 33 | cmd("ovs-ofctl", "add-flow", brTun, "table=1,priority=0,dl_dst=00:00:00:00:00:00/01:00:00:00:00:00,actions=resubmit(,20)") 34 | cmd("ovs-ofctl", "add-flow", brTun, "table=2,priority=0,actions=drop") 35 | cmd("ovs-ofctl", "add-flow", brTun, "table=3,priority=0,actions=drop") 36 | cmd("ovs-ofctl", "add-flow", brTun, "table=10,priority=1,actions=learn(table=20,hard_timeout=300,priority=1,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:0->NXM_OF_VLAN_TCI[],load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],output:NXM_OF_IN_PORT[]),output:"+patchTunNum) 37 | cmd("ovs-ofctl", "add-flow", brTun, "table=19,priority=0,actions=resubmit(,21)") 38 | cmd("ovs-ofctl", "add-flow", brTun, "table=20,priority=0,actions=resubmit(,21)") 39 | cmd("ovs-ofctl", "add-flow", brTun, "table=21,priority=0,actions=drop") 40 | return brTun, brInt 41 | } 42 | 43 | func UpdateNodes(nodes *nlan.Nodes, con *context.Context) (string, string) { 44 | return "", "" 45 | } 46 | 47 | func DeleteNodes(nodes *nlan.Nodes, con *context.Context) (string, string) { 48 | return "", "" 49 | } 50 | -------------------------------------------------------------------------------- /agent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "time" 8 | 9 | config_interfaces "github.com/araobp/nlan/agent/config/interfaces" 10 | config_ptn "github.com/araobp/nlan/agent/config/ptn" 11 | config_router "github.com/araobp/nlan/agent/config/router" 12 | config_vhosts "github.com/araobp/nlan/agent/config/vhosts" 13 | "github.com/araobp/nlan/agent/context" 14 | "github.com/araobp/nlan/env" 15 | "github.com/araobp/nlan/model/nlan" 16 | "github.com/araobp/nlan/util" 17 | ) 18 | 19 | type agent struct { 20 | con *context.Context 21 | } 22 | 23 | func (a *agent) route(crud int, model *nlan.Model) (exit uint32) { 24 | ptn := model.GetPtn() 25 | dvr := model.GetDvr() 26 | vhosts := model.GetVhosts() 27 | router := model.GetRouter() 28 | interfaces := model.GetInterfaces() 29 | 30 | exit = 0 31 | defer func() { 32 | if r := recover(); r != nil { 33 | log.Println(r) 34 | exit = 1 35 | } 36 | }() 37 | 38 | if ptn != nil { 39 | log.Print("Routing to ptn module...") 40 | config_ptn.Crud(crud, ptn, a.con) 41 | } 42 | if interfaces != nil { 43 | log.Print("Routing to interfaces module...") 44 | config_interfaces.Crud(crud, interfaces, a.con) 45 | } 46 | if dvr != nil { 47 | // 48 | } 49 | if vhosts != nil { 50 | log.Print("Routing to vhosts module...") 51 | config_vhosts.Crud(crud, vhosts, a.con) 52 | } 53 | if router != nil { 54 | log.Print("Routing to router module...") 55 | config_router.Crud(crud, router, a.con) 56 | } 57 | return exit 58 | } 59 | 60 | func main() { 61 | 62 | // Command options 63 | target := os.Getenv("HOSTNAME") 64 | 65 | // Log file setup 66 | file := fmt.Sprintf("/var/volume/nlan-agent-%s.log", target) 67 | f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 68 | if err != nil { 69 | log.Fatalf("Unable to open log file: %s", file) 70 | } 71 | log.SetOutput(f) 72 | 73 | // Gets cmd and cmdp 74 | cmd, cmdp := util.GetCmd(true) 75 | 76 | //Adds a secondary IP address to eth0 77 | router := util.GetHostname() 78 | secondary := util.GetSecondaryIp(router) 79 | if secondary != "" { 80 | cmd("ip", "address", "add", secondary, "dev", "eth0") 81 | } 82 | 83 | //Agent 84 | c := &context.Context{Cmd: cmd, CmdP: cmdp} 85 | a := agent{con: c} 86 | 87 | log.Print("Starting...") 88 | model := new(nlan.Model) 89 | util.GetModel(router, model) 90 | log.Printf("State for %s: %v", router, model) 91 | exit := a.route(env.ADD, model) 92 | log.Printf("Started: %d", exit) 93 | 94 | //Infinite loop 95 | for { 96 | time.Sleep(1 * time.Second) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /agent/config/vhosts/vhosts.go: -------------------------------------------------------------------------------- 1 | package vhosts 2 | 3 | import ( 4 | "github.com/araobp/nlan/agent/context" 5 | "github.com/araobp/nlan/env" 6 | "github.com/araobp/nlan/model/nlan" 7 | 8 | "log" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | type ipAddress struct { 14 | network string 15 | abcd string 16 | abc string 17 | d string 18 | mask string 19 | } 20 | 21 | func getIpAddress(network string) ipAddress { 22 | a := strings.Split(network, "/") 23 | abcd := a[0] 24 | mask := a[1] 25 | as := strings.Split(abcd, ".") 26 | abc := strings.Join(as[0:3], ".") 27 | d := as[3] 28 | addr := ipAddress{ 29 | network: network, 30 | abcd: abcd, 31 | abc: abc, 32 | d: d, 33 | mask: mask, 34 | } 35 | return addr 36 | } 37 | 38 | func Crud(crud int, in *nlan.Vhosts, con *context.Context) { 39 | 40 | vhostProps := in.GetVhostProps() 41 | log.Print("Vhosts called...") 42 | var crudVhosts func(ipAddress, uint32, *context.Context) 43 | 44 | switch crud { 45 | case env.ADD: 46 | crudVhosts = addVhosts 47 | case env.UPDATE: 48 | crudVhosts = updateVhosts 49 | case env.DELETE: 50 | crudVhosts = deleteVhosts 51 | default: 52 | log.Fatal("CRUD unidentified") 53 | } 54 | 55 | for _, props := range vhostProps { 56 | log.Print(props) 57 | network := props.Network 58 | address := getIpAddress(network) 59 | vhosts := props.Vhosts 60 | 61 | log.Printf("Address: %v, Vhosts: %d", address, vhosts) 62 | crudVhosts(address, vhosts, con) 63 | log.Print("crudVhosts() completed") 64 | } 65 | } 66 | 67 | func addVhosts(addr ipAddress, vhosts uint32, con *context.Context) { 68 | 69 | cmd, _ := con.GetCmd() 70 | br := "br_" + addr.abcd 71 | br_ip := addr.abcd 72 | cmd("brctl", "addbr", br) 73 | cmd("ip", "addr", "add", "dev", br, addr.network) 74 | cmd("ip", "link", "set", "dev", br, "up") 75 | for i := 1; i <= int(vhosts); i++ { 76 | d, _ := strconv.Atoi(addr.d) 77 | id := strconv.Itoa(d + i) 78 | ip := addr.abc + "." + id 79 | ns := ip 80 | cmd("ip", "netns", "add", ns) 81 | cmd("ip", "link", "add", ns, "type", "veth", "peer", "name", "temp") 82 | cmd("ip", "link", "set", "dev", "temp", "netns", ns) 83 | cmd("ip", "netns", "exec", ns, "ip", "link", "set", "dev", "temp", "name", "eth0") 84 | cmd("brctl", "addif", br, ns) 85 | cmd("ip", "link", "set", "dev", ns, "promisc", "on") 86 | cmd("ip", "link", "set", "dev", ns, "up") 87 | cmd("ip", "netns", "exec", ns, "ip", "link", "set", "dev", "eth0", "up") 88 | cmd("ip", "netns", "exec", ns, "ip", "addr", "add", "dev", "eth0", ip+"/"+addr.mask) 89 | cmd("ip", "netns", "exec", ns, "ip", "route", "add", "default", "via", br_ip, "dev", "eth0") 90 | } 91 | } 92 | 93 | func updateVhosts(addr ipAddress, vhosts uint32, con *context.Context) { 94 | // 95 | } 96 | 97 | func deleteVhosts(addr ipAddress, vhosts uint32, con *context.Context) { 98 | // 99 | } 100 | -------------------------------------------------------------------------------- /etc/plugins/fabric2.py: -------------------------------------------------------------------------------- 1 | import tega.tree 2 | import tega.subscriber 3 | 4 | class Fabric2(tega.subscriber.PlugIn): 5 | 6 | def __init__(self): 7 | super().__init__() 8 | 9 | plugins = tega.tree.Cont('plugins') 10 | with self.tx() as t: 11 | plugins.fabric2 = self.func(self._state) 12 | t.put(plugins.fabric2, ephemeral=True) 13 | 14 | def on_notify(self, notifications): 15 | pass 16 | 17 | def on_message(self, channel, tega_id, message): 18 | pass 19 | 20 | def _state(self): 21 | 22 | routers_leaf = ['lf1', 'lf2', 'lf3', 'lf4', 'lf5', 'lf6'] 23 | routers_spine = ['sp1', 'sp2'] 24 | routers = [] 25 | routers.extend(routers_leaf) 26 | routers.extend(routers_spine) 27 | 28 | self.rpc('plugins.ipam', '10.10.1.1', *routers) 29 | routers_ = self.get('ip') 30 | ip = {} # ip addresses 31 | r = {} # roots 32 | for router, ip_ in routers_.items(): 33 | ip[router] = ip_.split('/')[0] 34 | 35 | # Roots 36 | for router in routers: 37 | r[router] = tega.tree.Cont('config-'+router) 38 | 39 | # Loopback address 40 | i = 0 41 | for router in routers: 42 | i += 1 43 | r[router].Router.Loopback = '10.1.1.{}/32'.format(i) 44 | 45 | # Interfaces 46 | def _params(mode, local, remote, address): 47 | return dict(Mode=mode, Local=local, Remote=remote, Address=address) 48 | i = 0 49 | for leaf in routers_leaf: 50 | i += 1 51 | j = 0 52 | for spine in routers_spine: 53 | j += 1 54 | address = '10.20{}.{}.1/24'.format(i, j) 55 | dev = '{}-{}'.format(leaf, spine) 56 | r[leaf].Interfaces[dev] = _params("gre", ip[leaf], ip[spine], address) 57 | address = '10.20{}.{}.2/24'.format(i, j) 58 | dev = '{}-{}'.format(spine, leaf) 59 | r[spine].Interfaces[dev] = _params("gre", ip[spine], ip[leaf], address) 60 | 61 | # BGP 62 | i = 0 63 | na = [] 64 | nb = [] 65 | for router in routers_leaf: 66 | i += 1 67 | n1 = dict(Peer='10.20{}.1.2'.format(i), RemoteAs=1000) 68 | n2 = dict(Peer='10.20{}.2.2'.format(i), RemoteAs=1000) 69 | r[router].Router.Bgp['10{}'.format(i)].Neighbors = [n1, n2] 70 | na.append(dict(Peer='10.20{}.1.1'.format(i), RemoteAs=100+i, NextHopSelf=True)) 71 | nb.append(dict(Peer='10.20{}.2.1'.format(i), RemoteAs=100+i, NextHopSelf=True)) 72 | r['sp1'].Router.Bgp['1000'].Neighbors = na 73 | r['sp2'].Router.Bgp['1000'].Neighbors = nb 74 | 75 | # Vhosts 76 | def _vhosts(*args): 77 | return [dict(Network=n, Vhosts=2) for n in args] 78 | i = 0 79 | for router in routers_leaf: 80 | i += 1 81 | r[router].Vhosts.VhostProps = _vhosts('172.21.{}.1/24'.format(i), '172.22.{}.1/24'.format(i)) 82 | 83 | # Commit 84 | with self.tx() as t: 85 | for router in routers: 86 | t.put(r[router]) 87 | -------------------------------------------------------------------------------- /etc/plugins/fabric.py: -------------------------------------------------------------------------------- 1 | import tega.tree 2 | import tega.subscriber 3 | 4 | class Fabric(tega.subscriber.PlugIn): 5 | 6 | def __init__(self): 7 | super().__init__() 8 | 9 | plugins = tega.tree.Cont('plugins') 10 | with self.tx() as t: 11 | plugins.fabric = self.func(self._state) 12 | t.put(plugins.fabric, ephemeral=True) 13 | 14 | def on_notify(self, notifications): 15 | pass 16 | 17 | def on_message(self, channel, tega_id, message): 18 | pass 19 | 20 | def _state(self): 21 | 22 | routers_leaf = ['lf1', 'lf2', 'lf3', 'lf4', 'lf5', 'lf6'] 23 | routers_spine = ['sp1', 'sp2'] 24 | routers = [] 25 | routers.extend(routers_leaf) 26 | routers.extend(routers_spine) 27 | 28 | self.rpc('plugins.ipam', '10.10.1.1', *routers) 29 | routers_ = self.get('ip') 30 | ip = {} # ip addresses 31 | r = {} # roots 32 | for router, ip_ in routers_.items(): 33 | ip[router] = ip_.split('/')[0] 34 | 35 | # Roots 36 | for router in routers: 37 | r[router] = tega.tree.Cont('config-'+router) 38 | 39 | # Loopback address 40 | i = 0 41 | for router in routers: 42 | i += 1 43 | r[router].Router.Loopback = '10.1.1.{}/32'.format(i) 44 | 45 | # Nodes (vertices) 46 | def _nodes(ptn, l2sw): 47 | return dict(Ptn=ptn, L2sw=l2sw) 48 | for router in routers: 49 | r[router].Ptn.dc.Nodes =_nodes(router+'ptn', router+'l2sw') 50 | 51 | # Links (edges) 52 | def _links(localIp, remoteIps): 53 | return dict(LocalIp=localIp, RemoteIps=remoteIps) 54 | for router in routers_leaf: 55 | r[router].Ptn.dc.Links = _links(ip[router], [ip[r] for r in routers_spine]) 56 | for router in routers_spine: 57 | r[router].Ptn.dc.Links = _links(ip[router], [ip[r] for r in routers_leaf]) 58 | 59 | # VPN 60 | def _vpn(vid, vni, peers, ip): 61 | return dict(Vid=vid, Vni=vni, Peers=peers, Ip=ip) 62 | i = 0 63 | j = 6 64 | va = [] 65 | vb = [] 66 | for router in routers_leaf: 67 | i += 1 68 | j += 1 69 | r[router].Ptn.dc.L2Vpn = [_vpn(i, i, [ip['sp1']], '10.20{}.1.1/24'.format(i)), 70 | _vpn(j, j, [ip['sp2']], '10.20{}.2.1/24'.format(i))] 71 | va.append(_vpn(i, i, [ip[router]], '10.20{}.1.2/24'.format(i))) 72 | vb.append(_vpn(j, j, [ip[router]], '10.20{}.2.2/24'.format(i))) 73 | r['sp1'].Ptn.dc.L2Vpn = va 74 | r['sp2'].Ptn.dc.L2Vpn = vb 75 | 76 | # BGP 77 | i = 0 78 | na = [] 79 | nb = [] 80 | for router in routers_leaf: 81 | i += 1 82 | n1 = dict(Peer='10.20{}.1.2'.format(i), RemoteAs=1000) 83 | n2 = dict(Peer='10.20{}.2.2'.format(i), RemoteAs=1000) 84 | r[router].Router.Bgp['10{}'.format(i)].Neighbors = [n1, n2] 85 | na.append(dict(Peer='10.20{}.1.1'.format(i), RemoteAs=100+i, NextHopSelf=True)) 86 | nb.append(dict(Peer='10.20{}.2.1'.format(i), RemoteAs=100+i, NextHopSelf=True)) 87 | r['sp1'].Router.Bgp['1000'].Neighbors = na 88 | r['sp2'].Router.Bgp['1000'].Neighbors = nb 89 | 90 | # Vhosts 91 | def _vhosts(*args): 92 | return [dict(Network=n, Vhosts=2) for n in args] 93 | i = 0 94 | for router in routers_leaf: 95 | i += 1 96 | r[router].Vhosts.VhostProps = _vhosts('172.21.{}.1/24'.format(i), '172.22.{}.1/24'.format(i)) 97 | 98 | # Commit 99 | with self.tx() as t: 100 | for router in routers: 101 | t.put(r[router]) 102 | -------------------------------------------------------------------------------- /util/ovsdb.go: -------------------------------------------------------------------------------- 1 | // 2015/10/15 2 | // Minimal RFC7047(OVSDB mgmt protocol) implementation for one-shot operation. 3 | package util 4 | 5 | import ( 6 | "container/list" 7 | "encoding/json" 8 | "fmt" 9 | "log" 10 | "net" 11 | ) 12 | 13 | const ( 14 | DATABASE = "Open_vSwitch" 15 | SOCK = "/var/run/openvswitch/db.sock" 16 | BUFSIZE = 4096 17 | ) 18 | 19 | type jsonrpc struct { 20 | Method string `json:"method"` 21 | Params []interface{} `json:"params"` 22 | Id int `json:"id"` 23 | } 24 | 25 | type Operation struct { 26 | Op string `json:"op"` 27 | Table string `json:"table"` 28 | Where []interface{} `json:"where"` 29 | Columns []string `json:"columns"` 30 | } 31 | 32 | type OperationWoColumns struct { 33 | Op string `json:"op"` 34 | Table string `json:"table"` 35 | Where []interface{} `json:"where"` 36 | } 37 | 38 | type Response struct { 39 | Error interface{} `json:"error"` 40 | Id int `json:"id"` 41 | Result []Rows 42 | } 43 | 44 | type Rows struct { 45 | Rows []map[string]interface{} `json:"rows"` 46 | } 47 | 48 | func Condition(column string, function string, value interface{}) []interface{} { 49 | return []interface{}{column, function, value} 50 | } 51 | 52 | func read(conn net.Conn) []byte { 53 | buf := make([]byte, BUFSIZE) 54 | n, err := conn.Read(buf) 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | response := buf[0:n] 59 | return response 60 | } 61 | 62 | var id int = 0 63 | 64 | // Synchronous JSON-RPC request to OVSDB. 65 | // Note: root privilege required for executing this API. 66 | func RequestSync(method string, params []interface{}) []byte { 67 | conn, err := net.Dial("unix", SOCK) 68 | if err != nil { 69 | log.Fatal(err) 70 | } 71 | defer conn.Close() 72 | id++ 73 | rpc := jsonrpc{ 74 | Method: method, 75 | Params: params, 76 | Id: id, 77 | } 78 | json_data, err := json.Marshal(rpc) 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | log.Printf("Request: %s\n", string(json_data)) 83 | fmt.Fprintf(conn, string(json_data)) 84 | response := read(conn) 85 | log.Printf("Response: %s\n", string(response)) 86 | return response 87 | } 88 | 89 | // Fetches ofport from OVSDB. 90 | func GetOfport(port string) int { 91 | cond := Condition("name", "==", port) 92 | ope := OperationWoColumns{ 93 | Op: "select", 94 | Table: "Interface", 95 | Where: []interface{}{cond}, 96 | } 97 | resp := RequestSync("transact", []interface{}{DATABASE, ope}) 98 | 99 | var r Response 100 | json.Unmarshal(resp, &r) 101 | row := r.Result[0].Rows[0] 102 | ofport := int(row["ofport"].(float64)) 103 | log.Printf("ofport: %d\n", ofport) 104 | return ofport 105 | } 106 | 107 | // Fetches VXLAN ofport from OVSDB. 108 | func GetVxlanPorts(peers *[]string) *list.List { 109 | cond := Condition("type", "==", "vxlan") 110 | ope := Operation{ 111 | Op: "select", 112 | Table: "Interface", 113 | Where: []interface{}{cond}, 114 | Columns: []string{"ofport", "options"}, 115 | } 116 | resp := RequestSync("transact", []interface{}{DATABASE, ope}) 117 | var r Response 118 | json.Unmarshal(resp, &r) 119 | rows := r.Result[0].Rows 120 | l := list.New() 121 | for _, ip := range *peers { 122 | for _, row := range rows { 123 | options := row["options"].([]interface{}) 124 | for _, e := range options[1].([]interface{}) { 125 | var ip_ string 126 | elm := e.([]interface{}) 127 | if elm[0].(string) == "remote_ip" { 128 | ip_ = elm[1].(string) 129 | if ip == ip_ { 130 | ofport := int(row["ofport"].(float64)) 131 | log.Printf("ofport: %d\n", ofport) 132 | l.PushBack(ofport) 133 | } 134 | break 135 | } 136 | } 137 | } 138 | } 139 | return l 140 | } 141 | 142 | func GetBridgeNames() *list.List { 143 | ope := Operation{ 144 | Op: "select", 145 | Table: "Bridge", 146 | Where: []interface{}{}, // empty 147 | Columns: []string{"name"}, 148 | } 149 | resp := RequestSync("transact", []interface{}{DATABASE, ope}) 150 | var r Response 151 | json.Unmarshal(resp, &r) 152 | rows := r.Result[0].Rows 153 | l := list.New() 154 | for _, row := range rows { 155 | bridge := row["name"].(string) 156 | l.PushBack(bridge) 157 | } 158 | return l 159 | } 160 | -------------------------------------------------------------------------------- /doc/misc/etcd.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net" 7 | "os" 8 | "strconv" 9 | "strings" 10 | "time" 11 | 12 | "github.com/araobp/nlan/env" 13 | "github.com/coreos/etcd/client" 14 | "golang.org/x/net/context" 15 | ) 16 | 17 | // etcd client 18 | func getKapi() (client.KeysAPI, context.Context) { 19 | 20 | etcdAddress := os.Getenv("ETCD_ADDRESS") 21 | if etcdAddress == "" { 22 | log.Fatalf("ETCD_ADDRESS unset") 23 | } 24 | 25 | config := client.Config{ 26 | Endpoints: []string{etcdAddress}, 27 | Transport: client.DefaultTransport, 28 | HeaderTimeoutPerRequest: time.Second, 29 | } 30 | cont := context.Background() 31 | c, err := client.New(config) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | return client.NewKeysAPI(c), cont 36 | } 37 | 38 | // Registers a host IP address with etcd 39 | func RegisterHost() string { 40 | 41 | hostname := os.Getenv("HOSTNAME") 42 | if hostname == "" { 43 | log.Fatalf("HOSTNAME unset") 44 | } 45 | kapi, cont := getKapi() 46 | 47 | key := "/nlan/hosts/" + hostname 48 | interfaces, _ := net.Interfaces() 49 | var addrs []net.Addr 50 | for _, inter := range interfaces { 51 | if inter.Name == "eth0" { 52 | addrs, _ = inter.Addrs() 53 | } 54 | } 55 | value := addrs[0].String() 56 | _, err := kapi.Set(cont, key, value, nil) 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | return hostname 61 | } 62 | 63 | // Gets a list of all host names and their addresses from etcd 64 | func ListHosts(secondary bool) map[string]interface{} { 65 | 66 | kapi, cont := getKapi() 67 | var key string 68 | switch secondary { 69 | case false: 70 | key = "/nlan/hosts" 71 | 72 | case true: 73 | key = "/nlan/ip" 74 | } 75 | list, err := kapi.Get(cont, key, &client.GetOptions{Recursive: true}) 76 | hosts := make(map[string]interface{}) 77 | if err == nil { 78 | nodes := list.Node.Nodes 79 | for _, node := range nodes { 80 | path := strings.Split(node.Key, "/") 81 | ipmask := strings.Split(node.Value, "/") 82 | hostname := path[3] 83 | ip := ipmask[0] 84 | hosts[hostname] = ip 85 | } 86 | } 87 | return hosts 88 | } 89 | 90 | // Sets NLAN state to etcd 91 | func SetState(hostname string, state interface{}) { 92 | 93 | kapi, cont := getKapi() 94 | key := "/nlan/state/" + hostname + "/json" 95 | wire, _ := json.Marshal(state) 96 | value := string(wire) 97 | _, err := kapi.Set(cont, key, value, nil) 98 | if err != nil { 99 | log.Fatal(err) 100 | } 101 | } 102 | 103 | // Gets NLAN state from etcd 104 | func GetState(hostname string, state interface{}) { 105 | 106 | kapi, cont := getKapi() 107 | key := "/nlan/state/" + hostname + "/json" 108 | r, err := kapi.Get(cont, key, &client.GetOptions{Recursive: true}) 109 | if err == nil { 110 | wire := []byte(r.Node.Value) 111 | err = json.Unmarshal(wire, state) 112 | if err != nil { 113 | log.Fatal(err) 114 | } 115 | } 116 | } 117 | 118 | // Sets NLAN mode to etcd 119 | func SetMode(hostname string, mode int) { 120 | 121 | kapi, cont := getKapi() 122 | key := "/nlan/state/" + hostname + "/mode" 123 | value := strconv.Itoa(mode) 124 | _, err := kapi.Set(cont, key, value, nil) 125 | if err != nil { 126 | log.Fatal(err) 127 | } 128 | } 129 | 130 | // Gets NLAN mode from etcd 131 | func GetMode(hostname string) int { 132 | 133 | var i int 134 | kapi, cont := getKapi() 135 | key := "/nlan/state/" + hostname + "/mode" 136 | r, err := kapi.Get(cont, key, &client.GetOptions{Recursive: false}) 137 | if err != nil { 138 | i = env.INIT 139 | } else { 140 | i, _ = strconv.Atoi(r.Node.Value) 141 | } 142 | return i 143 | } 144 | 145 | // Resets NLAN state on etcd 146 | func ResetState() { 147 | 148 | kapi, cont := getKapi() 149 | key := "/nlan/state" 150 | list, err := kapi.Get(cont, key, &client.GetOptions{Recursive: false}) 151 | if err == nil { 152 | nodes := list.Node.Nodes 153 | for _, node := range nodes { 154 | kapi.Delete(cont, node.Key, &client.DeleteOptions{Recursive: true}) 155 | } 156 | } 157 | } 158 | 159 | // Gets a secondary IP address from etcd 160 | func GetSecondaryIp(hostname string) string { 161 | 162 | kapi, cont := getKapi() 163 | key := "/nlan/ip/" + hostname 164 | var secondary string 165 | r, err := kapi.Get(cont, key, &client.GetOptions{Recursive: false}) 166 | if err == nil { 167 | secondary = r.Node.Value 168 | } 169 | return secondary 170 | } 171 | -------------------------------------------------------------------------------- /doc/misc/20160117.nlan.yang: -------------------------------------------------------------------------------- 1 | module nlan { 2 | 3 | yang-version 1; 4 | namespace "urn:araobp:nlan:bridges"; 5 | prefix "nlan"; 6 | contact "https://github.com/arapbp"; 7 | 8 | description "NLAN module"; 9 | revision 2015-12-01; 10 | 11 | // distributed-virtual-router model 12 | grouping dvr-model { 13 | 14 | leaf ovs_bridges { 15 | type boolean; 16 | default true; 17 | } 18 | 19 | list vxlan { 20 | key local_ip; 21 | leaf local_ip { 22 | type string; 23 | } 24 | leaf-list remote_ips { 25 | type string; 26 | } 27 | } 28 | 29 | list subnets { 30 | key vid; 31 | leaf vid { 32 | type uint16; 33 | } 34 | leaf vni { 35 | type uint32; 36 | mandatory true; 37 | } 38 | list ip_dvr { 39 | key addr; 40 | leaf addr { 41 | type string; 42 | } 43 | leaf mode { 44 | type string; 45 | mandatory true; 46 | } 47 | leaf dhcp { 48 | type string; 49 | default "diabled"; 50 | } 51 | } 52 | leaf-list ports { 53 | type string; 54 | } 55 | leaf-list peers { 56 | type string; 57 | } 58 | } 59 | } 60 | 61 | // simulated packet-transport-network model 62 | grouping ptn-model { 63 | 64 | list networks { 65 | key id; 66 | leaf id { 67 | type string; 68 | } 69 | // simulated packet-transport-nodes 70 | container nodes { 71 | leaf ptn { 72 | type string; 73 | } 74 | leaf l2sw { 75 | type string; 76 | } 77 | } 78 | 79 | // simulated links among packet-transport-nodes 80 | container links { 81 | leaf local_ip { 82 | type string; 83 | mandatory true; 84 | } 85 | leaf-list remote_ips { 86 | type string; 87 | } 88 | } 89 | 90 | // simulated layer-2 VPNs 91 | list l2-vpn { 92 | key vid; 93 | leaf vid { 94 | type uint16; 95 | } 96 | leaf vni { 97 | type uint32; 98 | mandatory true; 99 | } 100 | leaf-list peers { 101 | type string; 102 | } 103 | leaf ip { 104 | type string; 105 | mandatory true; 106 | } 107 | } 108 | } 109 | } 110 | 111 | // Virtual hosts model 112 | grouping vhosts-model { 113 | list vhost-props { 114 | key network; 115 | leaf network { 116 | type string; 117 | } 118 | leaf vhosts { 119 | type uint16; 120 | } 121 | } 122 | } 123 | 124 | // Router model 125 | grouping router-model { 126 | leaf loopback { 127 | type string; 128 | } 129 | // quagga-bgpd(false) or gobgp(true) 130 | leaf embedded-bgp { 131 | type boolean; 132 | default false; 133 | } 134 | list ospf { 135 | key area; 136 | leaf area { 137 | type string; 138 | } 139 | leaf-list networks { 140 | type string; 141 | } 142 | } 143 | list bgp { 144 | key as; 145 | leaf as { 146 | type uint16; 147 | } 148 | list neighbors { 149 | key peer; 150 | leaf peer { 151 | type string; 152 | } 153 | leaf remote-as { 154 | type uint16; 155 | } 156 | leaf route-reflector-client { 157 | type boolean; 158 | default false; 159 | } 160 | leaf next-hop-self { 161 | type boolean; 162 | default false; 163 | } 164 | } 165 | } 166 | } 167 | 168 | // NLAN Request message 169 | container request { 170 | container model { 171 | container dvr { 172 | uses dvr-model; 173 | } 174 | container ptn { 175 | uses ptn-model; 176 | } 177 | container vhosts { 178 | uses vhosts-model; 179 | } 180 | container router { 181 | uses router-model; 182 | } 183 | } 184 | } 185 | 186 | // NLAN Response message 187 | container response { 188 | leaf exit { 189 | type uint16; // shell command exit code 190 | } 191 | leaf log_message { 192 | type string; // log messages 193 | } 194 | } 195 | 196 | // NLAN Master/Agent capabilities 197 | container capabilities { 198 | leaf-list capability { 199 | type string; 200 | } 201 | } 202 | 203 | container clear-mode { 204 | leaf terminate { 205 | type boolean; 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /util/tega.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "os" 8 | "strings" 9 | 10 | "github.com/araobp/nlan/model/nlan" 11 | "github.com/araobp/tega/driver" 12 | 13 | netstat "github.com/drael/GOnetstat" 14 | ) 15 | 16 | var ope *driver.Operation 17 | var hostname string 18 | 19 | const ( 20 | IP_PATH = "ip" 21 | HOSTS_PATH = "hosts" 22 | RAW_PATH = "raw" 23 | CONFIG_PATH = "config" 24 | OPERATIONAL_PATH = "operational" 25 | STATS_PATH = "stats" 26 | ) 27 | 28 | type Self struct { 29 | } 30 | 31 | func (r *Self) OnInit() { 32 | ope.RegisterRpc(fmt.Sprintf("%s.%s", RAW_PATH, hostname), raw) 33 | ope.RegisterRpc(fmt.Sprintf("%s-%s.ip.hook.route", OPERATIONAL_PATH, hostname), ipRoute) 34 | ope.RegisterRpc(fmt.Sprintf("%s-%s.ip.hook.addr", OPERATIONAL_PATH, hostname), ipAddr) 35 | ope.RegisterRpc(fmt.Sprintf("%s-%s.hook.netstat", STATS_PATH, hostname), netstat_) 36 | 37 | registerHost() 38 | } 39 | 40 | func (r *Self) OnNotify(v *[]driver.Notification) { 41 | } 42 | 43 | func (r *Self) OnMessage(channel string, tegaId string, msg *driver.Message) { 44 | } 45 | 46 | func init() { 47 | var err error 48 | hostname = os.Getenv("HOSTNAME") 49 | if hostname == "" { 50 | hostname = "localhost" 51 | } 52 | tega := os.Getenv("TEGA_ADDRESS") 53 | self := &Self{} 54 | ope, err = driver.NewOperation(hostname, tega, 0, self, driver.LOCAL) 55 | if err != nil { 56 | log.Fatal(err) 57 | } 58 | } 59 | 60 | // Returns os.Getenv("HOSTNAME") 61 | func GetHostname() string { 62 | return hostname 63 | } 64 | 65 | // Registers a host IP address with tega 66 | func registerHost() { 67 | var err error 68 | if err != nil { 69 | log.Fatal(err) 70 | } 71 | path := fmt.Sprintf("%s.%s", HOSTS_PATH, hostname) 72 | interfaces, _ := net.Interfaces() 73 | var addrs []net.Addr 74 | for _, inter := range interfaces { 75 | if inter.Name == "eth0" { 76 | addrs, _ = inter.Addrs() 77 | } 78 | } 79 | value := addrs[0].String() 80 | err = ope.PutE(path, value) 81 | if err != nil { 82 | log.Fatal(err) 83 | } 84 | } 85 | 86 | // Gets a list of all host names and their addresses from tega 87 | func listHosts(path string) map[string]interface{} { 88 | var nodes map[string]interface{} 89 | err := ope.Get(path, &nodes) 90 | if err != nil { 91 | log.Fatal(err) 92 | } 93 | hosts := make(map[string]interface{}) 94 | for host, ipmask := range nodes { 95 | log.Print(host) 96 | log.Print(ipmask) 97 | hosts[host] = strings.Split(ipmask.(string), "/")[0] 98 | } 99 | return hosts 100 | } 101 | 102 | // Lists up all hosts on HOSTS_PATH 103 | func ListHosts() map[string]interface{} { 104 | return listHosts(HOSTS_PATH) 105 | } 106 | 107 | // Lists up all hosts on IP_PATH 108 | func ListIps() map[string]interface{} { 109 | return listHosts(IP_PATH) 110 | } 111 | 112 | // Sets NLAN config to tega 113 | func SetModel(hostname string, model *nlan.Model) { 114 | path := fmt.Sprintf("%s-%s", CONFIG_PATH, hostname) 115 | err := ope.Put(path, model) 116 | if err != nil { 117 | log.Fatal(err) 118 | } 119 | } 120 | 121 | // Gets NLAN state from tega 122 | func GetModel(hostname string, model *nlan.Model) { 123 | path := fmt.Sprintf("%s-%s", CONFIG_PATH, hostname) 124 | err := ope.Get(path, model) 125 | if err != nil { 126 | log.Fatal(err) 127 | } 128 | } 129 | 130 | // Resets NLAN state on tega 131 | func ResetState() { 132 | // TODO: implementation 133 | /* 134 | err := ope.Delete("nlan.state") 135 | if err != nil { 136 | log.Print(err) 137 | } 138 | */ 139 | } 140 | 141 | // Gets a secondary IP address from tega 142 | func GetSecondaryIp(hostname string) string { 143 | path := fmt.Sprintf("%s.%s", IP_PATH, hostname) 144 | var secondary string 145 | err := ope.Get(path, &secondary) 146 | if err != nil { 147 | log.Fatal(err) 148 | } 149 | return secondary 150 | } 151 | 152 | // Executes a raw command (i.e., shell command) 153 | func raw(argsKwargs driver.ArgsKwargs) (driver.Result, error) { 154 | args := strings.Split(argsKwargs.Args[0].(string), " ") 155 | cmd := args[0] 156 | var cmdArgs []string 157 | if len(args) > 1 { 158 | cmdArgs = args[1:] 159 | } 160 | result, _ := OutputCmd(cmd, cmdArgs...) // Executes a raw command 161 | return driver.Result{Res: result}, nil 162 | } 163 | 164 | func ipRoute(argsKwargs driver.ArgsKwargs) (driver.Result, error) { 165 | value := driver.Result{Res: RouteMap()} 166 | path := fmt.Sprintf("%s-%s.ip.route", OPERATIONAL_PATH, hostname) 167 | ope.PutE(path, value.Res) 168 | return value, nil 169 | } 170 | 171 | func ipAddr(argsKwargs driver.ArgsKwargs) (driver.Result, error) { 172 | devMap, addrMap := AddrMap() 173 | devPath := fmt.Sprintf("%s-%s.ip.dev", OPERATIONAL_PATH, hostname) 174 | addrPath := fmt.Sprintf("%s-%s.ip.addr", OPERATIONAL_PATH, hostname) 175 | ope.PutE(devPath, devMap) 176 | ope.PutE(addrPath, addrMap) 177 | res := []interface{}{devMap, addrMap} 178 | value := driver.Result{Res: res} 179 | return value, nil 180 | } 181 | 182 | func netstat_(argsKwargs driver.ArgsKwargs) (driver.Result, error) { 183 | tcpstat := Netstat(TCP) 184 | udpstat := Netstat(UDP) 185 | data := make(map[string]*[]netstat.Process) 186 | data["tcp"] = tcpstat 187 | data["udp"] = udpstat 188 | netstatPath := fmt.Sprintf("%s-%s.netstat", STATS_PATH, hostname) 189 | ope.PutE(netstatPath, data) 190 | value := driver.Result{Res: data} 191 | return value, nil 192 | } 193 | -------------------------------------------------------------------------------- /agent/config/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/araobp/nlan/agent/context" 5 | "github.com/araobp/nlan/env" 6 | "github.com/araobp/nlan/model/nlan" 7 | "github.com/araobp/nlan/util" 8 | api "github.com/osrg/gobgp/api" 9 | "github.com/osrg/gobgp/packet" 10 | gobgp "github.com/osrg/gobgp/server" 11 | 12 | "log" 13 | "strconv" 14 | "strings" 15 | ) 16 | 17 | func Crud(crud int, in *nlan.Router, con *context.Context) { 18 | 19 | loopback := in.Loopback 20 | embedded := in.EmbeddedBgp // quagga-bgpd(false) or gobgp(true) 21 | ospf := in.GetOspf() 22 | bgp := in.GetBgp() 23 | cmd, _ := con.GetCmd() 24 | log.Print("Router called...") 25 | 26 | var s *gobgp.BgpServer 27 | var g *gobgp.Server 28 | if embedded { 29 | cmd("/etc/init.d/quagga", "stop") 30 | s = gobgp.NewBgpServer() 31 | go s.Serve() 32 | g = gobgp.NewGrpcServer(50051, s.GrpcReqCh) 33 | go g.Serve() 34 | } 35 | 36 | var crudRouter func(string, bool, *gobgp.BgpServer, []*nlan.Ospf, map[string]*nlan.Attrs, *context.Context) 37 | 38 | switch crud { 39 | case env.ADD: 40 | crudRouter = addRouter 41 | case env.UPDATE: 42 | crudRouter = updateRouter 43 | case env.DELETE: 44 | crudRouter = deleteRouter 45 | default: 46 | log.Fatal("CRUD unidentified") 47 | } 48 | 49 | log.Printf("Loopback: %s", loopback) 50 | crudRouter(loopback, embedded, s, ospf, bgp, con) 51 | log.Print("crudRouter() completed") 52 | } 53 | 54 | func routerOspfNetworks(s *[][]string, area string, networks []string) { 55 | for _, network := range networks { 56 | n := []string{} 57 | n = append(n, "network") 58 | n = append(n, network) 59 | n = append(n, "area") 60 | n = append(n, area) 61 | *s = append(*s, n) 62 | } 63 | } 64 | 65 | func routerBgpNeighbors(s *[][]string, attrs *nlan.Attrs) { 66 | for _, n := range attrs.Neighbors { 67 | peer := n.Peer 68 | as := n.RemoteAs 69 | client := n.RouteReflectorClient 70 | nextHopSelf := n.NextHopSelf 71 | n := []string{} 72 | n = append(n, "neighbor") 73 | n = append(n, peer) 74 | n = append(n, "remote-as") 75 | n = append(n, strconv.FormatUint(uint64(as), 10)) 76 | *s = append(*s, n) 77 | if client == true { 78 | c := []string{} 79 | c = append(c, "neighbor") 80 | c = append(c, peer) 81 | c = append(c, "route-reflector-client") 82 | *s = append(*s, c) 83 | } 84 | if nextHopSelf == true { 85 | n := []string{} 86 | n = append(n, "neighbor") 87 | n = append(n, peer) 88 | n = append(n, "next-hop-self") 89 | *s = append(*s, n) 90 | } 91 | } 92 | } 93 | 94 | func gobgpReqModNeighbor(s *gobgp.BgpServer, attrs *nlan.Attrs, con *context.Context) { 95 | for _, n := range attrs.Neighbors { 96 | peer := n.Peer 97 | as := n.RemoteAs 98 | client := n.RouteReflectorClient 99 | p := api.Peer{} 100 | p.Conf = &api.PeerConf{ 101 | NeighborAddress: peer, 102 | PeerAs: as, 103 | } 104 | if client == true { 105 | p.RouteReflector = &api.RouteReflector{ 106 | RouteReflectorClient: true, 107 | } 108 | } 109 | req := gobgp.NewGrpcRequest(gobgp.REQ_MOD_NEIGHBOR, "", bgp.RouteFamily(0), &api.ModNeighborArguments{ 110 | Operation: api.Operation_ADD, 111 | Peer: &p, 112 | }) 113 | s.GrpcReqCh <- req 114 | res := <-req.ResponseCh 115 | if err := res.Err(); err != nil { 116 | log.Print(err) 117 | } 118 | } 119 | } 120 | 121 | func gobgpReqModGlobalConfig(s *gobgp.BgpServer, routerId string, as int64, con *context.Context) { 122 | req := gobgp.NewGrpcRequest(gobgp.REQ_MOD_GLOBAL_CONFIG, "", bgp.RouteFamily(0), &api.ModGlobalConfigArguments{ 123 | Operation: api.Operation_ADD, 124 | Global: &api.Global{ 125 | As: uint32(as), 126 | RouterId: routerId, 127 | ListenPort: -1, // gobgp won't listen on tcp:179 128 | }, 129 | }) 130 | s.GrpcReqCh <- req 131 | res := <-req.ResponseCh 132 | if err := res.Err(); err != nil { 133 | log.Print(err) 134 | } 135 | } 136 | 137 | func addRouter(loopback string, embedded bool, s *gobgp.BgpServer, ospf []*nlan.Ospf, bgp map[string]*nlan.Attrs, con *context.Context) { 138 | 139 | cmd, cmdp := con.GetCmd() 140 | 141 | // Loopback address 142 | cmd("ip", "addr", "add", "dev", "lo", loopback) 143 | 144 | // Allow receiving packets from non-best-path interfaces 145 | cmd("sysctl", "-w", "net.ipv4.conf.all.rp_filter=2") 146 | // Allow routing packets with local addresses 147 | cmd("sysctl", "-w", "net.ipv4.conf.all.accept_local=1") 148 | 149 | var script [][]string 150 | if ospf != nil { 151 | script = append(script, []string{"router", "ospf"}) 152 | script = append(script, []string{"redistribute", "connected"}) 153 | for _, o := range ospf { 154 | area := o.Area 155 | networks := o.Networks 156 | log.Print("OSPF Networks: %v", networks) 157 | routerOspfNetworks(&script, area, networks) 158 | } 159 | script = append(script, []string{"exit"}) 160 | } 161 | if bgp != nil { 162 | for as, neighs := range bgp { 163 | if embedded { 164 | routerId := strings.Split(loopback, "/")[0] 165 | asInt, _ := strconv.ParseInt(as, 10, 64) 166 | gobgpReqModGlobalConfig(s, routerId, asInt, con) 167 | } else { 168 | script = append(script, []string{"router", "bgp", as}) 169 | script = append(script, []string{"redistribute", "connected"}) 170 | } 171 | if neighs != nil { 172 | if embedded { 173 | gobgpReqModNeighbor(s, neighs, con) 174 | } else { 175 | routerBgpNeighbors(&script, neighs) 176 | } 177 | } 178 | } 179 | script = append(script, []string{"exit"}) 180 | } 181 | if len(script) > 0 && !embedded { 182 | batch := util.VtyshBatch(script) 183 | cmdp("vtysh", batch...) 184 | } 185 | } 186 | 187 | func updateRouter(loopback string, embedded bool, s *gobgp.BgpServer, ospf []*nlan.Ospf, bgp map[string]*nlan.Attrs, con *context.Context) { 188 | // 189 | } 190 | 191 | func deleteRouter(loopback string, embedded bool, s *gobgp.BgpServer, ospf []*nlan.Ospf, bgp map[string]*nlan.Attrs, con *context.Context) { 192 | // 193 | } 194 | -------------------------------------------------------------------------------- /etc/ptn-ospf.yaml: -------------------------------------------------------------------------------- 1 | # OSPF simulation 2 | # 3 | pe1: 4 | Ptn: 5 | Networks: 6 | - Id: wan 7 | Nodes: 8 | Ptn: ptnpe1w 9 | L2sw: l2swpe1w 10 | Links: 11 | LocalIp: ${pe1} 12 | RemoteIps: [${pe2}, ${pe3}, ${pe4}, ${rr}] 13 | L2Vpn: 14 | - Vid: 101 15 | Vni: 1 16 | Peers: [${pe2}, ${pe3}, ${pe4}, ${rr}] 17 | Ip: 10.200.1.101/24 18 | - Vid: 102 19 | Vni: 2 20 | Peers: [${pe2}, ${pe3}, ${pe4}, ${rr}] 21 | Ip: 10.200.2.101/24 22 | - Id: access 23 | Nodes: 24 | Ptn: ptnpe1a 25 | L2sw: l2swpe1a 26 | Links: 27 | LocalIp: ${pe1} 28 | RemoteIps: [${ce1}, ${ce2}] 29 | L2Vpn: 30 | - Vid: 11 31 | Vni: 111 32 | Peers: [${ce1}] 33 | Ip: 10.201.11.1/24 34 | - Vid: 12 35 | Vni: 112 36 | Peers: [${ce2}] 37 | Ip: 10.201.12.1/24 38 | Router: 39 | Loopback: 10.1.1.1/32 40 | Ospf: 41 | - Area: 0.0.0.0 42 | Networks: [10.200.1.101/24, 10.200.2.101/24] 43 | - Area: 0.0.0.0 44 | Networks: [10.201.11.1/24, 10.201.12.1/24] 45 | pe2: 46 | Ptn: 47 | Networks: 48 | - Id: wan 49 | Nodes: 50 | Ptn: ptnpe2w 51 | L2sw: l2swpe2w 52 | Links: 53 | LocalIp: ${pe2} 54 | RemoteIps: [${pe1}, ${pe3}, ${pe4}, ${rr}] 55 | L2Vpn: 56 | - Vid: 101 57 | Vni: 1 58 | Peers: [${pe1}, ${pe3}, ${pe4}, ${rr}] 59 | Ip: 10.200.1.102/24 60 | - Vid: 102 61 | Vni: 2 62 | Peers: [${pe2}, ${pe3}, ${pe4}, ${rr}] 63 | Ip: 10.200.2.102/24 64 | - Id: access 65 | Nodes: 66 | Ptn: ptnpe2a 67 | L2sw: l2swpe2a 68 | Links: 69 | LocalIp: ${pe2} 70 | RemoteIps: [${ce1}, ${ce2}] 71 | L2Vpn: 72 | - Vid: 11 73 | Vni: 211 74 | Peers: [${ce1}] 75 | Ip: 10.202.11.1/24 76 | - Vid: 12 77 | Vni: 212 78 | Peers: [${ce2}] 79 | Ip: 10.202.12.1/24 80 | Router: 81 | Loopback: 10.1.1.2/32 82 | Ospf: 83 | - Area: 0.0.0.0 84 | Networks: [10.200.1.102/24, 10.200.2.102/24] 85 | - Area: 0.0.0.0 86 | Networks: [10.202.11.1/24, 10.202.12.1/24] 87 | pe3: 88 | Ptn: 89 | Networks: 90 | - Id: wan 91 | Nodes: 92 | Ptn: ptnpe3w 93 | L2sw: l2swpe3w 94 | Links: 95 | LocalIp: ${pe3} 96 | RemoteIps: [${pe1}, ${pe2}, ${pe4}, ${rr}] 97 | L2Vpn: 98 | - Vid: 101 99 | Vni: 1 100 | Peers: [${pe1}, ${pe2}, ${pe4}, ${rr}] 101 | Ip: 10.200.1.103/24 102 | - Vid: 102 103 | Vni: 2 104 | Peers: [${pe1}, ${pe2}, ${pe4}, ${rr}] 105 | Ip: 10.200.2.103/24 106 | - Id: access 107 | Nodes: 108 | Ptn: ptnpe3a 109 | L2sw: l2swpe3a 110 | Links: 111 | LocalIp: ${pe3} 112 | RemoteIps: [${ce3}, ${ce4}] 113 | L2Vpn: 114 | - Vid: 13 115 | Vni: 313 116 | Peers: [${ce3}] 117 | Ip: 10.203.13.1/24 118 | - Vid: 14 119 | Vni: 314 120 | Peers: [${ce4}] 121 | Ip: 10.203.14.1/24 122 | Router: 123 | Loopback: 10.1.1.3/32 124 | Ospf: 125 | - Area: 0.0.0.0 126 | Networks: [10.200.1.103/24, 10.200.2.103/24] 127 | - Area: 0.0.0.0 128 | Networks: [10.203.13.1/24, 10.203.14.1/24] 129 | pe4: 130 | Ptn: 131 | Networks: 132 | - Id: wan 133 | Nodes: 134 | Ptn: ptnpe4w 135 | L2sw: l2swpe4w 136 | Links: 137 | LocalIp: ${pe4} 138 | RemoteIps: [${pe1}, ${pe2}, ${pe3}, ${rr}] 139 | L2Vpn: 140 | - Vid: 101 141 | Vni: 1 142 | Peers: [${pe1}, ${pe2}, ${pe3}, ${rr}] 143 | Ip: 10.200.1.104/24 144 | - Vid: 102 145 | Vni: 2 146 | Peers: [${pe1}, ${pe2}, ${pe3}, ${rr}] 147 | Ip: 10.200.2.104/24 148 | - Id: access 149 | Nodes: 150 | Ptn: ptnpe4a 151 | L2sw: l2swpe4a 152 | Links: 153 | LocalIp: ${pe4} 154 | RemoteIps: [${ce3}, ${ce4}] 155 | L2Vpn: 156 | - Vid: 13 157 | Vni: 413 158 | Peers: [${ce3}] 159 | Ip: 10.204.13.1/24 160 | - Vid: 14 161 | Vni: 414 162 | Peers: [${ce4}] 163 | Ip: 10.204.14.1/24 164 | Router: 165 | Loopback: 10.1.1.4/32 166 | Ospf: 167 | - Area: 0.0.0.0 168 | Networks: [10.200.1.104/24, 10.200.2.104/24] 169 | - Area: 0.0.0.0 170 | Networks: [10.204.13.1/24, 10.204.14.1/24] 171 | rr: 172 | Ptn: 173 | Networks: 174 | - Id: wan 175 | Nodes: 176 | Ptn: ptnrrw 177 | L2sw: l2swrrw 178 | Links: 179 | LocalIp: ${rr} 180 | RemoteIps: [${pe1}, ${pe2}, ${pe3}, ${pe4}] 181 | L2Vpn: 182 | - Vid: 101 183 | Vni: 1 184 | Peers: [${pe1}, ${pe2}, ${pe3}, ${pe4}] 185 | Ip: 10.200.1.105/24 186 | - Vid: 102 187 | Vni: 2 188 | Peers: [${pe1}, ${pe2}, ${pe3}, ${pe4}] 189 | Ip: 10.200.2.105/24 190 | Router: 191 | Loopback: 10.1.1.5/32 192 | Ospf: 193 | - Area: 0.0.0.0 194 | Networks: [10.200.1.105/24, 10.200.2.105/24] 195 | ce1: 196 | Ptn: 197 | Networks: 198 | - Id: access 199 | Nodes: 200 | Ptn: ptnce1a 201 | L2sw: l2swce1a 202 | Links: 203 | LocalIp: ${ce1} 204 | RemoteIps: [${pe1}, ${pe2}] 205 | L2Vpn: 206 | - Vid: 1 207 | Vni: 111 208 | Peers: [${pe1}] 209 | Ip: 10.201.11.2/24 210 | - Vid: 2 211 | Vni: 211 212 | Peers: [${pe2}] 213 | Ip: 10.202.11.2/24 214 | Vhosts: 215 | VhostProps: 216 | - Network: 172.21.1.1/24 217 | Vhosts: 2 218 | - Network: 172.22.1.1/24 219 | Vhosts: 2 220 | Router: 221 | Loopback: 10.1.2.1/32 222 | Ospf: 223 | - Area: 0.0.0.0 224 | Networks: [10.201.11.2/24, 10.202.11.2/24] 225 | ce2: 226 | Ptn: 227 | Networks: 228 | - Id: access 229 | Nodes: 230 | Ptn: ptnce2a 231 | L2sw: l2swce2a 232 | Links: 233 | LocalIp: ${ce2} 234 | RemoteIps: [${pe1}, ${pe2}] 235 | L2Vpn: 236 | - Vid: 1 237 | Vni: 112 238 | Peers: [${pe1}] 239 | Ip: 10.201.12.2/24 240 | - Vid: 2 241 | Vni: 212 242 | Peers: [${pe2}] 243 | Ip: 10.202.12.2/24 244 | Vhosts: 245 | VhostProps: 246 | - Network: 172.21.2.1/24 247 | Vhosts: 2 248 | - Network: 172.22.2.1/24 249 | Vhosts: 2 250 | Router: 251 | Loopback: 10.1.2.2/32 252 | Ospf: 253 | - Area: 0.0.0.0 254 | Networks: [10.201.12.2/24, 10.202.12.2/24] 255 | ce3: 256 | Ptn: 257 | Networks: 258 | - Id: access 259 | Nodes: 260 | Ptn: ptnce3a 261 | L2sw: l2swce3a 262 | Links: 263 | LocalIp: ${ce3} 264 | RemoteIps: [${pe3}, ${pe4}] 265 | L2Vpn: 266 | - Vid: 3 267 | Vni: 313 268 | Peers: [${pe3}] 269 | Ip: 10.203.13.2/24 270 | - Vid: 4 271 | Vni: 413 272 | Peers: [${pe4}] 273 | Ip: 10.204.13.2/24 274 | Vhosts: 275 | VhostProps: 276 | - Network: 172.21.3.1/24 277 | Vhosts: 2 278 | - Network: 172.22.3.1/24 279 | Vhosts: 2 280 | Router: 281 | Loopback: 10.1.2.3/32 282 | Ospf: 283 | - Area: 0.0.0.0 284 | Networks: [10.203.13.2/24, 10.204.13.2/24] 285 | ce4: 286 | Ptn: 287 | Networks: 288 | - Id: access 289 | Nodes: 290 | Ptn: ptnce4a 291 | L2sw: l2swce4a 292 | Links: 293 | LocalIp: ${ce4} 294 | RemoteIps: [${pe3}, ${pe4}] 295 | L2Vpn: 296 | - Vid: 3 297 | Vni: 314 298 | Peers: [${pe3}] 299 | Ip: 10.203.14.2/24 300 | - Vid: 4 301 | Vni: 414 302 | Peers: [${pe4}] 303 | Ip: 10.204.14.2/24 304 | Vhosts: 305 | VhostProps: 306 | - Network: 172.21.4.1/24 307 | Vhosts: 2 308 | - Network: 172.22.4.1/24 309 | Vhosts: 2 310 | Router: 311 | Loopback: 10.1.2.4/32 312 | Ospf: 313 | - Area: 0.0.0.0 314 | Networks: [10.203.14.2/24, 10.204.14.2/24] 315 | -------------------------------------------------------------------------------- /etc/plugins/ptnbgp.py: -------------------------------------------------------------------------------- 1 | import tega.tree 2 | import tega.subscriber 3 | 4 | class PtnBgp(tega.subscriber.PlugIn): 5 | 6 | def __init__(self): 7 | super().__init__() 8 | 9 | plugins = tega.tree.Cont('plugins') 10 | with self.tx() as t: 11 | plugins.ptnbgp = self.func(self._state) 12 | t.put(plugins.ptnbgp, ephemeral=True) 13 | 14 | def on_notify(self, notifications): 15 | pass 16 | 17 | def on_message(self, channel, tega_id, message): 18 | pass 19 | 20 | def _state(self): 21 | 22 | _routers = ['10.10.1.1', 'pe1', 'pe2', 'pe3', 'pe4', 'rr', 'ce1', 'ce2', 'ce3', 'ce4'] 23 | 24 | self.rpc('plugins.ipam', *_routers) 25 | routers = self.get('ip') 26 | g = globals() 27 | for router, ip in routers.items(): 28 | g[router] = ip.split('/')[0] 29 | plugins = tega.tree.Cont('plugins') 30 | 31 | # Roots 32 | _pe1 = tega.tree.Cont('config-pe1') 33 | _pe2 = tega.tree.Cont('config-pe2') 34 | _pe3 = tega.tree.Cont('config-pe3') 35 | _pe4 = tega.tree.Cont('config-pe4') 36 | _rr = tega.tree.Cont('config-rr') 37 | _ce1 = tega.tree.Cont('config-ce1') 38 | _ce2 = tega.tree.Cont('config-ce2') 39 | _ce3 = tega.tree.Cont('config-ce3') 40 | _ce4 = tega.tree.Cont('config-ce4') 41 | 42 | # Loopback address 43 | _pe1.Router.Loopback = '10.1.1.1/32' 44 | _pe2.Router.Loopback = '10.1.1.2/32' 45 | _pe3.Router.Loopback = '10.1.1.3/32' 46 | _pe4.Router.Loopback = '10.1.1.4/32' 47 | _rr.Router.Loopback = '10.1.1.5/32' 48 | _ce1.Router.Loopback = '10.1.2.1/32' 49 | _ce2.Router.Loopback = '10.1.2.2/32' 50 | _ce3.Router.Loopback = '10.1.2.3/32' 51 | _ce4.Router.Loopback = '10.1.2.4/32' 52 | 53 | # Nodes (vertices) 54 | def _nodes(ptn, l2sw): 55 | return dict(Ptn=ptn, L2sw=l2sw) 56 | _pe1.Ptn.wan.Nodes = _nodes('ptpe1w', 'l2spe1w') 57 | _pe2.Ptn.wan.Nodes = _nodes('ptpe2w', 'l2spe2w') 58 | _pe3.Ptn.wan.Nodes = _nodes('ptpe3w', 'l2spe3w') 59 | _pe4.Ptn.wan.Nodes = _nodes('ptpe4w', 'l2spe4w') 60 | _rr.Ptn.wan.Nodes = _nodes('ptrrw' , 'l2srrw') 61 | _pe1.Ptn.access.Nodes = _nodes('ptpe1a', 'l2spe1a') 62 | _pe2.Ptn.access.Nodes = _nodes('ptpe2a', 'l2spe2a') 63 | _pe3.Ptn.access.Nodes = _nodes('ptpe3a', 'l2spe3a') 64 | _pe4.Ptn.access.Nodes = _nodes('ptpe4a', 'l2spe4a') 65 | _ce1.Ptn.access.Nodes = _nodes('ptce1a', 'l2sce1a') 66 | _ce2.Ptn.access.Nodes = _nodes('ptce2a', 'l2sce2a') 67 | _ce3.Ptn.access.Nodes = _nodes('ptce3a', 'l2sce3a') 68 | _ce4.Ptn.access.Nodes = _nodes('ptce4a', 'l2sce4a') 69 | 70 | # Links (edges) 71 | def _links(localIp, remoteIps): 72 | return dict(LocalIp=localIp, RemoteIps=remoteIps) 73 | _pe1.Ptn.wan.Links = _links(pe1, [pe2, pe3, pe4, rr]) 74 | _pe2.Ptn.wan.Links = _links(pe2, [pe1, pe3, pe4, rr]) 75 | _pe3.Ptn.wan.Links = _links(pe3, [pe1, pe2, pe4, rr]) 76 | _pe4.Ptn.wan.Links = _links(pe4, [pe1, pe2, pe3, rr]) 77 | _rr.Ptn.wan.Links = _links(rr, [pe1, pe2, pe3, pe4]) 78 | _pe1.Ptn.access.Links = _links(pe1, [ce1, ce2]) 79 | _pe2.Ptn.access.Links = _links(pe2, [ce1, ce2]) 80 | _pe3.Ptn.access.Links = _links(pe3, [ce3, ce4]) 81 | _pe4.Ptn.access.Links = _links(pe4, [ce3, ce4]) 82 | _ce1.Ptn.access.Links = _links(ce1, [pe1, pe2]) 83 | _ce2.Ptn.access.Links = _links(ce2, [pe1, pe2]) 84 | _ce3.Ptn.access.Links = _links(ce3, [pe3, pe4]) 85 | _ce4.Ptn.access.Links = _links(ce4, [pe3, pe4]) 86 | 87 | # VPN 88 | def _vpn(vid, vni, peers, ip): 89 | return dict(Vid=vid, Vni=vni, Peers=peers, Ip=ip) 90 | vpn101 = _vpn(101, 1, [pe2, pe3, pe4, rr], '10.200.1.101/24') 91 | vpn102 = _vpn(102, 2, [pe2, pe3, pe4, rr], '10.200.2.101/24') 92 | _pe1.Ptn.wan.L2Vpn = [vpn101, vpn102] 93 | vpn101 = _vpn(101, 1, [pe1, pe3, pe4, rr], '10.200.1.102/24') 94 | vpn102 = _vpn(102, 2, [pe1, pe3, pe4, rr], '10.200.2.102/24') 95 | _pe2.Ptn.wan.L2Vpn = [vpn101, vpn102] 96 | vpn101 = _vpn(101, 1, [pe1, pe2, pe4, rr], '10.200.1.103/24') 97 | vpn102 = _vpn(102, 2, [pe1, pe2, pe4, rr], '10.200.2.103/24') 98 | _pe3.Ptn.wan.L2Vpn = [vpn101, vpn102] 99 | vpn101 = _vpn(101, 1, [pe1, pe2, pe3, rr], '10.200.1.104/24') 100 | vpn102 = _vpn(102, 2, [pe1, pe2, pe3, rr], '10.200.2.104/24') 101 | _pe4.Ptn.wan.L2Vpn = [vpn101, vpn102] 102 | vpn101 = _vpn(101, 1, [pe1, pe2, pe3, pe4], '10.200.1.105/24') 103 | vpn102 = _vpn(102, 2, [pe1, pe2, pe3, pe4], '10.200.2.105/24') 104 | _rr.Ptn.wan.L2Vpn = [vpn101, vpn102] 105 | vpn11 = _vpn(11, 111, [ce1], '10.201.11.1/24') 106 | vpn12 = _vpn(12, 112, [ce2], '10.201.12.1/24') 107 | _pe1.Ptn.access.L2Vpn = [vpn11, vpn12] 108 | vpn11 = _vpn(11, 211, [ce1], '10.202.11.1/24') 109 | vpn12 = _vpn(12, 212, [ce2], '10.202.12.1/24') 110 | _pe2.Ptn.access.L2Vpn = [vpn11, vpn12] 111 | vpn13 = _vpn(13, 313, [ce3], '10.203.13.1/24') 112 | vpn14 = _vpn(14, 314, [ce4], '10.203.14.1/24') 113 | _pe3.Ptn.access.L2Vpn = [vpn13, vpn14] 114 | vpn13 = _vpn(13, 413, [ce3], '10.204.13.1/24') 115 | vpn14 = _vpn(14, 414, [ce4], '10.204.14.1/24') 116 | _pe4.Ptn.access.L2Vpn = [vpn13, vpn14] 117 | vpn111 = _vpn(1, 111, [pe1], '10.201.11.2/24') 118 | vpn211 = _vpn(2, 211, [pe2], '10.202.11.2/24') 119 | _ce1.Ptn.access.L2Vpn = [vpn111, vpn211] 120 | vpn112 = _vpn(1, 112, [pe1], '10.201.12.2/24') 121 | vpn212 = _vpn(2, 212, [pe2], '10.202.12.2/24') 122 | _ce2.Ptn.access.L2Vpn = [vpn112, vpn212] 123 | vpn313 = _vpn(3, 313, [pe3], '10.203.13.2/24') 124 | vpn413 = _vpn(4, 413, [pe4], '10.204.13.2/24') 125 | _ce3.Ptn.access.L2Vpn = [vpn313, vpn413] 126 | vpn314 = _vpn(3, 314, [pe3], '10.203.14.2/24') 127 | vpn414 = _vpn(4, 414, [pe4], '10.204.14.2/24') 128 | _ce4.Ptn.access.L2Vpn = [vpn314, vpn414] 129 | 130 | # BGP 131 | def _neigh(peer, remoteAs, *, nextHopSelf=False, routeReflectorClient=False): 132 | if nextHopSelf: 133 | return dict(Peer=peer, RemoteAs=remoteAs, NextHopSelf=True) 134 | elif routeReflectorClient is True: 135 | return dict(Peer=peer, RemoteAs=remoteAs, RouteReflectorClient=True) 136 | else: 137 | return dict(Peer=peer, RemoteAs=remoteAs) 138 | n100 = _neigh('10.200.1.105', 100, nextHopSelf=True) 139 | n1001 = _neigh('10.201.11.2', 1001) 140 | n1002 = _neigh('10.201.12.2', 1002) 141 | _pe1.Router.Bgp['100'].Neighbors = [n100, n1001, n1002] 142 | n100 = _neigh('10.200.1.105', 100, nextHopSelf=True) 143 | n1001 = _neigh('10.202.11.2', 1001) 144 | n1002 = _neigh('10.202.12.2', 1002) 145 | _pe2.Router.Bgp['100'].Neighbors = [n100, n1001, n1002] 146 | n100 = _neigh('10.200.1.105', 100, nextHopSelf=True) 147 | n1003 = _neigh('10.203.13.2', 1003) 148 | n1004 = _neigh('10.203.14.2', 1004) 149 | _pe3.Router.Bgp['100'].Neighbors = [n100, n1003, n1004] 150 | n100 = _neigh('10.200.1.105', 100, nextHopSelf=True) 151 | n1003 = _neigh('10.204.13.2', 1003) 152 | n1004 = _neigh('10.204.14.2', 1004) 153 | _pe4.Router.Bgp['100'].Neighbors = [n100, n1003, n1004] 154 | n100_101 = _neigh('10.200.1.101', 100, routeReflectorClient=True) 155 | n100_102 = _neigh('10.200.1.102', 100, routeReflectorClient=True) 156 | n100_103 = _neigh('10.200.1.103', 100, routeReflectorClient=True) 157 | n100_104 = _neigh('10.200.1.104', 100, routeReflectorClient=True) 158 | _rr.Router.Bgp['100'].Neighbors = [n100_101, n100_102, n100_103, n100_104] 159 | _rr.Router.EmbeddedBgp = True 160 | n201 = _neigh('10.201.11.1', 100) 161 | n202 = _neigh('10.202.11.1', 100) 162 | _ce1.Router.Bgp['1001'].Neighbors = [n201, n202] 163 | n201 = _neigh('10.201.12.1', 100) 164 | n202 = _neigh('10.202.12.1', 100) 165 | _ce2.Router.Bgp['1002'].Neighbors = [n201, n202] 166 | n203 = _neigh('10.203.13.1', 100) 167 | n204 = _neigh('10.204.13.1', 100) 168 | _ce3.Router.Bgp['1003'].Neighbors = [n203, n204] 169 | n203 = _neigh('10.203.14.1', 100) 170 | n204 = _neigh('10.204.14.1', 100) 171 | _ce4.Router.Bgp['1004'].Neighbors = [n203, n204] 172 | 173 | # Vhosts 174 | def _vhosts(*args): 175 | return [dict(Network=n, Vhosts=2) for n in args] 176 | _ce1.Vhosts.VhostProps = _vhosts('172.21.1.1/24', '172.22.1.1/24') 177 | _ce2.Vhosts.VhostProps = _vhosts('172.21.2.1/24', '172.22.2.1/24') 178 | _ce3.Vhosts.VhostProps = _vhosts('172.21.3.1/24', '172.22.3.1/24') 179 | _ce4.Vhosts.VhostProps = _vhosts('172.21.4.1/24', '172.22.4.1/24') 180 | 181 | with self.tx() as t: 182 | t.put(_pe1) 183 | t.put(_pe2) 184 | t.put(_pe3) 185 | t.put(_pe4) 186 | t.put(_rr) 187 | t.put(_ce1) 188 | t.put(_ce2) 189 | t.put(_ce3) 190 | t.put(_ce4) 191 | -------------------------------------------------------------------------------- /etc/ptn-bgp.yaml: -------------------------------------------------------------------------------- 1 | # iBGP/eBGP simulation 2 | # 3 | pe1: 4 | Ptn: 5 | wan: 6 | Nodes: 7 | Ptn: ptpe1w 8 | L2sw: l2spe1w 9 | Links: 10 | LocalIp: ${pe1} 11 | RemoteIps: [${pe2}, ${pe3}, ${pe4}, ${rr}] 12 | L2Vpn: 13 | - Vid: 101 14 | Vni: 1 15 | Peers: [${pe2}, ${pe3}, ${pe4}, ${rr}] 16 | Ip: 10.200.1.101/24 17 | - Vid: 102 18 | Vni: 2 19 | Peers: [${pe2}, ${pe3}, ${pe4}, ${rr}] 20 | Ip: 10.200.2.101/24 21 | access: 22 | Nodes: 23 | Ptn: ptpe1a 24 | L2sw: l2spe1a 25 | Links: 26 | LocalIp: ${pe1} 27 | RemoteIps: [${ce1}, ${ce2}] 28 | L2Vpn: 29 | - Vid: 11 30 | Vni: 111 31 | Peers: [${ce1}] 32 | Ip: 10.201.11.1/24 33 | - Vid: 12 34 | Vni: 112 35 | Peers: [${ce2}] 36 | Ip: 10.201.12.1/24 37 | Router: 38 | Loopback: 10.1.1.1/32 39 | Bgp: 40 | "100": 41 | Neighbors: 42 | - Peer: 10.200.1.105 43 | RemoteAs: 100 44 | NextHopSelf: true 45 | - Peer: 10.201.11.2 46 | RemoteAs: 1001 47 | - Peer: 10.201.12.2 48 | RemoteAs: 1002 49 | pe2: 50 | Ptn: 51 | wan: 52 | Nodes: 53 | Ptn: ptpe2w 54 | L2sw: l2spe2w 55 | Links: 56 | LocalIp: ${pe2} 57 | RemoteIps: [${pe1}, ${pe3}, ${pe4}, ${rr}] 58 | L2Vpn: 59 | - Vid: 101 60 | Vni: 1 61 | Peers: [${pe1}, ${pe3}, ${pe4}, ${rr}] 62 | Ip: 10.200.1.102/24 63 | - Vid: 102 64 | Vni: 2 65 | Peers: [${pe2}, ${pe3}, ${pe4}, ${rr}] 66 | Ip: 10.200.2.102/24 67 | access: 68 | Nodes: 69 | Ptn: ptpe2a 70 | L2sw: l2spe2a 71 | Links: 72 | LocalIp: ${pe2} 73 | RemoteIps: [${ce1}, ${ce2}] 74 | L2Vpn: 75 | - Vid: 11 76 | Vni: 211 77 | Peers: [${ce1}] 78 | Ip: 10.202.11.1/24 79 | - Vid: 12 80 | Vni: 212 81 | Peers: [${ce2}] 82 | Ip: 10.202.12.1/24 83 | Router: 84 | Loopback: 10.1.1.2/32 85 | Bgp: 86 | "100": 87 | Neighbors: 88 | - Peer: 10.200.1.105 89 | RemoteAs: 100 90 | NextHopSelf: true 91 | - Peer: 10.202.11.2 92 | RemoteAs: 1001 93 | - Peer: 10.202.12.2 94 | RemoteAs: 1002 95 | pe3: 96 | Ptn: 97 | wan: 98 | Nodes: 99 | Ptn: ptpe3w 100 | L2sw: l2spe3w 101 | Links: 102 | LocalIp: ${pe3} 103 | RemoteIps: [${pe1}, ${pe2}, ${pe4}, ${rr}] 104 | L2Vpn: 105 | - Vid: 101 106 | Vni: 1 107 | Peers: [${pe1}, ${pe2}, ${pe4}, ${rr}] 108 | Ip: 10.200.1.103/24 109 | - Vid: 102 110 | Vni: 2 111 | Peers: [${pe1}, ${pe2}, ${pe4}, ${rr}] 112 | Ip: 10.200.2.103/24 113 | access: 114 | Nodes: 115 | Ptn: ptpe3a 116 | L2sw: l2spe3a 117 | Links: 118 | LocalIp: ${pe3} 119 | RemoteIps: [${ce3}, ${ce4}] 120 | L2Vpn: 121 | - Vid: 13 122 | Vni: 313 123 | Peers: [${ce3}] 124 | Ip: 10.203.13.1/24 125 | - Vid: 14 126 | Vni: 314 127 | Peers: [${ce4}] 128 | Ip: 10.203.14.1/24 129 | Router: 130 | Loopback: 10.1.1.3/32 131 | Bgp: 132 | "100": 133 | Neighbors: 134 | - Peer: 10.200.1.105 135 | RemoteAs: 100 136 | NextHopSelf: true 137 | - Peer: 10.203.13.2 138 | RemoteAs: 1003 139 | - Peer: 10.203.14.2 140 | RemoteAs: 1004 141 | pe4: 142 | Ptn: 143 | wan: 144 | Nodes: 145 | Ptn: ptpe4w 146 | L2sw: l2spe4w 147 | Links: 148 | LocalIp: ${pe4} 149 | RemoteIps: [${pe1}, ${pe2}, ${pe3}, ${rr}] 150 | L2Vpn: 151 | - Vid: 101 152 | Vni: 1 153 | Peers: [${pe1}, ${pe2}, ${pe3}, ${rr}] 154 | Ip: 10.200.1.104/24 155 | - Vid: 102 156 | Vni: 2 157 | Peers: [${pe1}, ${pe2}, ${pe3}, ${rr}] 158 | Ip: 10.200.2.104/24 159 | access: 160 | Nodes: 161 | Ptn: ptpe4a 162 | L2sw: l2spe4a 163 | Links: 164 | LocalIp: ${pe4} 165 | RemoteIps: [${ce3}, ${ce4}] 166 | L2Vpn: 167 | - Vid: 13 168 | Vni: 413 169 | Peers: [${ce3}] 170 | Ip: 10.204.13.1/24 171 | - Vid: 14 172 | Vni: 414 173 | Peers: [${ce4}] 174 | Ip: 10.204.14.1/24 175 | Router: 176 | Loopback: 10.1.1.4/32 177 | Bgp: 178 | "100": 179 | Neighbors: 180 | - Peer: 10.200.1.105 181 | RemoteAs: 100 182 | NextHopSelf: true 183 | - Peer: 10.204.13.2 184 | RemoteAs: 1003 185 | - Peer: 10.204.14.2 186 | RemoteAs: 1004 187 | rr: 188 | Ptn: 189 | wan: 190 | Nodes: 191 | Ptn: ptrrw 192 | L2sw: l2srrw 193 | Links: 194 | LocalIp: ${rr} 195 | RemoteIps: [${pe1}, ${pe2}, ${pe3}, ${pe4}] 196 | L2Vpn: 197 | - Vid: 101 198 | Vni: 1 199 | Peers: [${pe1}, ${pe2}, ${pe3}, ${pe4}] 200 | Ip: 10.200.1.105/24 201 | - Vid: 102 202 | Vni: 2 203 | Peers: [${pe1}, ${pe2}, ${pe3}, ${pe4}] 204 | Ip: 10.200.2.105/24 205 | Router: 206 | Loopback: 10.1.1.5/32 207 | EmbeddedBgp: true 208 | Bgp: 209 | "100": 210 | Neighbors: 211 | - Peer: 10.200.1.101 212 | RemoteAs: 100 213 | RouteReflectorClient: true 214 | - Peer: 10.200.1.102 215 | RemoteAs: 100 216 | RouteReflectorClient: true 217 | - Peer: 10.200.1.103 218 | RemoteAs: 100 219 | RouteReflectorClient: true 220 | - Peer: 10.200.1.104 221 | RemoteAs: 100 222 | RouteReflectorClient: true 223 | ce1: 224 | Ptn: 225 | access: 226 | Nodes: 227 | Ptn: ptce1a 228 | L2sw: l2sce1a 229 | Links: 230 | LocalIp: ${ce1} 231 | RemoteIps: [${pe1}, ${pe2}] 232 | L2Vpn: 233 | - Vid: 1 234 | Vni: 111 235 | Peers: [${pe1}] 236 | Ip: 10.201.11.2/24 237 | - Vid: 2 238 | Vni: 211 239 | Peers: [${pe2}] 240 | Ip: 10.202.11.2/24 241 | Vhosts: 242 | VhostProps: 243 | - Network: 172.21.1.1/24 244 | Vhosts: 2 245 | - Network: 172.22.1.1/24 246 | Vhosts: 2 247 | Router: 248 | Loopback: 10.1.2.1/32 249 | Bgp: 250 | "1001": 251 | Neighbors: 252 | - Peer: 10.201.11.1 253 | RemoteAs: 100 254 | - Peer: 10.202.11.1 255 | RemoteAs: 100 256 | ce2: 257 | Ptn: 258 | access: 259 | Nodes: 260 | Ptn: ptce2a 261 | L2sw: l2sce2a 262 | Links: 263 | LocalIp: ${ce2} 264 | RemoteIps: [${pe1}, ${pe2}] 265 | L2Vpn: 266 | - Vid: 1 267 | Vni: 112 268 | Peers: [${pe1}] 269 | Ip: 10.201.12.2/24 270 | - Vid: 2 271 | Vni: 212 272 | Peers: [${pe2}] 273 | Ip: 10.202.12.2/24 274 | Vhosts: 275 | VhostProps: 276 | - Network: 172.21.2.1/24 277 | Vhosts: 2 278 | - Network: 172.22.2.1/24 279 | Vhosts: 2 280 | Router: 281 | Loopback: 10.1.2.2/32 282 | Bgp: 283 | "1002": 284 | Neighbors: 285 | - Peer: 10.201.12.1 286 | RemoteAs: 100 287 | - Peer: 10.202.12.1 288 | RemoteAs: 100 289 | ce3: 290 | Ptn: 291 | access: 292 | Nodes: 293 | Ptn: ptce3a 294 | L2sw: l2sce3a 295 | Links: 296 | LocalIp: ${ce3} 297 | RemoteIps: [${pe3}, ${pe4}] 298 | L2Vpn: 299 | - Vid: 3 300 | Vni: 313 301 | Peers: [${pe3}] 302 | Ip: 10.203.13.2/24 303 | - Vid: 4 304 | Vni: 413 305 | Peers: [${pe4}] 306 | Ip: 10.204.13.2/24 307 | Vhosts: 308 | VhostProps: 309 | - Network: 172.21.3.1/24 310 | Vhosts: 2 311 | - Network: 172.22.3.1/24 312 | Vhosts: 2 313 | Router: 314 | Loopback: 10.1.2.3/32 315 | Bgp: 316 | "1003": 317 | Neighbors: 318 | - Peer: 10.203.13.1 319 | RemoteAs: 100 320 | - Peer: 10.204.13.1 321 | RemoteAs: 100 322 | ce4: 323 | Ptn: 324 | access: 325 | Nodes: 326 | Ptn: ptce4a 327 | L2sw: l2sce4a 328 | Links: 329 | LocalIp: ${ce4} 330 | RemoteIps: [${pe3}, ${pe4}] 331 | L2Vpn: 332 | - Vid: 3 333 | Vni: 314 334 | Peers: [${pe3}] 335 | Ip: 10.203.14.2/24 336 | - Vid: 4 337 | Vni: 414 338 | Peers: [${pe4}] 339 | Ip: 10.204.14.2/24 340 | Vhosts: 341 | VhostProps: 342 | - Network: 172.21.4.1/24 343 | Vhosts: 2 344 | - Network: 172.22.4.1/24 345 | Vhosts: 2 346 | Router: 347 | Loopback: 10.1.2.4/32 348 | Bgp: 349 | "1004": 350 | Neighbors: 351 | - Peer: 10.203.14.1 352 | RemoteAs: 100 353 | - Peer: 10.204.14.1 354 | RemoteAs: 100 355 | -------------------------------------------------------------------------------- /doc/RPI.md: -------------------------------------------------------------------------------- 1 | # Setting up the software on Raspberry Pi 2 | 3 | 2015/12/27-2015/12/30 4 | 5 | ![Raspberry Pi](https://raw.github.com/araobp/neutron-lan/master/misc/rpi.png) 6 | 7 | My Raspberry Pi 1 Model B that I bought in Akihabara in Feb 2014. 8 | 9 | ## [Step1] Hypriot (Debian Linux with docker pre-installed) 10 | I used 8Gbytes SD memory card. zenmap is to find IP address that DHCP server (on my home gateway) assigned to my Raspberry Pi. 11 | - [Hypriot Docker image](http://blog.hypriot.com/downloads/) 12 | - [Win32 Disk Imager](http://sourceforge.net/projects/win32diskimager/) 13 | - [zenmap](https://nmap.org/) 14 | 15 | ## [Step2] Python3.5 16 | Download Python3.5, build and install it: 17 | ``` 18 | $ wget https://www.python.org/ftp/python/3.5.1/Python-3.5.1.tgz 19 | $ tar zxvf Python-3.5.1.tgz 20 | $ cd Python-3.5.1 21 | $ autoconf 22 | $ ./configure 23 | $ make; make install 24 | ``` 25 | 26 | ###Python3.5 packages required for nlan/tega 27 | - https://github.com/tornadoweb/tornado 28 | - pip3.5 install mako 29 | - pip3.5 install httplib2 30 | - pip3.5 install pyyaml 31 | - apt-get install libncurses-dev 32 | - pip3.5 install readline 33 | 34 | ## [Step3] Go 35 | ``` 36 | $ cd $HOME 37 | $ apt-get install gcc 38 | $ apt-get install bzip2 39 | $ curl http://dave.cheney.net/paste/go-linux-arm-bootstrap-c788a8e.tbz | tar xj 40 | $ curl https://storage.googleapis.com/golang/go1.5.2.src.tar.gz | tar xz 41 | $ export GOROOT_BOOTSTRAP=/root/go-linux-arm-bootstrap 42 | $ cd $HOME/go/src 43 | $ ./make.bash 44 | $ ulimit -s 1024 45 | ``` 46 | Then append the following two lines to $HOME/.bashrc: 47 | ``` 48 | export GOROOT=$HOME/go 49 | export GOPATH=$HOME/work 50 | export PATH=$PATH:$GOROOT/bin:$GOPATH/bin 51 | ``` 52 | 53 | Note: it took two hours to complete the building processes. 54 | 55 | [Reference] http://dave.cheney.net/2015/09/04/building-go-1-5-on-the-raspberry-pi 56 | 57 | ## [Step4] protocol buffers 58 | ``` 59 | $ apt-get clone https://github.com/google/protobuf 60 | $ apt-get install autoconf 61 | $ apt-get install unzip 62 | $ apt-get install libtool 63 | $ apt-get install g++ 64 | $ apt-get install make 65 | $ cd protobuf 66 | $ ./autogen.sh 67 | $ ./configure 68 | $ make 69 | $ make install 70 | $ go get github.com/golang/protobuf/proto 71 | $ go get github.com/golang/protobuf/protoc-gen-go 72 | $ cp ~/work/src/github.com/golang/protobuf/protoc-gen-go/protoc-gen-go ~/work/bin 73 | ``` 74 | Note: it took hours... 75 | 76 | Do not forget to append the following line to your .bashrc: 77 | ``` 78 | export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARLY_PATH 79 | ``` 80 | 81 | ## [Step5] tega db 82 | ``` 83 | $ go get github.com/araobp/tega/driver 84 | ``` 85 | 86 | ## [Step6] Networking modules 87 | 88 | ### ip-command-related capabilities 89 | netns has already been supported on this kernel, so I do not need to reconfigure the kernel to add netns. 90 | 91 | ### Linux Bridge 92 | ``` 93 | $ apt-get install bridge-utils 94 | ``` 95 | Confirm that docker0 has already been created: 96 | ``` 97 | $ brctl show 98 | bridge name bridge id STP enabled interfaces 99 | docker0 8000.024244da82d8 no 100 | ``` 101 | ### GoBGP 102 | ``` 103 | $ go get github.com/osrg/gpbgp/gobgpd 104 | $ go get github.com/osrg/gpbgp/gobgp 105 | ``` 106 | Note: GoBGP is optinal -- you may run gobgpd instead of quagga/bgp on Route Reflector container. 107 | 108 | ### Open vSwitch 109 | Compile and build deb packages: 110 | ``` 111 | $ wget http://openvswitch.org/releases/openvswitch-2.4.0.tar.gz 112 | $ tar zxvf openvswitch-2.4.0.tar.gz 113 | $ cd openvswitch-2.4.0 114 | $ apt-get install build-essential fakeroot 115 | $ apt-get install debhelper autoconf automake libssl-dev bzip2 openssl graphviz python-all procps python-qt4 python-zopeinterface python-twisted-conch libtool 116 | $ `DEB_BUILD_OPTIONS='parallel=8 nocheck' fakeroot debian/rules binary` 117 | ``` 118 | 119 | Confirm that deb packages have been created: 120 | ``` 121 | $ cd 122 | $ ls -F 123 | : 124 | openvswitch-2.4.0/ 125 | openvswitch-common_2.4.0-1_armhf.deb 126 | openvswitch-datapath-dkms_2.4.0-1_all.deb 127 | openvswitch-datapath-source_2.4.0-1_all.deb 128 | openvswitch-dbg_2.4.0-1_armhf.deb 129 | openvswitch-ipsec_2.4.0-1_armhf.deb 130 | openvswitch-pki_2.4.0-1_all.deb 131 | openvswitch-switch_2.4.0-1_armhf.deb 132 | openvswitch-test_2.4.0-1_all.deb 133 | openvswitch-testcontroller_2.4.0-1_armhf.deb 134 | openvswitch-vtep_2.4.0-1_armhf.deb 135 | : 136 | ``` 137 | Then install part of the deb packages: 138 | ``` 139 | $ cd 140 | $ apt-get install dkms uuid-runtime 141 | $ dpkg -i openvswitch-common_2.4.0-1_armhf.deb 142 | $ dpkg -i openvswitch-switch_2.4.0-1_armhf.deb 143 | $ dpkg -i openvswitch-datapath-dkms_2.4.0-1_all.deb 144 | ``` 145 | 146 | ## [Step7] Pulling rpi-raspbian docker image 147 | ``` 148 | $ docker pull resin/rpi-raspbian 149 | ``` 150 | 151 | ## [Step8] Creating "router" container 152 | 153 | ### Installing required utilities 154 | ``` 155 | $ docker run --name base -i -t resin/rpi-raspbian /bin/bash 156 | root@dce29feab2aa:/# apt-get update 157 | root@dce29feab2aa:/# apt-get install ssh 158 | root@dce29feab2aa:/# apt-get install iputils-ping 159 | root@dce29feab2aa:/# apt-get install bridge-utils 160 | root@dce29feab2aa:/# apt-get install quagga 161 | root@dce29feab2aa:/# apt-get install vim 162 | root@dce29feab2aa:/# cd 163 | root@dce29feab2aa:/# mkdir bin 164 | ``` 165 | ### Allowing SSH root loging 166 | Append the following to /etc/ssh/sshd_config to allow ssh root login to the Docker container: 167 | ``` 168 | #PermitRootLogin wihtout-password 169 | PermitRootLogin yes 170 | ``` 171 | Then 172 | ``` 173 | $ /etc/init.d/ssh start 174 | ``` 175 | ### Setting up Quagga 176 | ``` 177 | $ cd /etc/quagga 178 | $ touch zebra.conf 179 | $ touch ospfd.conf 180 | $ touch bgpd.conf 181 | ``` 182 | 183 | Then edit "/etc/quagga/daemons" as follows: 184 | ``` 185 | zebra=yes 186 | bgpd=yes 187 | ospfd=yes 188 | ospf6d=no 189 | ripd=no 190 | ripngd=no 191 | isisd=no 192 | babeld=no 193 | ``` 194 | 195 | ### Copying additional packages and binaries to the container 196 | Copy ovs packages and gobgp to the container: 197 | ``` 198 | $ ip addr show dev eth0 199 | 18: eth0@if19: mtu 1500 qdisc noqueue state UP group default 200 | link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff 201 | inet 172.17.0.2/16 scope global eth0 202 | valid_lft forever preferred_lft forever 203 | inet6 fe80::42:acff:fe11:2/64 scope link 204 | valid_lft forever preferred_lft forever 205 | ``` 206 | 207 | At the docker host, 208 | ``` 209 | $ cd 210 | $ scp openvswitch-common_2.4.0-1_armhf.deb root@172.17.0.2:~ 211 | $ scp openvswitch-switch_2.4.0-1_armhf.deb root@172.17.0.2:~ 212 | $ cd ~/work/bin 213 | $ scp gobgp root@172.17.0.2:~/bin 214 | $ scp gobgpd root@172.17.0.2:~/bin 215 | ``` 216 | 217 | At the container, 218 | ``` 219 | $ dpkg -i openvswitch-common_2.4.0-1_armhf.deb 220 | $ dpkg -i openvswitch-switch_2.4.0-1_armhf.deb 221 | ``` 222 | If you encounter dependency problems, try: 223 | ``` 224 | $ apt-get -f install 225 | ``` 226 | ### Commit the change 227 | ``` 228 | $ docker commit base router 229 | $ docker images 230 | REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE 231 | router latest 47057103372d 6 minutes ago 165.1 MB 232 | resin/rpi-raspbian latest e97a8531a526 5 days ago 80.28 MB 233 | hypriot/rpi-swarm latest 039c550f6208 7 weeks ago 10.92 MB 234 | ``` 235 | ## [Step9] nlan installation 236 | 237 | Follow the setup instruction on [README.md](../README.md). 238 | 239 | ``` 240 | $ docker ps 241 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 242 | dad01276cbb9 nlan/agent:ver0.1 "/bin/sh -c 'service " 7 minutes ago Up 7 minutes ce4 243 | 6babcf9cfed6 nlan/agent:ver0.1 "/bin/sh -c 'service " 7 minutes ago Up 7 minutes ce3 244 | 50d0d0a82dee nlan/agent:ver0.1 "/bin/sh -c 'service " 7 minutes ago Up 7 minutes ce2 245 | 02165f398b7a nlan/agent:ver0.1 "/bin/sh -c 'service " 7 minutes ago Up 7 minutes ce1 246 | d621e51bd766 nlan/agent:ver0.1 "/bin/sh -c 'service " 7 minutes ago Up 7 minutes rr 247 | 475e8aeb37ed nlan/agent:ver0.1 "/bin/sh -c 'service " 7 minutes ago Up 7 minutes pe4 248 | 1a862e95ec36 nlan/agent:ver0.1 "/bin/sh -c 'service " 7 minutes ago Up 7 minutes pe3 249 | 13b6228fb516 nlan/agent:ver0.1 "/bin/sh -c 'service " 8 minutes ago Up 7 minutes pe2 250 | 692c49789d53 nlan/agent:ver0.1 "/bin/sh -c 'service " 8 minutes ago Up 8 minutes pe1 251 | $ ./master.sh ptn-bgp 252 | ``` 253 | ## [Step10] Using the simulated WAN 254 | ``` 255 | $ cd docker 256 | $ ./ssh.sh ce1 257 | root@ce1:~# ip route 258 | default via 172.17.0.1 dev eth0 259 | 10.1.1.1 via 10.201.11.1 dev int_br111 proto zebra 260 | 10.1.1.2 via 10.202.11.1 dev int_br211 proto zebra 261 | 10.1.1.3 via 10.202.11.1 dev int_br211 proto zebra 262 | 10.1.1.4 via 10.202.11.1 dev int_br211 proto zebra 263 | 10.1.1.5 via 10.202.11.1 dev int_br211 proto zebra 264 | 10.1.2.2 via 10.202.11.1 dev int_br211 proto zebra 265 | 10.1.2.3 via 10.202.11.1 dev int_br211 proto zebra 266 | 10.1.2.4 via 10.202.11.1 dev int_br211 proto zebra 267 | 10.10.10.0/24 dev eth0 proto kernel scope link src 10.10.10.6 268 | 10.200.1.0/24 via 10.202.11.1 dev int_br211 proto zebra 269 | 10.200.2.0/24 via 10.202.11.1 dev int_br211 proto zebra 270 | 10.201.11.0/24 dev int_br111 proto kernel scope link src 10.201.11.2 271 | 10.201.12.0/24 via 10.201.11.1 dev int_br111 proto zebra 272 | 10.202.11.0/24 dev int_br211 proto kernel scope link src 10.202.11.2 273 | 10.202.12.0/24 via 10.202.11.1 dev int_br211 proto zebra 274 | 10.203.13.0/24 via 10.202.11.1 dev int_br211 proto zebra 275 | 10.203.14.0/24 via 10.202.11.1 dev int_br211 proto zebra 276 | 10.204.13.0/24 via 10.202.11.1 dev int_br211 proto zebra 277 | 10.204.14.0/24 via 10.202.11.1 dev int_br211 proto zebra 278 | 172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.7 279 | 172.21.1.0/24 dev br_172.21.1.1 proto kernel scope link src 172.21.1.1 280 | 172.21.2.0/24 via 10.202.11.1 dev int_br211 proto zebra 281 | 172.21.3.0/24 via 10.202.11.1 dev int_br211 proto zebra 282 | 172.21.4.0/24 via 10.202.11.1 dev int_br211 proto zebra 283 | 172.22.1.0/24 dev br_172.22.1.1 proto kernel scope link src 172.22.1.1 284 | 172.22.2.0/24 via 10.202.11.1 dev int_br211 proto zebra 285 | 172.22.3.0/24 via 10.202.11.1 dev int_br211 proto zebra 286 | 172.22.4.0/24 via 10.202.11.1 dev int_br211 proto zebra 287 | 288 | root@ce1:~# ping 172.22.3.3 289 | PING 172.22.3.3 (172.22.3.3) 56(84) bytes of data. 290 | 64 bytes from 172.22.3.3: icmp_seq=1 ttl=61 time=19.1 ms 291 | 64 bytes from 172.22.3.3: icmp_seq=2 ttl=61 time=2.11 ms 292 | 64 bytes from 172.22.3.3: icmp_seq=3 ttl=61 time=2.06 ms 293 | ^C 294 | --- 172.22.3.3 ping statistics --- 295 | 3 packets transmitted, 3 received, 0% packet loss, time 2003ms 296 | rtt min/avg/max/mdev = 2.067/7.785/19.178/8.056 ms 297 | 298 | root@ce1:~# vtysh 299 | 300 | Hello, this is Quagga (version 0.99.23.1). 301 | Copyright 1996-2005 Kunihiro Ishiguro, et al. 302 | 303 | ce1# show ip route 304 | Codes: K - kernel route, C - connected, S - static, R - RIP, 305 | O - OSPF, I - IS-IS, B - BGP, A - Babel, 306 | > - selected route, * - FIB route 307 | 308 | K>* 0.0.0.0/0 via 172.17.0.1, eth0 309 | B>* 10.1.1.1/32 [20/0] via 10.201.11.1, int_br111, 00:12:51 310 | B>* 10.1.1.2/32 [20/0] via 10.202.11.1, int_br211, 00:12:55 311 | B>* 10.1.1.3/32 [20/0] via 10.202.11.1, int_br211, 00:12:25 312 | B>* 10.1.1.4/32 [20/0] via 10.202.11.1, int_br211, 00:12:25 313 | B>* 10.1.1.5/32 [20/0] via 10.202.11.1, int_br211, 00:12:25 314 | C>* 10.1.2.1/32 is directly connected, lo 315 | B>* 10.1.2.2/32 [20/0] via 10.202.11.1, int_br211, 00:12:25 316 | B>* 10.1.2.3/32 [20/0] via 10.202.11.1, int_br211, 00:12:25 317 | B>* 10.1.2.4/32 [20/0] via 10.202.11.1, int_br211, 00:12:25 318 | C>* 10.10.10.0/24 is directly connected, eth0 319 | B>* 10.200.1.0/24 [20/0] via 10.202.11.1, int_br211, 00:12:55 320 | B>* 10.200.2.0/24 [20/0] via 10.202.11.1, int_br211, 00:12:55 321 | C>* 10.201.11.0/24 is directly connected, int_br111 322 | B>* 10.201.12.0/24 [20/0] via 10.201.11.1, int_br111, 00:12:51 323 | C>* 10.202.11.0/24 is directly connected, int_br211 324 | B>* 10.202.12.0/24 [20/0] via 10.202.11.1, int_br211, 00:12:55 325 | B>* 10.203.13.0/24 [20/0] via 10.202.11.1, int_br211, 00:12:25 326 | B>* 10.203.14.0/24 [20/0] via 10.202.11.1, int_br211, 00:12:25 327 | B>* 10.204.13.0/24 [20/0] via 10.202.11.1, int_br211, 00:12:25 328 | B>* 10.204.14.0/24 [20/0] via 10.202.11.1, int_br211, 00:12:25 329 | C>* 127.0.0.0/8 is directly connected, lo 330 | C>* 172.17.0.0/16 is directly connected, eth0 331 | C>* 172.21.1.0/24 is directly connected, br_172.21.1.1 332 | B>* 172.21.2.0/24 [20/0] via 10.202.11.1, int_br211, 00:12:25 333 | B>* 172.21.3.0/24 [20/0] via 10.202.11.1, int_br211, 00:12:25 334 | B>* 172.21.4.0/24 [20/0] via 10.202.11.1, int_br211, 00:12:25 335 | C>* 172.22.1.0/24 is directly connected, br_172.22.1.1 336 | B>* 172.22.2.0/24 [20/0] via 10.202.11.1, int_br211, 00:12:25 337 | B>* 172.22.3.0/24 [20/0] via 10.202.11.1, int_br211, 00:12:25 338 | ``` 339 | 340 | -------------------------------------------------------------------------------- /doc/GOBGP.md: -------------------------------------------------------------------------------- 1 | ## Using gobgp command 2 | 3 | Use [gobgp.sh](../scripts/gobgp.sh) to issue gobgp commands to "rr" container. 4 | 5 | ### global rib 6 | ``` 7 | $ ./gobgp.sh global rib 8 | Network Next Hop AS_PATH Age Attrs 9 | *> 10.1.1.1/32 10.200.1.101 00:19:06 [{Origin: ?} {Med: 0} {LocalPref: 100}] 10 | *> 10.1.1.2/32 10.200.1.102 00:19:35 [{Origin: ?} {Med: 0} {LocalPref: 100}] 11 | *> 10.1.1.3/32 10.200.1.103 00:18:57 [{Origin: ?} {Med: 0} {LocalPref: 100}] 12 | *> 10.1.1.4/32 10.200.1.104 00:19:08 [{Origin: ?} {Med: 0} {LocalPref: 100}] 13 | *> 10.1.2.1/32 10.200.1.101 1001 00:19:06 [{Origin: ?} {Med: 0} {LocalPref: 100}] 14 | * 10.1.2.1/32 10.200.1.102 1001 00:19:35 [{Origin: ?} {Med: 0} {LocalPref: 100}] 15 | *> 10.1.2.2/32 10.200.1.101 1002 00:19:06 [{Origin: ?} {Med: 0} {LocalPref: 100}] 16 | * 10.1.2.2/32 10.200.1.102 1002 00:19:35 [{Origin: ?} {Med: 0} {LocalPref: 100}] 17 | *> 10.1.2.3/32 10.200.1.103 1003 00:18:57 [{Origin: ?} {Med: 0} {LocalPref: 100}] 18 | * 10.1.2.3/32 10.200.1.104 1003 00:19:08 [{Origin: ?} {Med: 0} {LocalPref: 100}] 19 | *> 10.1.2.4/32 10.200.1.103 1004 00:18:57 [{Origin: ?} {Med: 0} {LocalPref: 100}] 20 | * 10.1.2.4/32 10.200.1.104 1004 00:19:08 [{Origin: ?} {Med: 0} {LocalPref: 100}] 21 | *> 10.10.10.0/24 10.200.1.101 00:19:06 [{Origin: ?} {Med: 0} {LocalPref: 100}] 22 | * 10.10.10.0/24 10.200.1.102 00:19:35 [{Origin: ?} {Med: 0} {LocalPref: 100}] 23 | * 10.10.10.0/24 10.200.1.103 00:18:57 [{Origin: ?} {Med: 0} {LocalPref: 100}] 24 | * 10.10.10.0/24 10.200.1.104 00:19:08 [{Origin: ?} {Med: 0} {LocalPref: 100}] 25 | *> 10.200.1.0/24 10.200.1.101 00:19:06 [{Origin: ?} {Med: 0} {LocalPref: 100}] 26 | * 10.200.1.0/24 10.200.1.102 00:19:35 [{Origin: ?} {Med: 0} {LocalPref: 100}] 27 | * 10.200.1.0/24 10.200.1.103 00:18:57 [{Origin: ?} {Med: 0} {LocalPref: 100}] 28 | * 10.200.1.0/24 10.200.1.104 00:19:08 [{Origin: ?} {Med: 0} {LocalPref: 100}] 29 | *> 10.200.2.0/24 10.200.1.101 00:19:06 [{Origin: ?} {Med: 0} {LocalPref: 100}] 30 | * 10.200.2.0/24 10.200.1.102 00:19:35 [{Origin: ?} {Med: 0} {LocalPref: 100}] 31 | * 10.200.2.0/24 10.200.1.103 00:18:57 [{Origin: ?} {Med: 0} {LocalPref: 100}] 32 | * 10.200.2.0/24 10.200.1.104 00:19:08 [{Origin: ?} {Med: 0} {LocalPref: 100}] 33 | *> 10.201.11.0/24 10.200.1.101 00:19:06 [{Origin: ?} {Med: 0} {LocalPref: 100}] 34 | *> 10.201.12.0/24 10.200.1.101 00:19:06 [{Origin: ?} {Med: 0} {LocalPref: 100}] 35 | *> 10.202.11.0/24 10.200.1.102 00:19:35 [{Origin: ?} {Med: 0} {LocalPref: 100}] 36 | *> 10.202.12.0/24 10.200.1.102 00:19:35 [{Origin: ?} {Med: 0} {LocalPref: 100}] 37 | *> 10.203.13.0/24 10.200.1.103 00:18:57 [{Origin: ?} {Med: 0} {LocalPref: 100}] 38 | *> 10.203.14.0/24 10.200.1.103 00:18:57 [{Origin: ?} {Med: 0} {LocalPref: 100}] 39 | *> 10.204.13.0/24 10.200.1.104 00:19:08 [{Origin: ?} {Med: 0} {LocalPref: 100}] 40 | *> 10.204.14.0/24 10.200.1.104 00:19:08 [{Origin: ?} {Med: 0} {LocalPref: 100}] 41 | *> 172.17.0.0/16 10.200.1.101 00:19:06 [{Origin: ?} {Med: 0} {LocalPref: 100}] 42 | * 172.17.0.0/16 10.200.1.102 00:19:35 [{Origin: ?} {Med: 0} {LocalPref: 100}] 43 | * 172.17.0.0/16 10.200.1.103 00:18:57 [{Origin: ?} {Med: 0} {LocalPref: 100}] 44 | * 172.17.0.0/16 10.200.1.104 00:19:08 [{Origin: ?} {Med: 0} {LocalPref: 100}] 45 | *> 172.21.1.0/24 10.200.1.101 1001 00:19:06 [{Origin: ?} {Med: 0} {LocalPref: 100}] 46 | * 172.21.1.0/24 10.200.1.102 1001 00:19:35 [{Origin: ?} {Med: 0} {LocalPref: 100}] 47 | *> 172.21.2.0/24 10.200.1.101 1002 00:19:06 [{Origin: ?} {Med: 0} {LocalPref: 100}] 48 | * 172.21.2.0/24 10.200.1.102 1002 00:19:35 [{Origin: ?} {Med: 0} {LocalPref: 100}] 49 | *> 172.21.3.0/24 10.200.1.103 1003 00:18:57 [{Origin: ?} {Med: 0} {LocalPref: 100}] 50 | * 172.21.3.0/24 10.200.1.104 1003 00:19:08 [{Origin: ?} {Med: 0} {LocalPref: 100}] 51 | *> 172.21.4.0/24 10.200.1.103 1004 00:18:57 [{Origin: ?} {Med: 0} {LocalPref: 100}] 52 | * 172.21.4.0/24 10.200.1.104 1004 00:19:08 [{Origin: ?} {Med: 0} {LocalPref: 100}] 53 | *> 172.22.1.0/24 10.200.1.101 1001 00:19:06 [{Origin: ?} {Med: 0} {LocalPref: 100}] 54 | * 172.22.1.0/24 10.200.1.102 1001 00:19:35 [{Origin: ?} {Med: 0} {LocalPref: 100}] 55 | *> 172.22.2.0/24 10.200.1.101 1002 00:19:06 [{Origin: ?} {Med: 0} {LocalPref: 100}] 56 | * 172.22.2.0/24 10.200.1.102 1002 00:19:35 [{Origin: ?} {Med: 0} {LocalPref: 100}] 57 | *> 172.22.3.0/24 10.200.1.103 1003 00:18:57 [{Origin: ?} {Med: 0} {LocalPref: 100}] 58 | * 172.22.3.0/24 10.200.1.104 1003 00:19:08 [{Origin: ?} {Med: 0} {LocalPref: 100}] 59 | *> 172.22.4.0/24 10.200.1.103 1004 00:18:57 [{Origin: ?} {Med: 0} {LocalPref: 100}] 60 | * 172.22.4.0/24 10.200.1.104 1004 00:19:08 [{Origin: ?} {Med: 0} {LocalPref: 100}] 61 | ``` 62 | 63 | ### neighbor 64 | ``` 65 | $ ./gobgp.sh neighbor 66 | Peer AS Up/Down State |#Advertised Received Accepted 67 | 10.200.1.101 100 00:20:27 Establ | 27 13 13 68 | 10.200.1.102 100 00:20:56 Establ | 25 13 13 69 | 10.200.1.103 100 00:20:18 Establ | 27 13 13 70 | 10.200.1.104 100 00:20:29 Establ | 25 13 13 71 | 72 | $ ./gobgp.sh neighbor 10.200.1.101 73 | BGP neighbor is 10.200.1.101, remote AS 100 74 | BGP version 4, remote router ID 10.1.1.1 75 | BGP state = BGP_FSM_ESTABLISHED, up for 00:20:50 76 | BGP OutQ = 0, Flops = 0 77 | Hold time is 0, keepalive interval is 30 seconds 78 | Configured hold time is 90, keepalive interval is 30 seconds 79 | Neighbor capabilities: 80 | BGP_CAP_MULTIPROTOCOL: 81 | RF_IPv4_UC: advertised and received 82 | BGP_CAP_ROUTE_REFRESH: advertised and received 83 | BGP_CAP_FOUR_OCTET_AS_NUMBER: advertised and received 84 | BGP_CAP_ROUTE_REFRESH_CISCO: received 85 | Message statistics: 86 | Sent Rcvd 87 | Opens: 1 1 88 | Notifications: 0 0 89 | Updates: 9 3 90 | Keepalives: 42 43 91 | Route Refesh: 0 0 92 | Discarded: 0 0 93 | Total: 52 47 94 | Route statistics: 95 | Advertised: 27 96 | Received: 13 97 | Accepted: 13 98 | ``` 99 | 100 | ### adj-rin-in/adj-rib-out 101 | ``` 102 | $ ./gobgp.sh neighbor 10.200.1.101 adj-in 103 | Network Next Hop AS_PATH Age Attrs 104 | 10.1.1.1/32 10.200.1.101 00:22:53 [{Origin: ?} {Med: 0} {LocalPref: 100}] 105 | 10.1.2.1/32 10.200.1.101 1001 00:22:53 [{Origin: ?} {Med: 0} {LocalPref: 100}] 106 | 10.1.2.2/32 10.200.1.101 1002 00:22:53 [{Origin: ?} {Med: 0} {LocalPref: 100}] 107 | 10.10.10.0/24 10.200.1.101 00:22:53 [{Origin: ?} {Med: 0} {LocalPref: 100}] 108 | 10.200.1.0/24 10.200.1.101 00:22:53 [{Origin: ?} {Med: 0} {LocalPref: 100}] 109 | 10.200.2.0/24 10.200.1.101 00:22:53 [{Origin: ?} {Med: 0} {LocalPref: 100}] 110 | 10.201.11.0/24 10.200.1.101 00:22:53 [{Origin: ?} {Med: 0} {LocalPref: 100}] 111 | 10.201.12.0/24 10.200.1.101 00:22:53 [{Origin: ?} {Med: 0} {LocalPref: 100}] 112 | 172.17.0.0/16 10.200.1.101 00:22:53 [{Origin: ?} {Med: 0} {LocalPref: 100}] 113 | 172.21.1.0/24 10.200.1.101 1001 00:22:53 [{Origin: ?} {Med: 0} {LocalPref: 100}] 114 | 172.21.2.0/24 10.200.1.101 1002 00:22:53 [{Origin: ?} {Med: 0} {LocalPref: 100}] 115 | 172.22.1.0/24 10.200.1.101 1001 00:22:53 [{Origin: ?} {Med: 0} {LocalPref: 100}] 116 | 172.22.2.0/24 10.200.1.101 1002 00:22:53 [{Origin: ?} {Med: 0} {LocalPref: 100}] 117 | 118 | $ ./gobgp.sh neighbor 10.200.1.101 adj-out 119 | Network Next Hop AS_PATH Attrs 120 | 10.1.1.2/32 10.200.1.102 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.2} {ClusterList: [0.0.0.0]}] 121 | 10.1.1.3/32 10.200.1.103 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.3} {ClusterList: [0.0.0.0]}] 122 | 10.1.1.4/32 10.200.1.104 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.4} {ClusterList: [0.0.0.0]}] 123 | 10.1.2.1/32 10.200.1.102 1001 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.2} {ClusterList: [0.0.0.0]}] 124 | 10.1.2.2/32 10.200.1.102 1002 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.2} {ClusterList: [0.0.0.0]}] 125 | 10.1.2.3/32 10.200.1.103 1003 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.3} {ClusterList: [0.0.0.0]}] 126 | 10.1.2.4/32 10.200.1.103 1004 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.3} {ClusterList: [0.0.0.0]}] 127 | 10.10.10.0/24 10.200.1.102 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.2} {ClusterList: [0.0.0.0]}] 128 | 10.200.1.0/24 10.200.1.102 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.2} {ClusterList: [0.0.0.0]}] 129 | 10.200.2.0/24 10.200.1.102 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.2} {ClusterList: [0.0.0.0]}] 130 | 10.201.11.0/24 10.200.1.102 1001 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.2} {ClusterList: [0.0.0.0]}] 131 | 10.201.12.0/24 10.200.1.102 1002 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.2} {ClusterList: [0.0.0.0]}] 132 | 10.202.11.0/24 10.200.1.102 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.2} {ClusterList: [0.0.0.0]}] 133 | 10.202.12.0/24 10.200.1.102 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.2} {ClusterList: [0.0.0.0]}] 134 | 10.203.13.0/24 10.200.1.103 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.3} {ClusterList: [0.0.0.0]}] 135 | 10.203.14.0/24 10.200.1.103 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.3} {ClusterList: [0.0.0.0]}] 136 | 10.204.13.0/24 10.200.1.104 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.4} {ClusterList: [0.0.0.0]}] 137 | 10.204.14.0/24 10.200.1.104 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.4} {ClusterList: [0.0.0.0]}] 138 | 172.17.0.0/16 10.200.1.102 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.2} {ClusterList: [0.0.0.0]}] 139 | 172.21.1.0/24 10.200.1.102 1001 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.2} {ClusterList: [0.0.0.0]}] 140 | 172.21.2.0/24 10.200.1.102 1002 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.2} {ClusterList: [0.0.0.0]}] 141 | 172.21.3.0/24 10.200.1.103 1003 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.3} {ClusterList: [0.0.0.0]}] 142 | 172.21.4.0/24 10.200.1.103 1004 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.3} {ClusterList: [0.0.0.0]}] 143 | 172.22.1.0/24 10.200.1.102 1001 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.2} {ClusterList: [0.0.0.0]}] 144 | 172.22.2.0/24 10.200.1.102 1002 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.2} {ClusterList: [0.0.0.0]}] 145 | 172.22.3.0/24 10.200.1.103 1003 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.3} {ClusterList: [0.0.0.0]}] 146 | 172.22.4.0/24 10.200.1.103 1004 [{Origin: ?} {Med: 0} {LocalPref: 100} {Originator: 10.1.1.3} {ClusterList: [0.0.0.0]}] 147 | ``` 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Interactive SDN/IOT with tega db and Jupyter/IPython 2 | 3 | ![All in one](https://docs.google.com/drawings/d/1Q4Et0x4pe4XPdw8pGaFjUrOnA9Un6XUIJAvUs4DqsaI/pub?w=600&h=400) 4 | 5 | This project nlan (meaning "new LAN") unifies outputs from my two other projects "[neutron-lan](https://github.com/araobp/neutron-lan)" and "[tega](https://github.com/araobp/tega)". 6 | 7 | ## Background and motivation 8 | - OpenDaylight MD-SAL is too heavy for networking Linux containers on my Raspberry Pi. 9 | - YANG is incompatible with Python dict, Golang map and so on: I just want JSON-centric MD-SAL. 10 | - As my hobby, I design a model-driven/event-driven architecture for networking Linux containers. 11 | - I think Jupyter/IPython is a wonderful IDE for SDN/IOT (and also for Deep Learning...). 12 | - [OCP networking](http://www.opencompute.org/wiki/Networking) is a wonderland! 13 | - If the computing power moves to the network edge, what you need is not VLAN but application-level logical seperation of network (SSL/TLS, WebSocket, RTP/RTCP, ...), that is, what you need is "session". 14 | 15 | ## Architecture 16 | Sort of "immutable infrastructure" for networking... 17 | 18 | ![NLAN architecture](https://docs.google.com/drawings/d/1VauRM6d2A03gIPxFbaVYdZpP8Yadre8KqL53XnntqDI/pub?w=600&h=400) 19 | 20 | ![NLAN archiecture internal](https://docs.google.com/drawings/d/1y2YXolq8bpm8E2xTgcDPfNzRtJKjc19cBVYKYm9prFE/pub?w=600&h=400) 21 | 22 | ## Visualization and analytics 23 | I use Jupyter and IPython for visualization and analytics of NLAN. 24 | ``` 25 | import networkx as nx 26 | get_ipython().magic('matplotlib inline') 27 | 28 | import tega.driver 29 | d = tega.driver.Driver(host='192.168.57.133') 30 | subnets = d.get(path='graph.subnets') 31 | 32 | g = nx.DiGraph(subnets['172.21.1.0/24']) 33 | nx.draw_spring(g, node_size=1000, with_labels=True, arrows=True, alpha=0.8) 34 | ``` 35 | ![NLAN visualization](./doc/jupyter/output_2_0.png) 36 | 37 | Directional graph of IP routing that Quagga and GoBGP have setup on the network 38 | 39 | Note: in case of OpenFlow Controller, the directional graph is calculated by the controller. 40 | ``` 41 | OpenFlow-based SDN: calculate directional graph and write the edges(flow entries) to the switches/routers. 42 | SDN with BGP/OSPF: write config/policy to the switches/routers, then each of the switches/routers calculates directional graph. 43 | ``` 44 | 45 | ### Jupyter notebook examples 46 | - [PTN topology](./doc/jupyter/topo.md) 47 | - [Subnet graph set up by BGP at each router](./doc/jupyter/BGP_route_graph.md) 48 | - [Server-client graph from netstat at each router](./doc/jupyter/server_client.md) 49 | 50 | You can find the notebooks [here](./ipynb). 51 | 52 | ## NLAN services 53 | - PTN: Packet Transport Network (Layer 1 and Layer 2) 54 | - Vhosts: netns-based virtual hosts 55 | - Router: Quagga configuration 56 | 57 | To be added: 58 | - Links: direct linking(veth/macvlan/tun/tap) 59 | - Bridges: non-distributed virtual switch(linux bridge per vlan) 60 | - DVR: Distributed Virtual Switch and Distributed Virtual Router (Layer 2 and Layer 3) 61 | 62 | ## Target use cases 63 | 64 | Use case 1 has already been implemented, and use case 2 is being planned at the moment. 65 | 66 | ### Use case 1: Network simulation 67 | 68 | This use case makes use of NLAN's PTN, vHosts and Router services. 69 | ![WAN simulation](https://docs.google.com/drawings/d/1VKfKlwnzWQ2-ImfXeB5uNegGBK0BnaGU_4lS8h4Qpcw/pub?w=640&h=480) 70 | 71 | #### Declarative state representations: 72 | - [ptn-bgp.yaml](./etc/ptn-bgp.yaml) 73 | - [ptn-ospf.yaml](./etc/ptn-ospf.yaml) 74 | 75 | #### Data trees on tega db 76 | ![NLAN data trees](https://docs.google.com/drawings/d/1JjByqUw7wvc9dKWcpEQF9F0_iQFdgl6X3o6rTb--12I/pub?w=480&h=300) 77 | 78 | #### Running the simulated network on Raspberry Pi 79 | This is sort of micro NFV(Network Function Virtualization) on a single Rapsberry Pi. 80 | - Nine virtual routers (Linux containers) 81 | - Sixteen virutal hosts (netns) 82 | 83 | You can learn how routing protocols work on this simulated network. 84 | 85 | [Setting up the software on Raspberry Pi](./doc/RPI.md) 86 | 87 | Log in the virtual routers with ssh, and try "ip" or "vtysh" commands: 88 | - ip route 89 | - ip addr 90 | - ip link 91 | - ip netns 92 | - vtysh: show run 93 | - vtysh: show ip route 94 | - vtysh: show ip bgp 95 | : 96 | 97 | #### Quagga and GoBGP: 98 | This use case makes use of Quagga, but [gobgp](https://github.com/osrg/gobgp) may optionally be used as Route Reflector or Route Server on "RR" container in the fig above. 99 | - [gobgpd.conf](./etc/gobgpd.conf) 100 | 101 | You can also launch gobgpd from NLAN agent by including "EmbeddedBgp: true" in your NLAN state file: 102 | ``` 103 | Router: 104 | Loopback: 10.1.1.5/32 105 | EmbeddedBgp: true 106 | Bgp: 107 | - As: 100 108 | Neighbors: 109 | - Peer: 10.200.1.101 110 | RemoteAs: 100 111 | RouteReflectorClient: true 112 | - Peer: 10.200.1.102 113 | RemoteAs: 100 114 | RouteReflectorClient: true 115 | - Peer: 10.200.1.103 116 | RemoteAs: 100 117 | RouteReflectorClient: true 118 | - Peer: 10.200.1.104 119 | RemoteAs: 100 120 | RouteReflectorClient: true 121 | ``` 122 | - [Using gobgp command](./doc/GOBGP.md) 123 | 124 | ### Use case 2: SOHO NFV (Network Functions Virtualization) 125 | 126 | This is the next use case I am going to work on... (as my hobby: not so practical) 127 | 128 | ![SONO-NFV](https://docs.google.com/drawings/d/11fJUimZVrGxqAdq-hJK4abDu0ZThkfHGtbl_94zW0rQ/pub?w=640&h=480) 129 | 130 | ## Network simulation with Linux containers 131 | I use Linux containers as virtual routers, and this tool will set up virtual links (L0/L1) and virtual switches (L2) over the containers. Then I will run Quagga/Zebra(L3) daemons over the virtual routers to study how legacy routing protocols work. 132 | - [An example of such a network](https://camo.githubusercontent.com/3f15c9634b2491185ec680fa5bb7d19f6f01146b/68747470733a2f2f646f63732e676f6f676c652e636f6d2f64726177696e67732f642f31564b664b6c776e7a5751322d496d6658654235754e656747424b30426e6147555f346c53386834517063772f7075623f773d39363026683d373230) 133 | - [Working with Docker for network simulation](https://camo.githubusercontent.com/77cf473ea9499432e57b06a951f5f5248419f9e1/68747470733a2f2f646f63732e676f6f676c652e636f6d2f64726177696e67732f642f313631426e383077384a5a4b513742586d496f306272377851346b71456442635f585a3235347a754f5253552f7075623f773d36383026683d343030) 134 | 135 | # NLAN installation 136 | 137 | [Step 1] Make a Docker image named "router" following the instruction [here](./docker/SETUP.md). 138 | 139 | [Step 2] Install and start tega db: 140 | 141 | You need to have Python3.5 installed on your Ubuntu/Debian. 142 | 143 | ``` 144 | $ go get github.com/araobp/tega/driver 145 | $ cd $GOPATH/src/github.com/araobp/tega 146 | $ python setup.py install 147 | $ pip install mako 148 | ``` 149 | For Hypriot/RaspberryPi, you need to export this environment variable: 150 | ``` 151 | $ export SETUP_SCRIPT=setup_rpi.sh 152 | ``` 153 | For Debian/Ubuntu, you do not need to export the variable above. 154 | 155 | Then start tega db: 156 | ``` 157 | $ cd scripts 158 | $ ./tegadb 159 | 160 | __ 161 | / /____ ____ _____ _ 162 | / __/ _ \/ __ `/ __ `/ 163 | / /_/ __/ /_/ / /_/ / 164 | \__/\___/\__, /\__,_/ 165 | /____/ 166 | 167 | tega_id: global, config: None, operational: None 168 | 169 | Namespace(config=None, extensions='/root/work/src/github.com/araobp/nlan/plugins/nlan', ghost=None, gport=None, logdir='./var', loglevel='INFO', maxlen=10, operational=None, port=8739, tegaid='global') 170 | 171 | INFO:2016-03-16 15:14:51,966:Reloading log from ./var... 172 | INFO:2016-03-16 15:14:51,972:Reloading done 173 | INFO:2016-03-16 15:14:52,675:plugin attached to idb: Hook 174 | INFO:2016-03-16 15:14:52,692:plugin attached to idb: Deployment 175 | INFO:2016-03-16 15:14:52,707:plugin attached to idb: Subnets 176 | INFO:2016-03-16 15:14:52,712:plugin attached to idb: Topo 177 | INFO:2016-03-16 15:14:52,739:plugin attached to idb: PtnBgp 178 | INFO:2016-03-16 15:14:52,765:plugin attached to idb: Workflow 179 | INFO:2016-03-16 15:14:52,782:plugin attached to idb: Fabric 180 | INFO:2016-03-16 15:14:52,800:plugin attached to idb: ServerClient 181 | INFO:2016-03-16 15:14:52,823:plugin attached to idb: IpAddressManagement 182 | INFO:2016-03-16 15:14:52,842:plugin attached to idb: Template 183 | ``` 184 | 185 | [Step 2] 186 | Try this at the tega CLI to put "ptn-bgp" state onto tega db: 187 | ``` 188 | [tega: 2] plugins.ptnbgp() 189 | ``` 190 | The script sets up [this network](https://camo.githubusercontent.com/3f15c9634b2491185ec680fa5bb7d19f6f01146b/68747470733a2f2f646f63732e676f6f676c652e636f6d2f64726177696e67732f642f31564b664b6c776e7a5751322d496d6658654235754e656747424b30426e6147555f346c53386834517063772f7075623f773d39363026683d373230). 191 | 192 | You may also try "plugins.fabric()" instead. It will setup L3 fabric simulating a data center network. 193 | 194 | [Step 3(option)] 195 | You may take a snapshop of tega db to make tega db's start-up faster: 196 | ``` 197 | [tega: 3] ss 198 | ``` 199 | 200 | [Step 4] Execute the following command to build Docker image with NLAN agent embedded and to start the containers: 201 | 202 | ``` 203 | [tega: 4] plugins.deploy() 204 | ``` 205 | 206 | NLAN agent on each container connects to tega db to fetch NLAN state. 207 | 208 | If you want to monitor the activities of each agents, subscribe(path="hosts") on the CLI ([example](./doc/monitoring-activities.md)). 209 | 210 | [Step 5] Confirm that all the containers are running 211 | 212 | ``` 213 | [tega: 5] subscribers 214 | Deployment: [Deployment] 215 | IpAddressManagement: [IpAddressManagement] 216 | Template: [Template] 217 | Topo: [Topo, config-.*] 218 | ce1: [ce1] 219 | ce2: [ce2] 220 | ce3: [ce3] 221 | ce4: [ce4] 222 | pe1: [pe1] 223 | pe2: [pe2] 224 | pe3: [pe3] 225 | pe4: [pe4] 226 | rr: [rr] 227 | 228 | ``` 229 | 230 | [Step 6] Try raw commands to check the state of each container 231 | 232 | ``` 233 | [tega: 6] raw.ce1('ip route') 234 | default via 172.17.0.1 dev eth0 235 | 10.1.1.1 via 10.201.11.1 dev int_br111 proto zebra 236 | 10.1.1.2 via 10.202.11.1 dev int_br211 proto zebra 237 | 10.1.1.3 via 10.201.11.1 dev int_br111 proto zebra 238 | 10.1.2.2 via 10.201.11.1 dev int_br111 proto zebra 239 | 10.1.2.3 via 10.201.11.1 dev int_br111 proto zebra 240 | 10.1.2.4 via 10.201.11.1 dev int_br111 proto zebra 241 | 10.10.10.0/24 dev eth0 proto kernel scope link src 10.10.10.6 242 | 10.200.1.0/24 via 10.201.11.1 dev int_br111 proto zebra 243 | 10.200.2.0/24 via 10.201.11.1 dev int_br111 proto zebra 244 | 10.201.11.0/24 dev int_br111 proto kernel scope link src 10.201.11.2 245 | 10.201.12.0/24 via 10.201.11.1 dev int_br111 proto zebra 246 | 10.202.11.0/24 dev int_br211 proto kernel scope link src 10.202.11.2 247 | 10.202.12.0/24 via 10.202.11.1 dev int_br211 proto zebra 248 | 10.203.13.0/24 via 10.201.11.1 dev int_br111 proto zebra 249 | 10.203.14.0/24 via 10.201.11.1 dev int_br111 proto zebra 250 | 10.204.13.0/24 via 10.201.11.1 dev int_br111 proto zebra 251 | 10.204.14.0/24 via 10.201.11.1 dev int_br111 proto zebra 252 | 172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.7 253 | 172.21.1.0/24 dev br_172.21.1.1 proto kernel scope link src 172.21.1.1 254 | 172.21.2.0/24 via 10.201.11.1 dev int_br111 proto zebra 255 | 172.21.3.0/24 via 10.201.11.1 dev int_br111 proto zebra 256 | 172.21.4.0/24 via 10.201.11.1 dev int_br111 proto zebra 257 | 172.22.1.0/24 dev br_172.22.1.1 proto kernel scope link src 172.22.1.1 258 | 172.22.2.0/24 via 10.201.11.1 dev int_br111 proto zebra 259 | 172.22.3.0/24 via 10.201.11.1 dev int_br111 proto zebra 260 | 172.22.4.0/24 via 10.201.11.1 dev int_br111 proto zebra 261 | 262 | [tega: 7] raw.ce2('ip route') 263 | : 264 | 265 | ``` 266 | You may also start a ssh session to the containers: 267 | ``` 268 | $ cd scripts 269 | $ ./ssh.sh pe1 270 | : 271 | $ ./ssh.sh ce1 272 | : 273 | ``` 274 | The password is "root". 275 | 276 | Or you may also use "ip netns" command to the containers: 277 | ``` 278 | $ ip netns exec pe1 ip route 279 | ``` 280 | 281 | [Step 8] Call hook functions to reflesh operational data trees 282 | ``` 283 | [tega: 8] plugins.hook() 284 | ``` 285 | 286 | [Step 9] Check the operational trees 287 | ``` 288 | [tega: 9] getr operational-(\w*)\.ip 289 | operational-ce1.ip: 290 | groups: 291 | - [ce1] 292 | instance: 293 | addr: {10.1.2.1: lo, 10.10.10.6: eth0, 10.201.11.2: int_br111, 10.202.11.2: int_br211, 294 | 127.0.0.1: lo, 172.17.0.7: eth0, 172.21.1.1: br_172.21.1.1, 172.22.1.1: br_172.22.1.1} 295 | dev: 296 | br_172.21.1.1: [172.21.1.1] 297 | br_172.22.1.1: [172.22.1.1] 298 | eth0: [172.17.0.7, 10.10.10.6] 299 | int_br111: [10.201.11.2] 300 | int_br211: [10.202.11.2] 301 | lo: [127.0.0.1, 10.1.2.1] 302 | hook: {addr: '%ce1.ipAddr', route: '%ce1.ipRoute'} 303 | route: 304 | 10.1.1.1/32: {Dev: int_br111, Src: '', Via: 10.201.11.1} 305 | 10.1.1.2/32: {Dev: int_br211, Src: '', Via: 10.202.11.1} 306 | 10.1.1.3/32: {Dev: int_br111, Src: '', Via: 10.201.11.1} 307 | 10.1.1.4/32: {Dev: int_br111, Src: '', Via: 10.201.11.1} 308 | 10.1.2.2/32: {Dev: int_br111, Src: '', Via: 10.201.11.1} 309 | 10.1.2.3/32: {Dev: int_br111, Src: '', Via: 10.201.11.1} 310 | 10.1.2.4/32: {Dev: int_br211, Src: '', Via: 10.202.11.1} 311 | 10.10.10.0/24: {Dev: eth0, Src: 10.10.10.6, Via: ''} 312 | 10.200.1.0/24: {Dev: int_br111, Src: '', Via: 10.201.11.1} 313 | 10.200.2.0/24: {Dev: int_br111, Src: '', Via: 10.201.11.1} 314 | 10.201.11.0/24: {Dev: int_br111, Src: 10.201.11.2, Via: ''} 315 | 10.201.12.0/24: {Dev: int_br111, Src: '', Via: 10.201.11.1} 316 | 10.202.11.0/24: {Dev: int_br211, Src: 10.202.11.2, Via: ''} 317 | 10.202.12.0/24: {Dev: int_br211, Src: '', Via: 10.202.11.1} 318 | 10.203.13.0/24: {Dev: int_br111, Src: '', Via: 10.201.11.1} 319 | 10.203.14.0/24: {Dev: int_br111, Src: '', Via: 10.201.11.1} 320 | 10.204.13.0/24: {Dev: int_br111, Src: '', Via: 10.201.11.1} 321 | 10.204.14.0/24: {Dev: int_br111, Src: '', Via: 10.201.11.1} 322 | 172.17.0.0/16: {Dev: eth0, Src: 172.17.0.7, Via: ''} 323 | 172.21.1.0/24: {Dev: br_172.21.1.1, Src: 172.21.1.1, Via: ''} 324 | 172.21.2.0/24: {Dev: int_br111, Src: '', Via: 10.201.11.1} 325 | 172.21.3.0/24: {Dev: int_br111, Src: '', Via: 10.201.11.1} 326 | 172.21.4.0/24: {Dev: int_br211, Src: '', Via: 10.202.11.1} 327 | 172.22.1.0/24: {Dev: br_172.22.1.1, Src: 172.22.1.1, Via: ''} 328 | 172.22.2.0/24: {Dev: int_br111, Src: '', Via: 10.201.11.1} 329 | 172.22.3.0/24: {Dev: int_br111, Src: '', Via: 10.201.11.1} 330 | 172.22.4.0/24: {Dev: int_br211, Src: '', Via: 10.202.11.1} 331 | default: {Dev: eth0, Src: '', Via: 172.17.0.1} 332 | operational-ce2.ip: 333 | groups: 334 | - [ce2] 335 | instance: 336 | addr: {10.1.2.2: lo, 10.10.10.7: eth0, 10.201.12.2: int_br112, 10.202.12.2: int_br212, 337 | 127.0.0.1: lo, 172.17.0.8: eth0, 172.21.2.1: br_172.21.2.1, 172.22.2.1: br_172.22.2.1} 338 | dev: 339 | br_172.21.2.1: [172.21.2.1] 340 | br_172.22.2.1: [172.22.2.1] 341 | eth0: [172.17.0.8, 10.10.10.7] 342 | int_br112: [10.201.12.2] 343 | : 344 | ``` 345 | [Step 10] Start jupyter notebook and open the notebooks [here](./ipynb/). 346 | ``` 347 | cd to the project root directory, then: 348 | $ cd ipynb 349 | $ jupyter notebook 350 | ``` 351 | 352 | You need to change the IP address to the one that tega db binds: 353 | ``` 354 | import tega.driver 355 | d = tega.driver.Driver(host='192.168.57.133') <== MODIFY THIS! 356 | ``` 357 | 358 | # Development environment setup 359 | 360 | ## Python3.5 361 | 362 | - Download the source code from [here](https://www.python.org/downloads/source/). 363 | - Build and install it. 364 | 365 | ## IPython/Jupyter 366 | The easiest way is to install Anaconda 367 | - https://www.continuum.io/downloads 368 | 369 | Note that Anaconda already includes Python3.5 and other packages used by this project as well. 370 | 371 | ## Golang and protobuf 372 | - Go lang installation: https://golang.org/dl/ 373 | - Protobuf build and installation: https://github.com/google/protobuf/blob/master/INSTALL.txt 374 | ``` 375 | $ ./autogen.sh 376 | $ ./configure 377 | $ make 378 | $ make install 379 | ``` 380 | - Add /usr/local/lib to LD_LIBRARY_PATH 381 | ``` 382 | $ export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARLY_PATH 383 | 384 | ``` 385 | ### Go plugin for vim 386 | 387 | Install [vim-go](https://github.com/fatih/vim-go) to your vim. 388 | -------------------------------------------------------------------------------- /model/nlan/nlan.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. 2 | // source: nlan.proto 3 | // DO NOT EDIT! 4 | 5 | /* 6 | Package nlan is a generated protocol buffer package. 7 | 8 | It is generated from these files: 9 | nlan.proto 10 | 11 | It has these top-level messages: 12 | State 13 | Model 14 | Dvr 15 | Subnets 16 | IpDvr 17 | Vxlan 18 | Network 19 | L2Vpn 20 | Links 21 | Nodes 22 | Router 23 | Attrs 24 | Neighbor 25 | Ospf 26 | Vhosts 27 | VhostProps 28 | Interface 29 | */ 30 | package nlan 31 | 32 | import proto "github.com/golang/protobuf/proto" 33 | import fmt "fmt" 34 | import math "math" 35 | 36 | // Reference imports to suppress errors if they are not otherwise used. 37 | var _ = proto.Marshal 38 | var _ = fmt.Errorf 39 | var _ = math.Inf 40 | 41 | type State struct { 42 | Router map[string]*Model `protobuf:"bytes,1,rep,name=Router" json:"Router,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` 43 | } 44 | 45 | func (m *State) Reset() { *m = State{} } 46 | func (m *State) String() string { return proto.CompactTextString(m) } 47 | func (*State) ProtoMessage() {} 48 | func (*State) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 49 | 50 | func (m *State) GetRouter() map[string]*Model { 51 | if m != nil { 52 | return m.Router 53 | } 54 | return nil 55 | } 56 | 57 | type Model struct { 58 | Dvr *Dvr `protobuf:"bytes,1,opt,name=Dvr" json:"Dvr,omitempty"` 59 | Ptn map[string]*Network `protobuf:"bytes,2,rep,name=Ptn" json:"Ptn,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` 60 | Router *Router `protobuf:"bytes,3,opt,name=Router" json:"Router,omitempty"` 61 | Vhosts *Vhosts `protobuf:"bytes,4,opt,name=Vhosts" json:"Vhosts,omitempty"` 62 | Interfaces map[string]*Interface `protobuf:"bytes,5,rep,name=Interfaces" json:"Interfaces,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` 63 | } 64 | 65 | func (m *Model) Reset() { *m = Model{} } 66 | func (m *Model) String() string { return proto.CompactTextString(m) } 67 | func (*Model) ProtoMessage() {} 68 | func (*Model) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 69 | 70 | func (m *Model) GetDvr() *Dvr { 71 | if m != nil { 72 | return m.Dvr 73 | } 74 | return nil 75 | } 76 | 77 | func (m *Model) GetPtn() map[string]*Network { 78 | if m != nil { 79 | return m.Ptn 80 | } 81 | return nil 82 | } 83 | 84 | func (m *Model) GetRouter() *Router { 85 | if m != nil { 86 | return m.Router 87 | } 88 | return nil 89 | } 90 | 91 | func (m *Model) GetVhosts() *Vhosts { 92 | if m != nil { 93 | return m.Vhosts 94 | } 95 | return nil 96 | } 97 | 98 | func (m *Model) GetInterfaces() map[string]*Interface { 99 | if m != nil { 100 | return m.Interfaces 101 | } 102 | return nil 103 | } 104 | 105 | type Dvr struct { 106 | OvsBridges bool `protobuf:"varint,1,opt,name=OvsBridges" json:"OvsBridges,omitempty"` 107 | Subnets []*Subnets `protobuf:"bytes,2,rep,name=Subnets" json:"Subnets,omitempty"` 108 | Vxlan []*Vxlan `protobuf:"bytes,3,rep,name=Vxlan" json:"Vxlan,omitempty"` 109 | } 110 | 111 | func (m *Dvr) Reset() { *m = Dvr{} } 112 | func (m *Dvr) String() string { return proto.CompactTextString(m) } 113 | func (*Dvr) ProtoMessage() {} 114 | func (*Dvr) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } 115 | 116 | func (m *Dvr) GetSubnets() []*Subnets { 117 | if m != nil { 118 | return m.Subnets 119 | } 120 | return nil 121 | } 122 | 123 | func (m *Dvr) GetVxlan() []*Vxlan { 124 | if m != nil { 125 | return m.Vxlan 126 | } 127 | return nil 128 | } 129 | 130 | type Subnets struct { 131 | IpDvr []*IpDvr `protobuf:"bytes,1,rep,name=IpDvr" json:"IpDvr,omitempty"` 132 | Peers []string `protobuf:"bytes,2,rep,name=Peers" json:"Peers,omitempty"` 133 | Ports []string `protobuf:"bytes,3,rep,name=Ports" json:"Ports,omitempty"` 134 | Vid uint32 `protobuf:"varint,4,opt,name=Vid" json:"Vid,omitempty"` 135 | Vni uint32 `protobuf:"varint,5,opt,name=Vni" json:"Vni,omitempty"` 136 | } 137 | 138 | func (m *Subnets) Reset() { *m = Subnets{} } 139 | func (m *Subnets) String() string { return proto.CompactTextString(m) } 140 | func (*Subnets) ProtoMessage() {} 141 | func (*Subnets) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } 142 | 143 | func (m *Subnets) GetIpDvr() []*IpDvr { 144 | if m != nil { 145 | return m.IpDvr 146 | } 147 | return nil 148 | } 149 | 150 | type IpDvr struct { 151 | Addr string `protobuf:"bytes,1,opt,name=Addr" json:"Addr,omitempty"` 152 | Dhcp string `protobuf:"bytes,2,opt,name=Dhcp" json:"Dhcp,omitempty"` 153 | Mode string `protobuf:"bytes,3,opt,name=Mode" json:"Mode,omitempty"` 154 | } 155 | 156 | func (m *IpDvr) Reset() { *m = IpDvr{} } 157 | func (m *IpDvr) String() string { return proto.CompactTextString(m) } 158 | func (*IpDvr) ProtoMessage() {} 159 | func (*IpDvr) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } 160 | 161 | type Vxlan struct { 162 | LocalIp string `protobuf:"bytes,1,opt,name=LocalIp" json:"LocalIp,omitempty"` 163 | RemoteIps []string `protobuf:"bytes,2,rep,name=RemoteIps" json:"RemoteIps,omitempty"` 164 | } 165 | 166 | func (m *Vxlan) Reset() { *m = Vxlan{} } 167 | func (m *Vxlan) String() string { return proto.CompactTextString(m) } 168 | func (*Vxlan) ProtoMessage() {} 169 | func (*Vxlan) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } 170 | 171 | type Network struct { 172 | L2Vpn []*L2Vpn `protobuf:"bytes,2,rep,name=L2Vpn" json:"L2Vpn,omitempty"` 173 | Links *Links `protobuf:"bytes,3,opt,name=Links" json:"Links,omitempty"` 174 | Nodes *Nodes `protobuf:"bytes,4,opt,name=Nodes" json:"Nodes,omitempty"` 175 | } 176 | 177 | func (m *Network) Reset() { *m = Network{} } 178 | func (m *Network) String() string { return proto.CompactTextString(m) } 179 | func (*Network) ProtoMessage() {} 180 | func (*Network) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } 181 | 182 | func (m *Network) GetL2Vpn() []*L2Vpn { 183 | if m != nil { 184 | return m.L2Vpn 185 | } 186 | return nil 187 | } 188 | 189 | func (m *Network) GetLinks() *Links { 190 | if m != nil { 191 | return m.Links 192 | } 193 | return nil 194 | } 195 | 196 | func (m *Network) GetNodes() *Nodes { 197 | if m != nil { 198 | return m.Nodes 199 | } 200 | return nil 201 | } 202 | 203 | type L2Vpn struct { 204 | Ip string `protobuf:"bytes,1,opt,name=Ip" json:"Ip,omitempty"` 205 | Peers []string `protobuf:"bytes,2,rep,name=Peers" json:"Peers,omitempty"` 206 | Vid uint32 `protobuf:"varint,3,opt,name=Vid" json:"Vid,omitempty"` 207 | Vni uint32 `protobuf:"varint,4,opt,name=Vni" json:"Vni,omitempty"` 208 | } 209 | 210 | func (m *L2Vpn) Reset() { *m = L2Vpn{} } 211 | func (m *L2Vpn) String() string { return proto.CompactTextString(m) } 212 | func (*L2Vpn) ProtoMessage() {} 213 | func (*L2Vpn) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } 214 | 215 | type Links struct { 216 | LocalIp string `protobuf:"bytes,1,opt,name=LocalIp" json:"LocalIp,omitempty"` 217 | RemoteIps []string `protobuf:"bytes,2,rep,name=RemoteIps" json:"RemoteIps,omitempty"` 218 | } 219 | 220 | func (m *Links) Reset() { *m = Links{} } 221 | func (m *Links) String() string { return proto.CompactTextString(m) } 222 | func (*Links) ProtoMessage() {} 223 | func (*Links) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } 224 | 225 | type Nodes struct { 226 | L2Sw string `protobuf:"bytes,1,opt,name=L2Sw" json:"L2Sw,omitempty"` 227 | Ptn string `protobuf:"bytes,2,opt,name=Ptn" json:"Ptn,omitempty"` 228 | } 229 | 230 | func (m *Nodes) Reset() { *m = Nodes{} } 231 | func (m *Nodes) String() string { return proto.CompactTextString(m) } 232 | func (*Nodes) ProtoMessage() {} 233 | func (*Nodes) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } 234 | 235 | type Router struct { 236 | Bgp map[string]*Attrs `protobuf:"bytes,1,rep,name=Bgp" json:"Bgp,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` 237 | EmbeddedBgp bool `protobuf:"varint,2,opt,name=EmbeddedBgp" json:"EmbeddedBgp,omitempty"` 238 | Loopback string `protobuf:"bytes,3,opt,name=Loopback" json:"Loopback,omitempty"` 239 | Ospf []*Ospf `protobuf:"bytes,4,rep,name=Ospf" json:"Ospf,omitempty"` 240 | } 241 | 242 | func (m *Router) Reset() { *m = Router{} } 243 | func (m *Router) String() string { return proto.CompactTextString(m) } 244 | func (*Router) ProtoMessage() {} 245 | func (*Router) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} } 246 | 247 | func (m *Router) GetBgp() map[string]*Attrs { 248 | if m != nil { 249 | return m.Bgp 250 | } 251 | return nil 252 | } 253 | 254 | func (m *Router) GetOspf() []*Ospf { 255 | if m != nil { 256 | return m.Ospf 257 | } 258 | return nil 259 | } 260 | 261 | type Attrs struct { 262 | Neighbors []*Neighbor `protobuf:"bytes,1,rep,name=Neighbors" json:"Neighbors,omitempty"` 263 | } 264 | 265 | func (m *Attrs) Reset() { *m = Attrs{} } 266 | func (m *Attrs) String() string { return proto.CompactTextString(m) } 267 | func (*Attrs) ProtoMessage() {} 268 | func (*Attrs) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} } 269 | 270 | func (m *Attrs) GetNeighbors() []*Neighbor { 271 | if m != nil { 272 | return m.Neighbors 273 | } 274 | return nil 275 | } 276 | 277 | type Neighbor struct { 278 | NextHopSelf bool `protobuf:"varint,1,opt,name=NextHopSelf" json:"NextHopSelf,omitempty"` 279 | Peer string `protobuf:"bytes,2,opt,name=Peer" json:"Peer,omitempty"` 280 | RemoteAs uint32 `protobuf:"varint,3,opt,name=RemoteAs" json:"RemoteAs,omitempty"` 281 | RouteReflectorClient bool `protobuf:"varint,4,opt,name=RouteReflectorClient" json:"RouteReflectorClient,omitempty"` 282 | } 283 | 284 | func (m *Neighbor) Reset() { *m = Neighbor{} } 285 | func (m *Neighbor) String() string { return proto.CompactTextString(m) } 286 | func (*Neighbor) ProtoMessage() {} 287 | func (*Neighbor) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} } 288 | 289 | type Ospf struct { 290 | Area string `protobuf:"bytes,1,opt,name=Area" json:"Area,omitempty"` 291 | Networks []string `protobuf:"bytes,2,rep,name=Networks" json:"Networks,omitempty"` 292 | } 293 | 294 | func (m *Ospf) Reset() { *m = Ospf{} } 295 | func (m *Ospf) String() string { return proto.CompactTextString(m) } 296 | func (*Ospf) ProtoMessage() {} 297 | func (*Ospf) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} } 298 | 299 | type Vhosts struct { 300 | VhostProps []*VhostProps `protobuf:"bytes,1,rep,name=VhostProps" json:"VhostProps,omitempty"` 301 | } 302 | 303 | func (m *Vhosts) Reset() { *m = Vhosts{} } 304 | func (m *Vhosts) String() string { return proto.CompactTextString(m) } 305 | func (*Vhosts) ProtoMessage() {} 306 | func (*Vhosts) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} } 307 | 308 | func (m *Vhosts) GetVhostProps() []*VhostProps { 309 | if m != nil { 310 | return m.VhostProps 311 | } 312 | return nil 313 | } 314 | 315 | type VhostProps struct { 316 | Network string `protobuf:"bytes,1,opt,name=Network" json:"Network,omitempty"` 317 | Vhosts uint32 `protobuf:"varint,2,opt,name=Vhosts" json:"Vhosts,omitempty"` 318 | } 319 | 320 | func (m *VhostProps) Reset() { *m = VhostProps{} } 321 | func (m *VhostProps) String() string { return proto.CompactTextString(m) } 322 | func (*VhostProps) ProtoMessage() {} 323 | func (*VhostProps) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} } 324 | 325 | type Interface struct { 326 | Mode string `protobuf:"bytes,1,opt,name=mode" json:"mode,omitempty"` 327 | Local string `protobuf:"bytes,2,opt,name=local" json:"local,omitempty"` 328 | Remote string `protobuf:"bytes,3,opt,name=remote" json:"remote,omitempty"` 329 | Address string `protobuf:"bytes,4,opt,name=address" json:"address,omitempty"` 330 | } 331 | 332 | func (m *Interface) Reset() { *m = Interface{} } 333 | func (m *Interface) String() string { return proto.CompactTextString(m) } 334 | func (*Interface) ProtoMessage() {} 335 | func (*Interface) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{16} } 336 | 337 | func init() { 338 | proto.RegisterType((*State)(nil), "nlan.State") 339 | proto.RegisterType((*Model)(nil), "nlan.Model") 340 | proto.RegisterType((*Dvr)(nil), "nlan.Dvr") 341 | proto.RegisterType((*Subnets)(nil), "nlan.Subnets") 342 | proto.RegisterType((*IpDvr)(nil), "nlan.IpDvr") 343 | proto.RegisterType((*Vxlan)(nil), "nlan.Vxlan") 344 | proto.RegisterType((*Network)(nil), "nlan.Network") 345 | proto.RegisterType((*L2Vpn)(nil), "nlan.L2Vpn") 346 | proto.RegisterType((*Links)(nil), "nlan.Links") 347 | proto.RegisterType((*Nodes)(nil), "nlan.Nodes") 348 | proto.RegisterType((*Router)(nil), "nlan.Router") 349 | proto.RegisterType((*Attrs)(nil), "nlan.Attrs") 350 | proto.RegisterType((*Neighbor)(nil), "nlan.Neighbor") 351 | proto.RegisterType((*Ospf)(nil), "nlan.Ospf") 352 | proto.RegisterType((*Vhosts)(nil), "nlan.Vhosts") 353 | proto.RegisterType((*VhostProps)(nil), "nlan.VhostProps") 354 | proto.RegisterType((*Interface)(nil), "nlan.Interface") 355 | } 356 | 357 | var fileDescriptor0 = []byte{ 358 | // 698 bytes of a gzipped FileDescriptorProto 359 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x54, 0x4b, 0x6f, 0xd3, 0x40, 360 | 0x10, 0x56, 0xe2, 0xa4, 0x8d, 0xc7, 0x7d, 0xb1, 0x14, 0x88, 0x42, 0x85, 0xc0, 0x42, 0x50, 0x81, 361 | 0x08, 0x22, 0x1c, 0x80, 0x8a, 0x4b, 0x4b, 0x2b, 0x11, 0x29, 0xa4, 0x55, 0x23, 0x72, 0xe3, 0xe0, 362 | 0xc4, 0x9b, 0x36, 0x8a, 0xeb, 0xb5, 0xd6, 0xdb, 0xd7, 0x1f, 0xe2, 0xc8, 0x6f, 0x64, 0x76, 0x76, 363 | 0xec, 0x3a, 0x3d, 0xf5, 0xe6, 0x79, 0xcf, 0xf7, 0xcd, 0xb7, 0x06, 0x48, 0x93, 0x28, 0xed, 0x66, 364 | 0x5a, 0x19, 0x25, 0x1a, 0xf6, 0x3b, 0x4c, 0xa0, 0x39, 0x32, 0x91, 0x91, 0xe2, 0x2d, 0xac, 0x9c, 365 | 0xaa, 0x4b, 0x23, 0x75, 0xbb, 0xf6, 0xd2, 0xdb, 0x0d, 0x7a, 0xcf, 0xba, 0x94, 0x4b, 0xc1, 0xae, 366 | 0x8b, 0x1c, 0xa5, 0x46, 0xdf, 0x76, 0xf6, 0x20, 0xa8, 0x98, 0x22, 0x00, 0x6f, 0x21, 0x6f, 0xb1, 367 | 0xa8, 0xb6, 0xeb, 0x8b, 0x0e, 0x34, 0xaf, 0xa2, 0xe4, 0x52, 0xb6, 0xeb, 0x68, 0x06, 0xbd, 0xc0, 368 | 0xf5, 0xf8, 0xa5, 0x62, 0x99, 0xec, 0xd5, 0xbf, 0xd6, 0xc2, 0x7f, 0x75, 0x68, 0x92, 0x25, 0x9e, 369 | 0x82, 0x77, 0x78, 0xa5, 0xa9, 0x2c, 0xe8, 0xf9, 0x2e, 0x0f, 0x1d, 0xe2, 0x15, 0x78, 0x27, 0x26, 370 | 0xc5, 0x7a, 0xbb, 0xc3, 0x76, 0xa5, 0xbe, 0x8b, 0x6e, 0x37, 0x71, 0xa7, 0xdc, 0xd4, 0xa3, 0xea, 371 | 0x35, 0x97, 0xe5, 0x7c, 0x36, 0x3a, 0x3e, 0x57, 0xb9, 0xc9, 0xdb, 0x8d, 0x6a, 0xd4, 0xf9, 0xc4, 372 | 0x47, 0x80, 0x7e, 0x8a, 0x69, 0xb3, 0x68, 0x2a, 0xf3, 0x76, 0x93, 0xa6, 0x3c, 0xaf, 0x4e, 0xb9, 373 | 0x8b, 0x3a, 0xb4, 0xdf, 0xa0, 0x55, 0x0e, 0x5e, 0x82, 0xba, 0xb3, 0x0c, 0x75, 0xdd, 0x35, 0x19, 374 | 0x4a, 0x73, 0xad, 0xf4, 0xc2, 0x82, 0xed, 0x1c, 0xc0, 0xe6, 0xbd, 0x6e, 0xcb, 0x1d, 0x5e, 0x2c, 375 | 0x77, 0xd8, 0x74, 0x1d, 0xca, 0x12, 0x22, 0xec, 0x37, 0xd1, 0x24, 0x04, 0xc0, 0xf1, 0x55, 0x7e, 376 | 0xa0, 0xe7, 0xf1, 0x19, 0xae, 0x6d, 0xcb, 0x5b, 0x58, 0xbe, 0x3a, 0xba, 0x9c, 0xa4, 0x12, 0x91, 377 | 0x3a, 0xb6, 0x78, 0x05, 0x76, 0xda, 0x5b, 0x8c, 0x6f, 0xd0, 0x81, 0x2c, 0x79, 0x77, 0xb7, 0x20, 378 | 0x57, 0xf8, 0xa7, 0xac, 0xb5, 0x69, 0xfd, 0xcc, 0x9d, 0xa2, 0x92, 0x46, 0x2e, 0xb1, 0x0e, 0xcd, 379 | 0x13, 0x29, 0xb5, 0x1b, 0xe0, 0x93, 0xa9, 0x34, 0xce, 0xf3, 0xc8, 0x44, 0x30, 0xe3, 0x79, 0x4c, 380 | 0x34, 0xaf, 0x93, 0x91, 0xce, 0x91, 0x51, 0x34, 0xc2, 0x4f, 0xdc, 0x53, 0xac, 0x41, 0x63, 0x3f, 381 | 0x8e, 0x35, 0x03, 0x46, 0xeb, 0xf0, 0x7c, 0x9a, 0x11, 0x5e, 0xb2, 0x2c, 0xe5, 0x74, 0x44, 0x3f, 382 | 0x7c, 0xcf, 0xdb, 0x8a, 0x4d, 0x58, 0x1d, 0xa8, 0x69, 0x94, 0xf4, 0x33, 0xae, 0x7a, 0x04, 0xfe, 383 | 0xa9, 0xbc, 0x50, 0x46, 0xf6, 0x33, 0x5e, 0xc4, 0xae, 0xcf, 0x44, 0xdb, 0xf5, 0x07, 0xbd, 0x71, 384 | 0x56, 0x28, 0x86, 0xd7, 0x27, 0x17, 0xc5, 0xe6, 0xe9, 0x22, 0x67, 0x9d, 0x14, 0x31, 0xeb, 0xb2, 385 | 0xb1, 0x21, 0x4e, 0x2f, 0x54, 0xc2, 0x31, 0x72, 0x85, 0xdf, 0xb9, 0x27, 0xb2, 0x5e, 0x2f, 0xd7, 386 | 0xb8, 0xc7, 0x05, 0x83, 0xf7, 0xaa, 0xe0, 0x89, 0x09, 0x8b, 0xc4, 0x8d, 0x78, 0x08, 0x92, 0x90, 387 | 0xd7, 0xb0, 0x6c, 0x0c, 0x7a, 0xa3, 0x6b, 0xce, 0x0c, 0x8a, 0x57, 0x60, 0xa9, 0xf9, 0x5b, 0x2b, 388 | 0x04, 0x2f, 0x42, 0xf0, 0x0e, 0xce, 0x32, 0x3e, 0xd5, 0x93, 0xaa, 0xee, 0xbb, 0xe8, 0x77, 0x1a, 389 | 0x7b, 0x0c, 0xc1, 0xd1, 0xc5, 0x44, 0xc6, 0xb1, 0x8c, 0x6d, 0x6e, 0x9d, 0xc4, 0xb2, 0x05, 0xad, 390 | 0x81, 0x52, 0xd9, 0x24, 0x9a, 0x2e, 0x1c, 0xe1, 0xa2, 0x0d, 0x8d, 0xe3, 0x3c, 0x9b, 0xe1, 0xd2, 391 | 0xb6, 0x17, 0xb8, 0x5e, 0xd6, 0xd3, 0xf9, 0x02, 0xad, 0xb2, 0xd9, 0x03, 0x5e, 0xf7, 0xbe, 0x31, 392 | 0x3a, 0x27, 0xb1, 0xbe, 0x83, 0x26, 0x19, 0xf8, 0x88, 0xfd, 0xa1, 0x9c, 0x9f, 0x9d, 0x4f, 0x94, 393 | 0xce, 0x79, 0xd9, 0x8d, 0xe2, 0x7d, 0x38, 0x37, 0x9e, 0xb0, 0x55, 0x7c, 0xdb, 0x8d, 0x87, 0xf2, 394 | 0xc6, 0xfc, 0x54, 0xd9, 0x48, 0x26, 0x33, 0x96, 0x37, 0x12, 0x62, 0xf9, 0x66, 0xb1, 0xe0, 0xfe, 395 | 0x8e, 0xba, 0xfd, 0x9c, 0x39, 0xdf, 0x81, 0x6d, 0x42, 0x7e, 0x2a, 0x67, 0x89, 0x9c, 0x1a, 0xa5, 396 | 0x7f, 0x24, 0x73, 0x99, 0x1a, 0x3a, 0x42, 0x2b, 0x7c, 0xe3, 0xd0, 0x91, 0x00, 0xb5, 0x8c, 0x18, 397 | 0xc0, 0x96, 0x1d, 0x4a, 0xba, 0x29, 0xf8, 0xef, 0x16, 0x7f, 0x0b, 0xf1, 0x1a, 0x80, 0xbe, 0x4e, 398 | 0xb4, 0xca, 0x8a, 0xa5, 0xb7, 0x2a, 0xff, 0x0e, 0xf2, 0x87, 0x1f, 0xaa, 0x59, 0xf6, 0xc2, 0xdc, 399 | 0x8f, 0x07, 0x6c, 0x94, 0x3f, 0x9f, 0x3a, 0x69, 0xa1, 0x0f, 0x7e, 0xf9, 0x9e, 0xed, 0x2e, 0x17, 400 | 0x56, 0xf0, 0xa5, 0x9e, 0x12, 0xab, 0x0e, 0x06, 0x88, 0x95, 0x9a, 0x00, 0xf2, 0x79, 0xb0, 0x75, 401 | 0x84, 0x2f, 0x47, 0xe6, 0x4e, 0xa1, 0xfe, 0x64, 0x85, 0xfe, 0xda, 0x9f, 0xff, 0x07, 0x00, 0x00, 402 | 0xff, 0xff, 0xc1, 0xb0, 0xa7, 0xa9, 0xc3, 0x05, 0x00, 0x00, 403 | } 404 | -------------------------------------------------------------------------------- /ipynb/topo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import networkx as nx\n", 12 | "%matplotlib inline" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 2, 18 | "metadata": { 19 | "collapsed": false 20 | }, 21 | "outputs": [ 22 | { 23 | "data": { 24 | "image/png": "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\n", 25 | "text/plain": [ 26 | "" 27 | ] 28 | }, 29 | "metadata": {}, 30 | "output_type": "display_data" 31 | } 32 | ], 33 | "source": [ 34 | "import tega.driver\n", 35 | "d = tega.driver.Driver(host='192.168.57.133')\n", 36 | "edges = d.get('topo.edges')\n", 37 | "g = nx.Graph([[edge['source'], edge['target']] for edge in edges])\n", 38 | "nx.draw(g, node_size=700, with_labels=True)" 39 | ] 40 | } 41 | ], 42 | "metadata": { 43 | "kernelspec": { 44 | "display_name": "Python 3", 45 | "language": "python", 46 | "name": "python3" 47 | }, 48 | "language_info": { 49 | "codemirror_mode": { 50 | "name": "ipython", 51 | "version": 3 52 | }, 53 | "file_extension": ".py", 54 | "mimetype": "text/x-python", 55 | "name": "python", 56 | "nbconvert_exporter": "python", 57 | "pygments_lexer": "ipython3", 58 | "version": "3.5.1" 59 | } 60 | }, 61 | "nbformat": 4, 62 | "nbformat_minor": 0 63 | } 64 | --------------------------------------------------------------------------------