├── .gitignore ├── hover ├── plugin_driver.go ├── api │ └── module.go ├── util │ ├── log.go │ ├── handle_pool.go │ └── uuid.go ├── db.go ├── hoverd │ └── main.go ├── iomodule.h ├── canvas │ ├── adapter_bridge.go │ ├── adapter.go │ ├── adapter_bpf.go │ └── graph.go ├── daemon │ ├── common_test.go │ └── adapter_bridge_test.go ├── netutil.go ├── patch.go ├── external_interfaces.go ├── renderer.go ├── bpf │ ├── bpf_module.go │ ├── bpf_table.go │ └── cfiles.go └── netlink_monitor.go ├── images └── iomodules.png ├── policy ├── client │ ├── client_suite_test.go │ ├── client.go │ └── client_test.go ├── server │ ├── server_suite_test.go │ └── dataplane_test.go ├── database │ ├── database_suite_test.go │ ├── database_test.go │ └── database.go ├── test │ ├── del.sh │ └── test.sh ├── models │ ├── dataplane.go │ └── policy.go ├── log │ └── log.go ├── policy │ └── main.go ├── fakes │ └── dataplane.go └── policy-ctl │ └── policy-ctl.go ├── gbp ├── log.go ├── gbp │ └── main.go ├── register.go ├── policy.go ├── test_basic.sh ├── server.go └── dataplane.go ├── dnsmon ├── log.go ├── dnsmon │ └── main.go ├── server.go └── dataplane.go ├── README.md └── LICENSE.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *.pyc 4 | .project 5 | -------------------------------------------------------------------------------- /hover/plugin_driver.go: -------------------------------------------------------------------------------- 1 | // vim: set ts=8:sts=8:sw=8:noet 2 | 3 | package hover 4 | -------------------------------------------------------------------------------- /images/iomodules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iovisor/iomodules/HEAD/images/iomodules.png -------------------------------------------------------------------------------- /policy/client/client_suite_test.go: -------------------------------------------------------------------------------- 1 | package client_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestClient(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Client Suite") 13 | } 14 | -------------------------------------------------------------------------------- /policy/server/server_suite_test.go: -------------------------------------------------------------------------------- 1 | package server_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestServer(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Server Suite") 13 | } 14 | -------------------------------------------------------------------------------- /policy/database/database_suite_test.go: -------------------------------------------------------------------------------- 1 | package database_test 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestDatabase(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Database Suite") 13 | } 14 | -------------------------------------------------------------------------------- /policy/test/del.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo ip netns del host1 4 | sudo ip netns del host2 5 | sudo ip netns del cont1 6 | sudo ip netns del cont2 7 | sudo ip link del veth1 8 | sudo ip link del veth2 9 | sudo ip link del br0 10 | 11 | sudo pkill policy 12 | sudo pkill hoverd 13 | 14 | 15 | -------------------------------------------------------------------------------- /policy/models/dataplane.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type ModuleEntry struct { 4 | Id string `json:"id"` 5 | ModuleType string `json:"module_type"` 6 | DisplayName string `json:"display_name"` 7 | Perm string `json:"permissions"` 8 | Config map[string]interface{} `json:"config"` 9 | Tags []string `json:"tags"` 10 | } 11 | 12 | type TableEntry struct { 13 | Key string `json:"key"` 14 | Value string `json:"value"` 15 | } 16 | -------------------------------------------------------------------------------- /hover/api/module.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package api 16 | 17 | type ModuleBase struct { 18 | ModuleType string `json:"module_type"` 19 | DisplayName string `json:"display_name"` 20 | Tags []string `json:"tags"` 21 | Config map[string]interface{} `json:"config"` 22 | } 23 | type Module struct { 24 | ModuleBase 25 | Id string `json:"id"` 26 | Perm string `json:"permissions"` 27 | } 28 | 29 | type ModuleTableEntry struct { 30 | Key string `json:"key"` 31 | Value string `json:"value"` 32 | } 33 | -------------------------------------------------------------------------------- /gbp/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gbp 16 | 17 | import ( 18 | _ "io/ioutil" 19 | "log" 20 | "os" 21 | ) 22 | 23 | var ( 24 | Debug *log.Logger 25 | Info *log.Logger 26 | Warn *log.Logger 27 | Error *log.Logger 28 | ) 29 | 30 | func logInit() { 31 | Debug = log.New(os.Stdout, "DBG: ", log.Ldate|log.Ltime|log.Lshortfile) 32 | Info = log.New(os.Stdout, "INF: ", log.Ldate|log.Ltime) 33 | Warn = log.New(os.Stdout, "WRN: ", log.Ldate|log.Ltime) 34 | Error = log.New(os.Stderr, "ERR: ", log.Ldate|log.Ltime) 35 | } 36 | 37 | func init() { 38 | logInit() 39 | } 40 | -------------------------------------------------------------------------------- /dnsmon/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dnsmon 16 | 17 | import ( 18 | _ "io/ioutil" 19 | "log" 20 | "os" 21 | ) 22 | 23 | var ( 24 | Debug *log.Logger 25 | Info *log.Logger 26 | Warn *log.Logger 27 | Error *log.Logger 28 | ) 29 | 30 | func logInit() { 31 | Debug = log.New(os.Stdout, "DBG: ", log.Ldate|log.Ltime|log.Lshortfile) 32 | Info = log.New(os.Stdout, "INF: ", log.Ldate|log.Ltime) 33 | Warn = log.New(os.Stdout, "WRN: ", log.Ldate|log.Ltime) 34 | Error = log.New(os.Stderr, "ERR: ", log.Ldate|log.Ltime) 35 | } 36 | 37 | func init() { 38 | logInit() 39 | } 40 | -------------------------------------------------------------------------------- /policy/log/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package log 16 | 17 | import ( 18 | _ "io/ioutil" 19 | "log" 20 | "os" 21 | ) 22 | 23 | var ( 24 | Debug *log.Logger 25 | Info *log.Logger 26 | Warn *log.Logger 27 | Error *log.Logger 28 | ) 29 | 30 | func logInit() { 31 | Debug = log.New(os.Stdout, "DBG: ", log.Ldate|log.Ltime|log.Lshortfile) 32 | Info = log.New(os.Stdout, "INF: ", log.Ldate|log.Ltime) 33 | Warn = log.New(os.Stdout, "WRN: ", log.Ldate|log.Ltime) 34 | Error = log.New(os.Stderr, "ERR: ", log.Ldate|log.Ltime) 35 | } 36 | 37 | func init() { 38 | logInit() 39 | } 40 | -------------------------------------------------------------------------------- /hover/util/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "io/ioutil" 19 | "log" 20 | "os" 21 | ) 22 | 23 | var ( 24 | Debug *log.Logger 25 | Info *log.Logger 26 | Warn *log.Logger 27 | Error *log.Logger 28 | ) 29 | 30 | func logInit() { 31 | Debug = log.New(ioutil.Discard, "DBG: ", log.Ldate|log.Ltime|log.Lshortfile) 32 | Info = log.New(os.Stdout, "INF: ", log.Ldate|log.Ltime) 33 | Warn = log.New(os.Stdout, "WRN: ", log.Ldate|log.Ltime) 34 | Error = log.New(os.Stderr, "ERR: ", log.Ldate|log.Ltime) 35 | } 36 | 37 | func init() { 38 | logInit() 39 | } 40 | -------------------------------------------------------------------------------- /policy/models/policy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package models 16 | 17 | type Policy struct { 18 | Id string `json:"id"` 19 | SourceEPG string `json:"sourceepg"` 20 | SourcePort string `json:"sourceport"` 21 | DestEPG string `json:"destepg"` 22 | DestPort string `json:"destport"` 23 | Protocol string `json:"protocol"` 24 | Action string `json:"action"` // action:Allow/Redirect 25 | } 26 | 27 | type EndpointEntry struct { 28 | Id string `json:"id"` 29 | Ip string `json:"ip"` 30 | EpgId string `json:"epgid"` 31 | } 32 | 33 | type InfoEntry struct { 34 | Id string `json:"id"` 35 | } 36 | 37 | type EndpointGroup struct { 38 | Id string `json:"id"` 39 | Epg string `json:"epg"` 40 | WireId string `json:"wire-id"` 41 | } 42 | -------------------------------------------------------------------------------- /hover/db.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // vim: set ts=8:sts=8:sw=8:noet 16 | 17 | package hover 18 | 19 | import ( 20 | "database/sql" 21 | "github.com/jmoiron/sqlx" 22 | _ "github.com/mattn/go-sqlite3" 23 | ) 24 | 25 | var schema = ` 26 | CREATE TABLE person ( 27 | first_name text, 28 | last_name text, 29 | email text 30 | ); 31 | 32 | CREATE TABLE place ( 33 | country text, 34 | city text NULL, 35 | telcode integer 36 | )` 37 | 38 | type person struct { 39 | FirstName string `db:"first_name"` 40 | LastName string `db:"last_name"` 41 | Email string 42 | } 43 | 44 | type place struct { 45 | Country string 46 | City sql.NullString 47 | TelCode int 48 | } 49 | 50 | var db *sqlx.DB 51 | 52 | func Connect() { 53 | db = sqlx.MustConnect("sqlite3", ":memory:") 54 | db.Exec(schema) 55 | } 56 | -------------------------------------------------------------------------------- /hover/hoverd/main.go: -------------------------------------------------------------------------------- 1 | // vim: set ts=8:sts=8:sw=8:noet 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "path/filepath" 12 | "syscall" 13 | 14 | "github.com/iovisor/iomodules/hover/daemon" 15 | "github.com/iovisor/iomodules/hover/util" 16 | ) 17 | 18 | var listenSocket string 19 | var helpFlag bool 20 | 21 | func init() { 22 | const ( 23 | listenSocketDefault = "127.0.0.1:5000" 24 | listenSocketHelp = "address:port to serve up the api" 25 | ) 26 | flag.StringVar(&listenSocket, "listen", listenSocketDefault, listenSocketHelp) 27 | flag.BoolVar(&helpFlag, "h", false, "print this help") 28 | flag.Usage = func() { 29 | fmt.Printf("Usage: %s -listen 0.0.0.0:5000\n", filepath.Base(os.Args[0])) 30 | fmt.Printf(" -listen ADDR:PORT %s (default=%s)\n", listenSocketHelp, listenSocketDefault) 31 | } 32 | } 33 | 34 | func main() { 35 | flag.Parse() 36 | if helpFlag { 37 | flag.Usage() 38 | os.Exit(0) 39 | } 40 | 41 | c := make(chan os.Signal, 1) 42 | signal.Notify(c, syscall.SIGTERM) 43 | signal.Notify(c, os.Interrupt) 44 | s := daemon.NewServer() 45 | go func() { 46 | <-c 47 | if s != nil { 48 | s.Close() 49 | } 50 | os.Exit(1) 51 | }() 52 | if s == nil { 53 | util.Warn.Println("Failed to start Hover Server") 54 | os.Exit(1) 55 | } 56 | util.Info.Printf("Hover Server listening on %s\n", listenSocket) 57 | http.ListenAndServe(listenSocket, s.Handler()) 58 | } 59 | -------------------------------------------------------------------------------- /hover/iomodule.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) PLUMgrid, Inc. 2 | // Licensed under the Apache License, Version 2.0 (the "License") 3 | 4 | #pragma once 5 | 6 | enum { 7 | RX_OK, 8 | RX_REDIRECT, 9 | RX_DROP, 10 | RX_RECIRCULATE, 11 | RX_ERROR, 12 | }; 13 | 14 | struct type_value { 15 | u64 type:8; 16 | u64 value:56; 17 | }; 18 | struct metadata { 19 | // An array of type/value pairs for the module to do with as it pleases. The 20 | // array is initialized to zero when the event first enters the module chain. 21 | // The values are preserved across modules. 22 | struct type_value data[8]; 23 | 24 | // A field reserved for use by the wrapper and helper functions. 25 | u32 flags; 26 | 27 | // The length of the packet currently being processed. Read-only. 28 | u32 pktlen; 29 | 30 | // The module id currently processing the packet. 31 | int module_id; 32 | 33 | // The interface on which a packet was received. Numbering is local to the 34 | // module. 35 | int in_ifc; 36 | 37 | // If the module intends to forward the packet, it must call pkt_redirect to 38 | // set this field to determine the next-hop. 39 | int redir_ifc; 40 | 41 | int clone_ifc; 42 | }; 43 | 44 | // iomodule must implement this function to attach to the networking stack 45 | static int handle_rx(void *pkt, struct metadata *md); 46 | 47 | static int pkt_redirect(void *pkt, struct metadata *md, int ifc); 48 | static int pkt_mirror(void *pkt, struct metadata *md, int ifc); 49 | static int pkt_drop(void *pkt, struct metadata *md); 50 | -------------------------------------------------------------------------------- /hover/util/handle_pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "fmt" 19 | "github.com/willf/bitset" 20 | ) 21 | 22 | // HandlePool is used to contain a sequential list of integer handles. Storage is a bit set. 23 | type HandlePool struct { 24 | bitset.BitSet 25 | } 26 | 27 | // NewHandlePool returns a new handle pool with size entries available. 28 | func NewHandlePool(size uint) *HandlePool { 29 | handles := &HandlePool{} 30 | // make sure ids is big enough, triggers extendSetMaybe 31 | handles.Set(size - 1).Clear(size - 1) 32 | // turn all the bits on 33 | handles.InPlaceUnion(handles.Complement()) 34 | return handles 35 | } 36 | 37 | // Acquire returns the lowest available id in the pool, or error if exhausted. 38 | func (handles *HandlePool) Acquire() (int, error) { 39 | handle, ok := handles.NextSet(0) 40 | if !ok { 41 | return -1, fmt.Errorf("HandlePool: pool empty") 42 | } 43 | handles.Clear(handle) 44 | return int(handle + 1), nil 45 | } 46 | 47 | // Release returns the id back into the pool 48 | func (handles *HandlePool) Release(handle int) { 49 | handles.Set(uint(handle - 1)) 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IO modules 2 | This repo contains the Hover Framework (IOModule manager) and plugins 3 | 4 | # Hover Framework 5 | Hover framework is a userspace deamon for managing IO Modules. It exposes REST front-end for dynamically loading, configuring, linking different modules to make a network topology. 6 | 7 | # IO Modules Architecture 8 | ![picture](images/iomodules.png) 9 | 10 | # Requirements 11 | * google go version 1.4 or greater 12 | * docker for some of the tests 13 | * BCC 14 | 15 | # Installing Hover 16 | ```bash 17 | # prereqs 18 | # make sure you have exported $GOPATH to your workspace directory. 19 | go get github.com/vishvananda/netns 20 | go get github.com/willf/bitset 21 | go get github.com/gorilla/mux 22 | # to pull customized fork of netlink 23 | go get github.com/vishvananda/netlink 24 | cd $GOPATH/src/github.com/vishvananda/netlink 25 | git remote add drzaeus77 https://github.com/drzaeus77/netlink 26 | git fetch drzaeus77 27 | git reset --hard drzaeus77/master 28 | 29 | go get github.com/iovisor/iomodules/hover 30 | go install github.com/iovisor/iomodules/hover/hoverd 31 | sudo -E go test -v github.com/iovisor/iomodules/hover/daemon 32 | 33 | # run the hoverd binary in standalone mode 34 | sudo $GOPATH/bin/hoverd 35 | ``` 36 | # Examples 37 | # DnsMon IOModule 38 | IO module that monitors and maintains statistics for IPv4/IPv6 DNS Queries 39 | ```bash 40 | go get github.com/iovisor/iomodules/dnsmon 41 | go install github.com/iovisor/iomodules/dnsmon/dnsmon 42 | sudo $GOPATH/bin/dnsmon -hover http://127.0.0.1:5000 43 | ``` 44 | # Policy IOModule 45 | IO module that implements group based policies for containers. 46 | ```bash 47 | go get github.com/iovisor/iomodules/policy 48 | $GOPATH/src/github.com/iovisor/iomodules/policy/test/test.sh 49 | 50 | 51 | -------------------------------------------------------------------------------- /dnsmon/dnsmon/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "net/http" 21 | "os" 22 | "path/filepath" 23 | 24 | "github.com/iovisor/iomodules/dnsmon" 25 | ) 26 | 27 | var listenSocket string 28 | var hoverUrl string 29 | var helpFlag bool 30 | 31 | func init() { 32 | const ( 33 | hoverDefault = "" 34 | hoverHelp = "Local hover URL" 35 | listenSocketDefault = "127.0.0.1:5001" 36 | listenSocketHelp = "address:port to listen for updates" 37 | ) 38 | flag.StringVar(&hoverUrl, "hover", hoverDefault, hoverHelp) 39 | flag.StringVar(&listenSocket, "listen", listenSocketDefault, listenSocketHelp) 40 | flag.BoolVar(&helpFlag, "h", false, "print this help") 41 | 42 | flag.Usage = func() { 43 | fmt.Printf("Usage: %s -hover http://localhost:5000\n", filepath.Base(os.Args[0])) 44 | fmt.Printf(" -hover URL %s (default=%s)\n", hoverHelp, hoverDefault) 45 | fmt.Printf(" -listen ADDR:PORT %s (default=%s)\n", listenSocketHelp, listenSocketDefault) 46 | } 47 | } 48 | 49 | func main() { 50 | flag.Parse() 51 | if helpFlag { 52 | flag.Usage() 53 | os.Exit(0) 54 | } 55 | if len(hoverUrl) == 0 { 56 | fmt.Println("Missing argument -hover") 57 | flag.Usage() 58 | os.Exit(1) 59 | } 60 | srv, err := dnsmon.NewServer(hoverUrl) 61 | if err != nil { 62 | fmt.Println(err) 63 | os.Exit(1) 64 | } 65 | dnsmon.Info.Printf("DnsMon Server listening on %s\n", listenSocket) 66 | http.ListenAndServe(listenSocket, srv.Handler()) 67 | } 68 | -------------------------------------------------------------------------------- /policy/policy/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 PLUMgrid and others 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "net/http" 21 | "os" 22 | 23 | "github.com/iovisor/iomodules/policy/log" 24 | "github.com/iovisor/iomodules/policy/server" 25 | ) 26 | 27 | var helpFlag bool 28 | var dataplaneUrl string 29 | var listenSocketDefault string 30 | var dataplaneHelp string 31 | var listenSocket string 32 | 33 | func init() { 34 | const ( 35 | dataplaneDefault = "" 36 | dataplaneHelp = "Local dataplane URL" 37 | listenSocketDefault = "127.0.0.1:5001" 38 | listenSocketHelp = "address:port to listen for policy updates" 39 | ) 40 | flag.StringVar(&dataplaneUrl, "dataplane", dataplaneDefault, dataplaneHelp) 41 | flag.StringVar(&listenSocket, "listen", listenSocketDefault, listenSocketHelp) 42 | flag.BoolVar(&helpFlag, "h", false, "print this help") 43 | 44 | flag.Usage = func() { 45 | fmt.Printf(" -dataplane URL %s (default=%s)\n", dataplaneHelp, dataplaneDefault) 46 | fmt.Printf(" -listen ADDR:PORT %s (default=%s)\n", listenSocketHelp, listenSocketDefault) 47 | } 48 | } 49 | 50 | func main() { 51 | flag.Parse() 52 | if helpFlag { 53 | flag.Usage() 54 | os.Exit(0) 55 | } 56 | if len(dataplaneUrl) == 0 { 57 | fmt.Println("Missing argument -dataplane") 58 | flag.Usage() 59 | os.Exit(1) 60 | } 61 | p, err := server.NewServer(dataplaneUrl, ":memory:") 62 | if err != nil { 63 | fmt.Println(err) 64 | os.Exit(1) 65 | } 66 | log.Info.Printf("Policy Server listening on %s\n", listenSocket) 67 | http.ListenAndServe(listenSocket, p.Handler()) 68 | } 69 | -------------------------------------------------------------------------------- /hover/util/uuid.go: -------------------------------------------------------------------------------- 1 | // vim: set ts=8:sts=8:sw=8:noet 2 | 3 | package util 4 | 5 | import ( 6 | "crypto/aes" 7 | "crypto/cipher" 8 | "crypto/rand" 9 | "encoding/binary" 10 | "encoding/hex" 11 | "fmt" 12 | "strings" 13 | ) 14 | 15 | func NewUUID4() string { 16 | uuid := make([]byte, 16) 17 | _, err := rand.Read(uuid) 18 | if err != nil { 19 | panic(fmt.Errorf("error reading random bytes: %s", err)) 20 | } 21 | // variant bits; see section 4.1.1 22 | uuid[8] = uuid[8]&^0xc0 | 0x80 23 | // version 4 (pseudo-random); see section 4.1.3 24 | uuid[6] = uuid[6]&^0xf0 | 0x40 25 | ret := fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]) 26 | return ret 27 | } 28 | 29 | // UUIDEncrypter allows binary data up to the block size (16 bytes) to be 30 | // encoded and used as a key (uuid). 31 | type UUIDEncrypter struct { 32 | iv []byte 33 | c cipher.Block 34 | } 35 | 36 | var Encrypter *UUIDEncrypter 37 | 38 | func init() { 39 | var err error 40 | Encrypter, err = NewUUIDEncrypter() 41 | if err != nil { 42 | panic(err) 43 | } 44 | } 45 | 46 | func NewUUIDEncrypter() (*UUIDEncrypter, error) { 47 | iv := make([]byte, aes.BlockSize) 48 | _, err := rand.Read(iv) 49 | if err != nil { 50 | return nil, fmt.Errorf("NewUUIDEncrypter: error seeding iv: %s", err) 51 | } 52 | 53 | key := []byte("doesntmatter1234") 54 | c, err := aes.NewCipher(key) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return &UUIDEncrypter{iv: iv, c: c}, nil 59 | } 60 | 61 | // EncodePair takes a pair of ints and encodes them cryptographically as a uuid 62 | // string 63 | func (u *UUIDEncrypter) EncodePair(a, b int) string { 64 | text := make([]byte, aes.BlockSize) 65 | binary.BigEndian.PutUint32(text[8:12], uint32(a)) 66 | binary.BigEndian.PutUint32(text[12:16], uint32(b)) 67 | stream := cipher.NewCTR(u.c, u.iv) 68 | stream.XORKeyStream(text, text) 69 | return fmt.Sprintf("%x-%x-%x-%x-%x", text[0:4], text[4:6], text[6:8], text[8:10], text[10:]) 70 | } 71 | 72 | // DecodePair takes a uuid string created by EncodePair and reverses that into 73 | // the original pair of ints. 74 | func (u *UUIDEncrypter) DecodePair(uuid string) (int, int, error) { 75 | text := make([]byte, aes.BlockSize) 76 | text, err := hex.DecodeString(strings.Replace(uuid, "-", "", -1)) 77 | if err != nil { 78 | return 0, 0, err 79 | } 80 | stream := cipher.NewCTR(u.c, u.iv) 81 | stream.XORKeyStream(text, text) 82 | a := int(binary.BigEndian.Uint32(text[8:12])) 83 | b := int(binary.BigEndian.Uint32(text[12:16])) 84 | return a, b, nil 85 | } 86 | -------------------------------------------------------------------------------- /gbp/gbp/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 PLUMgrid and others 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "net/http" 21 | "os" 22 | "path/filepath" 23 | 24 | "github.com/iovisor/iomodules/gbp" 25 | ) 26 | 27 | var upstreamUrl string 28 | var listenSocket string 29 | var dataplaneUrl string 30 | var helpFlag bool 31 | 32 | func init() { 33 | const ( 34 | upstreamDefault = "" 35 | upstreamHelp = "Upstream GBP API endpoint URL" 36 | dataplaneDefault = "" 37 | dataplaneHelp = "Local dataplane URL" 38 | listenSocketDefault = "127.0.0.1:5001" 39 | listenSocketHelp = "address:port to listen for policy updates" 40 | ) 41 | flag.StringVar(&upstreamUrl, "upstream", upstreamDefault, upstreamHelp) 42 | flag.StringVar(&dataplaneUrl, "dataplane", dataplaneDefault, dataplaneHelp) 43 | flag.StringVar(&listenSocket, "listen", listenSocketDefault, listenSocketHelp) 44 | flag.BoolVar(&helpFlag, "h", false, "print this help") 45 | 46 | flag.Usage = func() { 47 | fmt.Printf("Usage: %s -upstream http://www.example.com:8181\n", filepath.Base(os.Args[0])) 48 | fmt.Printf(" -upstream URL %s (default=%s)\n", upstreamHelp, upstreamDefault) 49 | fmt.Printf(" -dataplane URL %s (default=%s)\n", dataplaneHelp, dataplaneDefault) 50 | fmt.Printf(" -listen ADDR:PORT %s (default=%s)\n", listenSocketHelp, listenSocketDefault) 51 | } 52 | } 53 | 54 | func main() { 55 | flag.Parse() 56 | if helpFlag { 57 | flag.Usage() 58 | os.Exit(0) 59 | } 60 | if len(upstreamUrl) == 0 { 61 | fmt.Println("Missing argument -upstream") 62 | flag.Usage() 63 | os.Exit(1) 64 | } 65 | if len(dataplaneUrl) == 0 { 66 | fmt.Println("Missing argument -dataplane") 67 | flag.Usage() 68 | os.Exit(1) 69 | } 70 | notifier := gbp.NewNotifier(upstreamUrl, listenSocket) 71 | if err := notifier.NotifyEndpointUp(); err != nil { 72 | fmt.Println(err) 73 | os.Exit(1) 74 | } 75 | g, err := gbp.NewServer(upstreamUrl, dataplaneUrl) 76 | if err != nil { 77 | fmt.Println(err) 78 | os.Exit(1) 79 | } 80 | gbp.Info.Printf("GBP Server listening on %s\n", listenSocket) 81 | http.ListenAndServe(listenSocket, g.Handler()) 82 | } 83 | -------------------------------------------------------------------------------- /gbp/register.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 PLUMgrid and others 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Notify southbound renderer of new interfaces 16 | 17 | package gbp 18 | 19 | import ( 20 | "bytes" 21 | "encoding/json" 22 | "fmt" 23 | "io/ioutil" 24 | "net/http" 25 | ) 26 | 27 | type Notifier struct { 28 | url string 29 | client *http.Client 30 | location string 31 | } 32 | 33 | func NewNotifier(url, location string) *Notifier { 34 | return &Notifier{ 35 | url: url, 36 | client: &http.Client{}, 37 | location: location, 38 | } 39 | } 40 | 41 | type l3Address struct { 42 | IPAddress string `json:"ip-address"` 43 | L3Context string `json:"l3-context"` 44 | } 45 | type endpoint struct { 46 | // Name string `json:"name"` 47 | EndpointGroups []string `json:"endpoint-groups"` 48 | NetworkContainment string `json:"network-containment"` 49 | L3Address []l3Address `json:"l3-address"` 50 | Tenant string `json:"tenant"` 51 | Location string `json:"iovisor:uri"` 52 | } 53 | type endpointNotification struct { 54 | Input *endpoint `json:"input"` 55 | } 56 | 57 | func (n *Notifier) NotifyEndpointUp() error { 58 | Debug.Println("NotifyEndpointUp") 59 | // hardcoded for now 60 | l3addr := l3Address{"169.254.0.1", "finance"} 61 | notification := &endpointNotification{ 62 | Input: &endpoint{ 63 | // Name: "client1", 64 | EndpointGroups: []string{"client"}, 65 | NetworkContainment: "finance", 66 | L3Address: []l3Address{l3addr}, 67 | Tenant: "pepsi", 68 | Location: n.location, 69 | }, 70 | } 71 | b, err := json.Marshal(notification) 72 | if err != nil { 73 | return err 74 | } 75 | r := bytes.NewReader(b) 76 | req, err := http.NewRequest("POST", n.url+"/restconf/operations/endpoint:register-endpoint", r) 77 | req.SetBasicAuth("admin", "admin") 78 | req.Header.Set("Content-Type", "application/json") 79 | resp, err := n.client.Do(req) 80 | if err != nil { 81 | return err 82 | } 83 | _, err = ioutil.ReadAll(resp.Body) 84 | defer resp.Body.Close() 85 | if err != nil { 86 | return err 87 | } 88 | if resp.StatusCode != http.StatusOK { 89 | return fmt.Errorf("Bad status %d from upstream", resp.StatusCode) 90 | } 91 | return nil 92 | } 93 | func (n *Notifier) NotifyEndpointDown() { 94 | } 95 | -------------------------------------------------------------------------------- /hover/canvas/adapter_bridge.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // vim: set ts=8:sts=8:sw=8:noet 16 | 17 | package canvas 18 | 19 | import ( 20 | "fmt" 21 | "github.com/vishvananda/netlink" 22 | 23 | "github.com/iovisor/iomodules/hover/api" 24 | ) 25 | 26 | type BridgeAdapter struct { 27 | uuid string 28 | name string 29 | tags []string 30 | perm uint 31 | config map[string]interface{} 32 | link *netlink.Bridge 33 | } 34 | 35 | func NewBridgeAdapter(link *netlink.Bridge) *BridgeAdapter { 36 | return &BridgeAdapter{ 37 | uuid: link.Attrs().Name, 38 | name: link.Attrs().Name, 39 | tags: []string{}, 40 | perm: PermR, 41 | config: make(map[string]interface{}), 42 | link: link, 43 | } 44 | } 45 | 46 | func (ba *BridgeAdapter) UUID() string { return "b:" + ba.uuid } 47 | func (ba *BridgeAdapter) FD() int { return -1 } 48 | func (ba *BridgeAdapter) Tags() []string { return []string{} } 49 | func (ba *BridgeAdapter) Type() string { return "bridge" } 50 | func (ba *BridgeAdapter) Name() string { return ba.name } 51 | func (ba *BridgeAdapter) Perm() uint { return ba.perm } 52 | func (ba *BridgeAdapter) Close() {} 53 | 54 | func (ba *BridgeAdapter) SetConfig(req api.ModuleBase, g Graph, id int) error { 55 | return nil 56 | } 57 | 58 | func (ba *BridgeAdapter) Config() map[string]interface{} { 59 | return ba.config 60 | } 61 | 62 | func (ba *BridgeAdapter) Tables() []map[string]interface{} { 63 | return []map[string]interface{}{} 64 | } 65 | 66 | type BridgeTable struct { 67 | } 68 | 69 | func (ba *BridgeAdapter) Table(name string) AdapterTable { return &BridgeTable{} } 70 | func (table *BridgeTable) ID() string { return "0" } 71 | func (table *BridgeTable) Name() string { return "" } 72 | func (table *BridgeTable) Config() map[string]interface{} { return map[string]interface{}{} } 73 | func (table *BridgeTable) Get(key string) (interface{}, bool) { return nil, false } 74 | 75 | func (table *BridgeTable) Set(key, val string) error { 76 | return fmt.Errorf("BridgeTable: Set operation not supported") 77 | } 78 | func (table *BridgeTable) Delete(key string) error { 79 | return fmt.Errorf("BridgeTable: Delete operation not supported") 80 | } 81 | func (table *BridgeTable) Iter() <-chan api.ModuleTableEntry { 82 | ch := make(chan api.ModuleTableEntry) 83 | close(ch) 84 | return ch 85 | } 86 | -------------------------------------------------------------------------------- /hover/canvas/adapter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // vim: set ts=8:sts=8:sw=8:noet 16 | 17 | package canvas 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | 23 | "github.com/iovisor/iomodules/hover/api" 24 | "github.com/iovisor/iomodules/hover/bpf" 25 | "github.com/iovisor/iomodules/hover/util" 26 | ) 27 | 28 | var ( 29 | Debug = util.Debug 30 | Info = util.Info 31 | Warn = util.Warn 32 | Error = util.Error 33 | ) 34 | 35 | const ( 36 | PermW = 1 << (1 + iota) 37 | PermR 38 | ) 39 | 40 | type Adapter interface { 41 | UUID() string 42 | FD() int 43 | Close() 44 | Type() string 45 | Name() string 46 | Tags() []string 47 | Perm() uint 48 | Config() map[string]interface{} 49 | SetConfig(req api.ModuleBase, g Graph, id int) error 50 | Tables() []map[string]interface{} 51 | Table(name string) AdapterTable 52 | } 53 | 54 | type AdapterTable interface { 55 | ID() string 56 | Name() string 57 | Config() map[string]interface{} 58 | Get(key string) (interface{}, bool) 59 | Set(key, val string) error 60 | Delete(key string) error 61 | Iter() <-chan api.ModuleTableEntry 62 | } 63 | 64 | type Interface interface { 65 | ID() int 66 | Name() string 67 | } 68 | 69 | type AdapterNode struct { 70 | NodeBase 71 | adapter Adapter 72 | } 73 | 74 | func NewAdapter(req api.ModuleBase, g Graph, id int) (adapter Adapter, err error) { 75 | uuid := util.NewUUID4() 76 | 77 | parts := strings.SplitN(req.ModuleType, "/", 2) 78 | switch parts[0] { 79 | case "bpf": 80 | var subtype string 81 | if len(parts) > 1 { 82 | subtype = parts[1] 83 | } 84 | a := &BpfAdapter{ 85 | uuid: uuid[:8], 86 | perm: PermR | PermW, 87 | config: make(map[string]interface{}), 88 | subtype: subtype, 89 | } 90 | if err = a.SetConfig(req, g, id); err != nil { 91 | return 92 | } 93 | adapter = a 94 | case "bridge": 95 | a := &BridgeAdapter{ 96 | uuid: uuid[:8], 97 | name: req.DisplayName, 98 | tags: req.Tags, 99 | perm: PermR | PermW, 100 | config: make(map[string]interface{}), 101 | } 102 | if err = a.SetConfig(req, g, id); err != nil { 103 | return 104 | } 105 | adapter = a 106 | default: 107 | err = fmt.Errorf("unknown ModuleType %s", req.ModuleType) 108 | return 109 | } 110 | return 111 | } 112 | 113 | func NewAdapterNode(adapter Adapter) *AdapterNode { 114 | return &AdapterNode{ 115 | NodeBase: NewNodeBase(-1, adapter.FD(), adapter.UUID(), "", bpf.MAX_INTERFACES), 116 | adapter: adapter, 117 | } 118 | } 119 | 120 | func (n *AdapterNode) Close() { n.adapter.Close() } 121 | func (n *AdapterNode) Adapter() Adapter { return n.adapter } 122 | -------------------------------------------------------------------------------- /hover/daemon/common_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package daemon 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | "io" 21 | "io/ioutil" 22 | "net/http" 23 | "net/http/httptest" 24 | "runtime/debug" 25 | "testing" 26 | 27 | "github.com/vishvananda/netlink" 28 | "github.com/vishvananda/netns" 29 | 30 | "github.com/iovisor/iomodules/hover" 31 | ) 32 | 33 | type testCase struct { 34 | url string // url of the request 35 | method string // which htttp method to use 36 | body io.Reader // body of the request 37 | code int // expected pass criteria 38 | } 39 | 40 | func testWrapObject(t *testing.T, body interface{}) io.Reader { 41 | b, err := json.Marshal(body) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | return bytes.NewReader(b) 46 | } 47 | 48 | func testSetup(t *testing.T) (*httptest.Server, func()) { 49 | //os.Remove("/tmp/hover.db") 50 | s := NewServer() 51 | if s == nil { 52 | t.Fatal("Could not start Hover server") 53 | } 54 | srv := httptest.NewServer(s.Handler()) 55 | return srv, func() { 56 | s.Close() 57 | srv.Close() 58 | } 59 | } 60 | 61 | func testNetnsPair(t *testing.T, prefix string) ([]*netlink.Veth, []netns.NsHandle, func()) { 62 | testns1 := hover.NewNs() 63 | testns2 := hover.NewNs() 64 | 65 | cleanup := func() { 66 | testns2.Close() 67 | testns1.Close() 68 | } 69 | 70 | l1, err := hover.NewVeth(testns1, prefix+"1", "eth0", "10.10.1.1/24", nil) 71 | if err != nil { 72 | cleanup() 73 | t.Fatal(err) 74 | } 75 | l2, err := hover.NewVeth(testns2, prefix+"2", "eth0", "10.10.1.2/24", nil) 76 | if err != nil { 77 | cleanup() 78 | t.Fatal(err) 79 | } 80 | return []*netlink.Veth{l1, l2}, []netns.NsHandle{testns1, testns2}, cleanup 81 | } 82 | 83 | func testLinkModules(t *testing.T, srv *httptest.Server, from, to string) string { 84 | var l linkEntry 85 | testOne(t, testCase{ 86 | url: srv.URL + "/links/", 87 | body: testWrapObject(t, map[string]interface{}{ 88 | "from": from, 89 | "to": to, 90 | }), 91 | }, &l) 92 | return l.Id 93 | } 94 | 95 | func testOne(t *testing.T, test testCase, rsp interface{}) { 96 | client := &http.Client{} 97 | 98 | var resp *http.Response 99 | var err error 100 | switch test.method { 101 | case "", "POST": 102 | resp, err = client.Post(test.url, "application/json", test.body) 103 | case "GET": 104 | resp, err = client.Get(test.url) 105 | default: 106 | req, err := http.NewRequest(test.method, test.url, test.body) 107 | if err != nil { 108 | t.Fatal(err) 109 | } 110 | resp, err = client.Do(req) 111 | } 112 | if err != nil { 113 | t.Fatal(err) 114 | } 115 | body, err := ioutil.ReadAll(resp.Body) 116 | resp.Body.Close() 117 | if err != nil { 118 | debug.PrintStack() 119 | t.Fatal(err) 120 | } 121 | if test.code == 0 { 122 | test.code = http.StatusOK 123 | } 124 | if resp.StatusCode != test.code { 125 | debug.PrintStack() 126 | t.Fatalf("Expected %d, got %d :: %s", test.code, resp.StatusCode, string(body)) 127 | } 128 | if rsp != nil { 129 | if err := json.Unmarshal(body, rsp); err != nil { 130 | t.Fatal(err) 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /hover/netutil.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // vim: set ts=8:sts=8:sw=8:noet 16 | 17 | package hover 18 | 19 | import ( 20 | "runtime" 21 | 22 | "github.com/vishvananda/netlink" 23 | "github.com/vishvananda/netns" 24 | ) 25 | 26 | var ( 27 | initNs netns.NsHandle 28 | ) 29 | 30 | func nsContext() func() { 31 | runtime.LockOSThread() 32 | return func() { 33 | if err := netns.Set(initNs); err != nil { 34 | panic(err) 35 | } 36 | runtime.UnlockOSThread() 37 | } 38 | } 39 | 40 | func init() { 41 | initNs, _ = netns.Get() 42 | } 43 | 44 | func RunInNs(fd netns.NsHandle, fn func() error) error { 45 | defer nsContext()() 46 | if err := netns.Set(fd); err != nil { 47 | return err 48 | } 49 | if err := fn(); err != nil { 50 | return err 51 | } 52 | return nil 53 | } 54 | 55 | // NewVeth creates a veth pair with the given name, moves the peer into the 56 | // namespace n, then renames the peer to dstName and assigns the provided ip. 57 | // If insideFn is not nil, also executes that function in the context of the 58 | // given namespace. 59 | func NewVeth(n netns.NsHandle, name, dstName, ip string, insideFn func(netlink.Link) error) (link *netlink.Veth, err error) { 60 | l := &netlink.Veth{ 61 | LinkAttrs: netlink.LinkAttrs{ 62 | Name: name, 63 | }, 64 | PeerName: name + "_", 65 | } 66 | if err = netlink.LinkAdd(l); err != nil { 67 | return 68 | } 69 | defer func() { 70 | if err != nil { 71 | netlink.LinkDel(l) 72 | } 73 | }() 74 | 75 | otherL, err := netlink.LinkByName(l.PeerName) 76 | if err != nil { 77 | return 78 | } 79 | if err = netlink.LinkSetNsFd(otherL, int(n)); err != nil { 80 | return 81 | } 82 | err = RunInNs(n, func() error { 83 | lo, err := netlink.LinkByName("lo") 84 | if err != nil { 85 | return err 86 | } 87 | err = netlink.LinkSetUp(lo) 88 | if err != nil { 89 | return err 90 | } 91 | l, err := netlink.LinkByName(name + "_") 92 | if err != nil { 93 | return err 94 | } 95 | if err = netlink.LinkSetName(l, dstName); err != nil { 96 | return err 97 | } 98 | l.Attrs().Name = dstName 99 | a, err := netlink.ParseIPNet(ip) 100 | if err != nil { 101 | return err 102 | } 103 | if err := netlink.AddrAdd(l, &netlink.Addr{IPNet: a}); err != nil { 104 | return err 105 | } 106 | if insideFn != nil { 107 | if err := insideFn(l); err != nil { 108 | return err 109 | } 110 | } 111 | if err = netlink.LinkSetUp(l); err != nil { 112 | return err 113 | } 114 | return nil 115 | }) 116 | if err != nil { 117 | return 118 | } 119 | if err = netlink.LinkSetUp(l); err != nil { 120 | return 121 | } 122 | link = l 123 | return 124 | } 125 | 126 | // NewNs creates a new network namespace and returns a handle to it. The caller 127 | // is responsible for calling Close on the handle when done with it. 128 | func NewNs() netns.NsHandle { 129 | runtime.LockOSThread() 130 | defer runtime.UnlockOSThread() 131 | origns, err := netns.Get() 132 | if err != nil { 133 | panic(err) 134 | } 135 | n, err := netns.New() 136 | if err != nil { 137 | panic(err) 138 | } 139 | if err := netns.Set(origns); err != nil { 140 | panic(err) 141 | } 142 | return n 143 | } 144 | -------------------------------------------------------------------------------- /hover/patch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // vim: set ts=8:sts=8:sw=8:noet 16 | 17 | package hover 18 | 19 | import ( 20 | "fmt" 21 | "syscall" 22 | 23 | "github.com/vishvananda/netlink" 24 | ) 25 | 26 | func ensureQdisc(link netlink.Link, qdiscType string, handle, parent uint32) (netlink.Qdisc, error) { 27 | qds, err := netlink.QdiscList(link) 28 | if err != nil { 29 | return nil, err 30 | } 31 | for _, q := range qds { 32 | if q.Attrs().Handle == handle { 33 | //Debug.Printf("Found existing ingress qdisc %x\n", q.Attrs().Handle) 34 | return q, nil 35 | } 36 | } 37 | qdisc := &netlink.GenericQdisc{ 38 | QdiscAttrs: netlink.QdiscAttrs{ 39 | LinkIndex: link.Attrs().Index, 40 | Handle: handle, 41 | Parent: parent, 42 | }, 43 | QdiscType: qdiscType, 44 | } 45 | if err := netlink.QdiscAdd(qdisc); err != nil { 46 | return nil, fmt.Errorf("failed ensuring qdisc: %v", err) 47 | } 48 | return qdisc, nil 49 | } 50 | 51 | func ensureIngressFd(link netlink.Link, fd int) error { 52 | q, err := ensureQdisc(link, "ingress", netlink.MakeHandle(0xffff, 0), netlink.HANDLE_INGRESS) 53 | if err != nil { 54 | return err 55 | } 56 | fHandle := netlink.MakeHandle(0, 1) 57 | filter := &netlink.U32{ 58 | FilterAttrs: netlink.FilterAttrs{ 59 | LinkIndex: link.Attrs().Index, 60 | Parent: q.Attrs().Handle, 61 | Priority: 1, 62 | Protocol: syscall.ETH_P_ALL, 63 | }, 64 | Actions: []netlink.Action{ 65 | &netlink.BpfAction{Fd: fd, Name: "bpf1"}, 66 | }, 67 | ClassId: fHandle, 68 | } 69 | filters, err := netlink.FilterList(link, netlink.HANDLE_MIN_INGRESS) 70 | if err != nil { 71 | return fmt.Errorf("failed fetching ingress filter list: %s", err) 72 | } 73 | for _, f := range filters { 74 | if f, ok := f.(*netlink.U32); ok { 75 | if f.ClassId == fHandle { 76 | return nil 77 | } 78 | // remove all previous filters to ensure the 79 | // current one is the only one to be executed 80 | netlink.FilterDel(f) 81 | } 82 | } 83 | if err := netlink.FilterAdd(filter); err != nil { 84 | return fmt.Errorf("failed adding ingress filter: %s", err) 85 | } 86 | //Debug.Printf("ensureIngressFd(%s) success\n", link.Attrs().Name) 87 | return nil 88 | } 89 | 90 | func ensureEgressFd(link netlink.Link, fd int) error { 91 | q, err := ensureQdisc(link, "fq_codel", netlink.MakeHandle(1, 0), netlink.HANDLE_ROOT) 92 | if err != nil { 93 | return err 94 | } 95 | fHandle := netlink.MakeHandle(0, 2) 96 | filter := &netlink.U32{ 97 | FilterAttrs: netlink.FilterAttrs{ 98 | LinkIndex: link.Attrs().Index, 99 | Parent: q.Attrs().Handle, 100 | Priority: 1, 101 | Protocol: syscall.ETH_P_ALL, 102 | }, 103 | Actions: []netlink.Action{ 104 | &netlink.BpfAction{Fd: fd, Name: "bpf1"}, 105 | }, 106 | ClassId: fHandle, 107 | } 108 | filters, err := netlink.FilterList(link, netlink.HANDLE_MIN_EGRESS) 109 | if err != nil { 110 | return fmt.Errorf("failed fetching egress filter list: %s", err) 111 | } 112 | for _, f := range filters { 113 | if f, ok := f.(*netlink.U32); ok { 114 | if f.ClassId == fHandle { 115 | return nil 116 | } 117 | } 118 | } 119 | if err := netlink.FilterAdd(filter); err != nil { 120 | return fmt.Errorf("failed adding egress filter: %v", err) 121 | } 122 | return nil 123 | } 124 | -------------------------------------------------------------------------------- /dnsmon/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dnsmon 16 | 17 | import ( 18 | "encoding/json" 19 | "github.com/gorilla/mux" 20 | "net/http" 21 | "runtime" 22 | ) 23 | 24 | type DnsmonServer struct { 25 | handler http.Handler 26 | dataplane *Dataplane 27 | } 28 | 29 | type routeResponse struct { 30 | statusCode int 31 | contentType string 32 | body interface{} 33 | } 34 | 35 | type handlerFunc func(r *http.Request) routeResponse 36 | 37 | func makeHandler(fn handlerFunc) http.HandlerFunc { 38 | return func(w http.ResponseWriter, r *http.Request) { 39 | defer func() { 40 | if r := recover(); r != nil { 41 | switch err := r.(type) { 42 | case runtime.Error: 43 | http.Error(w, "Internal error", http.StatusBadRequest) 44 | panic(err) 45 | case error: 46 | Error.Println(err.Error()) 47 | http.Error(w, err.Error(), http.StatusBadRequest) 48 | default: 49 | http.Error(w, "Internal error", http.StatusBadRequest) 50 | panic(r) 51 | } 52 | } 53 | }() 54 | 55 | rsp := fn(r) 56 | sendReply(w, r, &rsp) 57 | Info.Printf("%s %s %d\n", r.Method, r.URL, rsp.statusCode) 58 | return 59 | } 60 | } 61 | 62 | func redirect(url string, code int) routeResponse { 63 | return routeResponse{statusCode: code, body: url} 64 | } 65 | func notFound() routeResponse { 66 | return routeResponse{statusCode: http.StatusNotFound} 67 | } 68 | 69 | func sendReply(w http.ResponseWriter, r *http.Request, rsp *routeResponse) { 70 | if rsp.body != nil { 71 | if len(rsp.contentType) != 0 { 72 | w.Header().Set("Content-Type", rsp.contentType) 73 | } else { 74 | w.Header().Set("Content-Type", "application/json") 75 | } 76 | } 77 | switch { 78 | case rsp.statusCode == 0: 79 | w.WriteHeader(http.StatusOK) 80 | rsp.statusCode = http.StatusOK 81 | case 100 <= rsp.statusCode && rsp.statusCode < 300: 82 | w.WriteHeader(rsp.statusCode) 83 | case 300 <= rsp.statusCode && rsp.statusCode < 400: 84 | loc := "" 85 | if x, ok := rsp.body.(string); ok { 86 | loc = x 87 | } 88 | http.Redirect(w, r, loc, rsp.statusCode) 89 | case 400 <= rsp.statusCode: 90 | if rsp.statusCode == http.StatusNotFound { 91 | Info.Printf("Not Found: %s\n", r.URL) 92 | http.NotFound(w, r) 93 | } else { 94 | msg := "" 95 | if x, ok := rsp.body.(string); ok { 96 | msg = x 97 | } 98 | http.Error(w, msg, rsp.statusCode) 99 | } 100 | default: 101 | } 102 | if rsp.body != nil { 103 | if err := json.NewEncoder(w).Encode(rsp.body); err != nil { 104 | panic(err) 105 | } 106 | } 107 | } 108 | 109 | type infoEntry struct { 110 | Id string `json:"id"` 111 | } 112 | 113 | func (srv *DnsmonServer) handleInfoGet(r *http.Request) routeResponse { 114 | return routeResponse{ 115 | body: &infoEntry{Id: srv.dataplane.Id()}, 116 | } 117 | } 118 | 119 | func (srv *DnsmonServer) Handler() http.Handler { 120 | return srv.handler 121 | } 122 | 123 | func NewServer(dataplaneUri string) (*DnsmonServer, error) { 124 | Info.Println("DnsMon module starting") 125 | rtr := mux.NewRouter() 126 | 127 | srv := &DnsmonServer{ 128 | handler: rtr, 129 | dataplane: NewDataplane(), 130 | } 131 | if err := srv.dataplane.Init(dataplaneUri); err != nil { 132 | return nil, err 133 | } 134 | 135 | rtr.Methods("GET").Path("/info").HandlerFunc(makeHandler(srv.handleInfoGet)) 136 | 137 | // new routes go here 138 | 139 | return srv, nil 140 | } 141 | -------------------------------------------------------------------------------- /hover/external_interfaces.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package hover 16 | 17 | import ( 18 | "fmt" 19 | "syscall" 20 | 21 | "github.com/vishvananda/netlink" 22 | 23 | "github.com/iovisor/iomodules/hover/bpf" 24 | "github.com/iovisor/iomodules/hover/canvas" 25 | ) 26 | 27 | type InterfaceNode interface { 28 | canvas.Node 29 | Link() netlink.Link 30 | SetLink(netlink.Link) 31 | } 32 | 33 | type ExtInterface struct { 34 | canvas.NodeBase 35 | link netlink.Link 36 | } 37 | 38 | func NewExtInterface(link netlink.Link) *ExtInterface { 39 | return &ExtInterface{ 40 | NodeBase: canvas.NewNodeBase(-1, -1, link.Attrs().Name, "i:", 1), 41 | link: link, 42 | } 43 | } 44 | 45 | func (ifc *ExtInterface) FD() int { 46 | if ifc.NodeBase.FD() >= 0 { 47 | return ifc.NodeBase.FD() 48 | } 49 | cflags := []string{ 50 | fmt.Sprintf("-DINTERFACE_ID=%d", ifc.link.Attrs().Index), 51 | } 52 | bpf := bpf.NewBpfModule(bpf.NetdevTxC, cflags) 53 | if bpf == nil { 54 | panic(fmt.Errorf("Failed to compile bpf module for %s egress", ifc.Path())) 55 | } 56 | // free the llvm memory, just keep the fd 57 | defer bpf.Close() 58 | fd, err := bpf.LoadNet("egress") 59 | if err != nil { 60 | panic(err) 61 | } 62 | fd2, err := syscall.Dup(fd) 63 | if err != nil { 64 | panic(err) 65 | } 66 | ifc.NodeBase.SetFD(fd2) 67 | return ifc.NodeBase.FD() 68 | } 69 | 70 | func (ifc *ExtInterface) Link() netlink.Link { return ifc.link } 71 | func (ifc *ExtInterface) SetLink(link netlink.Link) { ifc.link = link } 72 | func (ifc *ExtInterface) SetID(id int) { ifc.NodeBase.SetID(id) } 73 | 74 | type IngressChain struct { 75 | fd int 76 | } 77 | 78 | func NewIngressChain(chain [4]int) (*IngressChain, error) { 79 | cflags := []string{ 80 | fmt.Sprintf("-DCHAIN_VALUE0=%#x", chain[0]), 81 | fmt.Sprintf("-DCHAIN_VALUE1=%#x", chain[1]), 82 | fmt.Sprintf("-DCHAIN_VALUE2=%#x", chain[2]), 83 | fmt.Sprintf("-DCHAIN_VALUE3=%#x", chain[3]), 84 | } 85 | //Debug.Printf("netdev: %v\n", cflags) 86 | bpf := bpf.NewBpfModule(bpf.NetdevRxC, cflags) 87 | if bpf == nil { 88 | return nil, fmt.Errorf("NewIngressChain bpf compile failed") 89 | } 90 | defer bpf.Close() 91 | fd, err := bpf.LoadNet("ingress") 92 | if err != nil { 93 | return nil, err 94 | } 95 | fd2, err := syscall.Dup(fd) 96 | if err != nil { 97 | return nil, err 98 | } 99 | return &IngressChain{fd: fd2}, nil 100 | } 101 | 102 | func (c *IngressChain) Close() { syscall.Close(c.fd) } 103 | func (c *IngressChain) FD() int { return c.fd } 104 | 105 | type EgressChain struct { 106 | fd int 107 | } 108 | 109 | func NewEgressChain(chain [4]int) (*EgressChain, error) { 110 | cflags := []string{ 111 | fmt.Sprintf("-DCHAIN_VALUE0=%#x", chain[0]), 112 | fmt.Sprintf("-DCHAIN_VALUE1=%#x", chain[1]), 113 | fmt.Sprintf("-DCHAIN_VALUE2=%#x", chain[2]), 114 | fmt.Sprintf("-DCHAIN_VALUE3=%#x", chain[3]), 115 | } 116 | //Debug.Printf("netdev: %v\n", cflags) 117 | bpf := bpf.NewBpfModule(bpf.NetdevEgressC, cflags) 118 | if bpf == nil { 119 | return nil, fmt.Errorf("NewEgressChain bpf compile failed") 120 | } 121 | defer bpf.Close() 122 | fd, err := bpf.LoadNet("egress") 123 | if err != nil { 124 | return nil, err 125 | } 126 | fd2, err := syscall.Dup(fd) 127 | if err != nil { 128 | return nil, err 129 | } 130 | return &EgressChain{fd: fd2}, nil 131 | } 132 | 133 | func (c *EgressChain) Close() { syscall.Close(c.fd) } 134 | func (c *EgressChain) FD() int { return c.fd } 135 | -------------------------------------------------------------------------------- /hover/canvas/adapter_bpf.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // vim: set ts=8:sts=8:sw=8:noet 16 | 17 | package canvas 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | 23 | "github.com/iovisor/iomodules/hover/api" 24 | "github.com/iovisor/iomodules/hover/bpf" 25 | ) 26 | 27 | type BpfAdapter struct { 28 | uuid string 29 | name string 30 | tags []string 31 | perm uint 32 | config map[string]interface{} 33 | bpf *bpf.BpfModule 34 | fd int 35 | subtype string 36 | } 37 | 38 | func NewBpfAdapter(uuid, name string, b *bpf.BpfModule) *BpfAdapter { 39 | return &BpfAdapter{ 40 | uuid: uuid[:8], 41 | name: name, 42 | config: make(map[string]interface{}), 43 | bpf: b, 44 | } 45 | } 46 | 47 | func (adapter *BpfAdapter) Type() string { 48 | if adapter.subtype != "" { 49 | return "bpf/" + adapter.subtype 50 | } 51 | return "bpf" 52 | } 53 | func (adapter *BpfAdapter) Name() string { return adapter.name } 54 | func (adapter *BpfAdapter) Tags() []string { return adapter.tags } 55 | func (adapter *BpfAdapter) Perm() uint { return adapter.perm } 56 | 57 | func (adapter *BpfAdapter) SetConfig(req api.ModuleBase, g Graph, id int) error { 58 | var code, fullCode string 59 | for k, v := range req.Config { 60 | switch strings.ToLower(k) { 61 | case "code": 62 | val, ok := v.(string) 63 | if !ok { 64 | return fmt.Errorf("Expected code argument to be a string") 65 | } 66 | code = val 67 | fullCode = strings.Join([]string{bpf.IomoduleH, bpf.WrapperC, val}, "\n") 68 | } 69 | } 70 | cflags := []string{"-DMODULE_UUID_SHORT=\"" + adapter.uuid[:8] + "\""} 71 | 72 | adapter.name = req.DisplayName 73 | adapter.tags = req.Tags 74 | 75 | if orig, ok := adapter.config["code"]; ok { 76 | if orig != code { 77 | return fmt.Errorf("BPF code update not supported") 78 | } 79 | } else { 80 | adapter.bpf = bpf.NewBpfModule(fullCode, cflags) 81 | if adapter.bpf == nil { 82 | return fmt.Errorf("Could not load bpf code, check server log for details") 83 | } 84 | if err := adapter.Init(); err != nil { 85 | adapter.Close() 86 | return err 87 | } 88 | adapter.config["code"] = code 89 | } 90 | switch { 91 | case adapter.subtype == "policy": 92 | for _, node := range g.Nodes() { 93 | node.(Node).Groups().Remove(id) 94 | } 95 | for _, tag := range adapter.tags { 96 | if node := g.NodeByPath(tag); node != nil { 97 | node.Groups().Insert(id) 98 | } else { 99 | Warn.Printf("Could not find %s for policy\n", tag) 100 | } 101 | } 102 | case adapter.subtype == "forward": 103 | } 104 | return nil 105 | } 106 | 107 | func (adapter *BpfAdapter) Config() map[string]interface{} { return adapter.config } 108 | func (adapter *BpfAdapter) UUID() string { return "m:" + adapter.uuid } 109 | func (adapter *BpfAdapter) FD() int { return adapter.fd } 110 | 111 | func (adapter *BpfAdapter) Init() error { 112 | fd, err := adapter.bpf.InitRxHandler() 113 | if err != nil { 114 | Warn.Printf("Unable to init rx handler: %s\n", err) 115 | return err 116 | } 117 | adapter.fd = fd 118 | return nil 119 | } 120 | 121 | func (adapter *BpfAdapter) Close() { 122 | if adapter.bpf != nil { 123 | adapter.bpf.Close() 124 | } 125 | } 126 | 127 | func (adapter *BpfAdapter) Tables() []map[string]interface{} { 128 | result := [](map[string]interface{}){} 129 | for table := range adapter.bpf.TableIter() { 130 | result = append(result, table) 131 | } 132 | return result 133 | } 134 | 135 | func (adapter *BpfAdapter) Table(name string) AdapterTable { 136 | id := adapter.bpf.TableId(name) 137 | if ^uint64(id) == 0 { 138 | return nil 139 | } 140 | return bpf.NewBpfTable(id, adapter.bpf) 141 | } 142 | -------------------------------------------------------------------------------- /policy/test/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo ip netns add host1 4 | sudo ip netns add host2 5 | 6 | sudo ip netns exec host1 sudo ip link set lo up 7 | sudo ip netns exec host2 sudo ip link set lo up 8 | 9 | sudo ip link add br0 type bridge 10 | sudo ip link set br0 up 11 | 12 | sudo ip link add veth1 type veth peer name eth0 13 | sudo ip link set eth0 netns host1 14 | 15 | sudo ip link add veth2 type veth peer name eth0 16 | sudo ip link set eth0 netns host2 17 | 18 | sudo ip netns exec host1 ip link add br0 type bridge 19 | sudo ip netns exec host2 ip link add br0 type bridge 20 | 21 | sudo ip netns exec host1 ip link add br1 type bridge 22 | sudo ip netns exec host2 ip link add br1 type bridge 23 | 24 | sudo ip netns exec host1 ip link add vxlan1 type vxlan vni 1234 group 239.1.1.1 dstport 4789 dev br1 25 | sudo ip netns exec host1 ip link set vxlan1 up 26 | 27 | sudo ip netns exec host2 ip link add vxlan1 type vxlan vni 1234 group 239.1.1.1 dstport 4789 dev br1 28 | sudo ip netns exec host2 ip link set vxlan1 up 29 | 30 | 31 | sudo ip netns exec host1 ip link set dev eth0 master br1 32 | sudo ip netns exec host2 ip link set dev eth0 master br1 33 | 34 | sudo ip netns exec host1 ip addr add 192.168.1.1/24 dev br1 35 | sudo ip netns exec host2 ip addr add 192.168.1.2/24 dev br1 36 | 37 | sudo ip netns add cont1 38 | sudo ip netns add cont2 39 | 40 | sudo ip netns exec host1 ip link add h-veth1 type veth peer name c-eth1 41 | sudo ip netns exec host1 ip link set c-eth1 netns cont1 42 | 43 | sudo ip link set dev veth1 master br0 44 | sudo ip link set dev veth2 master br0 45 | 46 | sudo ip netns exec host2 ip link add h-veth1 type veth peer name c-eth1 47 | sudo ip netns exec host2 ip link set c-eth1 netns cont2 48 | 49 | sudo ip netns exec host1 ip link set dev h-veth1 master br0 50 | sudo ip netns exec host2 ip link set dev h-veth1 master br0 51 | 52 | sudo ip netns exec host1 ip link set dev vxlan1 master br0 53 | sudo ip netns exec host2 ip link set dev vxlan1 master br0 54 | 55 | sudo ip netns exec cont1 ip addr add 10.1.1.1/24 dev c-eth1 56 | sudo ip netns exec cont2 ip addr add 10.1.1.2/24 dev c-eth1 57 | 58 | sudo ifconfig veth1 up 59 | sudo ifconfig veth2 up 60 | 61 | sudo ip netns exec host1 ifconfig br1 up 62 | sudo ip netns exec host2 ifconfig br1 up 63 | 64 | sudo ip netns exec host1 ifconfig eth0 up 65 | sudo ip netns exec host2 ifconfig eth0 up 66 | sudo ip netns exec host1 ifconfig br0 up 67 | sudo ip netns exec host1 ifconfig h-veth1 up 68 | sudo ip netns exec host2 ifconfig h-veth1 up 69 | sudo ip netns exec host2 ifconfig br0 up 70 | sudo ip netns exec cont1 ifconfig c-eth1 up 71 | sudo ip netns exec cont2 ifconfig c-eth1 up 72 | 73 | sudo ip netns exec host1 $GOPATH/bin/hoverd & 74 | sudo ip netns exec host2 $GOPATH/bin/hoverd & 75 | 76 | sleep 5 77 | 78 | sudo ip netns exec host1 $GOPATH/bin/policy -dataplane http://localhost:5000 & 79 | sudo ip netns exec host2 $GOPATH/bin/policy -dataplane http://localhost:5000 & 80 | 81 | sleep 5 82 | 83 | sudo ip netns exec host1 $GOPATH/bin/policy-ctl endpoint-group create --endpoint-group-name web --wire-id 100 84 | sudo ip netns exec host1 $GOPATH/bin/policy-ctl endpoint-group create --endpoint-group-name app --wire-id 200 85 | sudo ip netns exec host1 $GOPATH/bin/policy-ctl endpoint create --endpoint-group-name web --ipaddress 10.1.1.1 86 | sudo ip netns exec host1 $GOPATH/bin/policy-ctl endpoint create --endpoint-group-name app --ipaddress 10.1.1.2 87 | 88 | sudo ip netns exec host2 $GOPATH/bin/policy-ctl endpoint-group create --endpoint-group-name web --wire-id 100 89 | sudo ip netns exec host2 $GOPATH/bin/policy-ctl endpoint-group create --endpoint-group-name app --wire-id 200 90 | sudo ip netns exec host2 $GOPATH/bin/policy-ctl endpoint create --endpoint-group-name web --ipaddress 10.1.1.1 91 | sudo ip netns exec host2 $GOPATH/bin/policy-ctl endpoint create --endpoint-group-name app --ipaddress 10.1.1.2 92 | 93 | sudo ip netns exec host1 $GOPATH/bin/policy-ctl policy-rule create --source-endpoint-group web --dest-endpoint-group app --protocol 1 94 | sudo ip netns exec host1 $GOPATH/bin/policy-ctl policy-rule create --source-endpoint-group app --dest-endpoint-group web --protocol 1 95 | 96 | sudo ip netns exec host2 $GOPATH/bin/policy-ctl policy-rule create --source-endpoint-group web --dest-endpoint-group app --protocol 1 97 | sudo ip netns exec host2 $GOPATH/bin/policy-ctl policy-rule create --source-endpoint-group app --dest-endpoint-group web --protocol 1 98 | -------------------------------------------------------------------------------- /hover/daemon/adapter_bridge_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package daemon 16 | 17 | import ( 18 | "os/exec" 19 | "sync" 20 | "testing" 21 | 22 | "github.com/vishvananda/netlink" 23 | "github.com/vishvananda/netns" 24 | 25 | "github.com/iovisor/iomodules/hover" 26 | "github.com/iovisor/iomodules/hover/api" 27 | ) 28 | 29 | func testBridgeSimpleSetup(t *testing.T, nsPrefix string, br *netlink.Bridge) ( 30 | *netlink.Bridge, []*netlink.Veth, []netns.NsHandle, func()) { 31 | links, nets, cleanup := testNetnsPair(t, nsPrefix) 32 | 33 | if br == nil { 34 | br = &netlink.Bridge{ 35 | LinkAttrs: netlink.LinkAttrs{ 36 | Name: "br0", 37 | }, 38 | } 39 | if err := netlink.LinkAdd(br); err != nil { 40 | cleanup() 41 | t.Fatal(err) 42 | } 43 | } 44 | 45 | cleanup2 := func() { 46 | netlink.LinkDel(br) 47 | cleanup() 48 | } 49 | 50 | if err := netlink.LinkSetMaster(links[0], br); err != nil { 51 | cleanup2() 52 | t.Fatal(err) 53 | } 54 | if err := netlink.LinkSetMaster(links[1], br); err != nil { 55 | cleanup2() 56 | t.Fatal(err) 57 | } 58 | if err := netlink.LinkSetUp(br); err != nil { 59 | cleanup2() 60 | t.Fatal(err) 61 | } 62 | return br, links, nets, cleanup2 63 | } 64 | 65 | func TestBridgeDetect(t *testing.T) { 66 | srv, cleanup := testSetup(t) 67 | defer cleanup() 68 | 69 | br, _, nets, cleanup2 := testBridgeSimpleSetup(t, "ns", nil) 70 | defer cleanup2() 71 | 72 | testOne(t, testCase{ 73 | url: srv.URL + "/modules/b:" + br.Attrs().Name, 74 | method: "GET", 75 | }, nil) 76 | 77 | var wg sync.WaitGroup 78 | wg.Add(1) 79 | go hover.RunInNs(nets[0], func() error { 80 | defer wg.Done() 81 | out, err := exec.Command("ping", "-c", "1", "10.10.1.2").Output() 82 | if err != nil { 83 | t.Error(string(out), err) 84 | } 85 | return nil 86 | }) 87 | wg.Wait() 88 | } 89 | 90 | func TestBridgePolicy(t *testing.T) { 91 | srv, cleanup := testSetup(t) 92 | defer cleanup() 93 | 94 | br, _, nets, cleanup2 := testBridgeSimpleSetup(t, "ns", nil) 95 | defer cleanup2() 96 | 97 | testOne(t, testCase{ 98 | url: srv.URL + "/modules/b:" + br.Attrs().Name, 99 | method: "GET", 100 | }, nil) 101 | 102 | var t1 api.Module 103 | testOne(t, testCase{ 104 | url: srv.URL + "/modules/", 105 | body: wrapCodePolicy(t, policyC, []string{"b:" + br.Attrs().Name}), 106 | }, &t1) 107 | 108 | var wg sync.WaitGroup 109 | wg.Add(1) 110 | go hover.RunInNs(nets[0], func() error { 111 | defer wg.Done() 112 | out, err := exec.Command("ping", "-c", "1", "10.10.1.2").Output() 113 | if err != nil { 114 | t.Error(string(out), err) 115 | } 116 | return nil 117 | }) 118 | wg.Wait() 119 | 120 | var c1, c2 api.ModuleTableEntry 121 | testOne(t, testCase{ 122 | url: srv.URL + "/modules/" + t1.Id + "/tables/counters/entries/0x0", 123 | method: "GET", 124 | }, &c1) 125 | if c1.Key != "0x0" || c1.Value == "0x0" { 126 | t.Fatalf("Expected counter 1 != 0, got %s", c1.Value) 127 | } 128 | testOne(t, testCase{ 129 | url: srv.URL + "/modules/" + t1.Id + "/tables/counters/entries/0x1", 130 | method: "GET", 131 | }, &c2) 132 | if c2.Key != "0x1" || c2.Value == "0x0" { 133 | t.Fatalf("Expected counter 1 != 0, got %s", c2.Value) 134 | } 135 | } 136 | 137 | func TestBridgePolicyLinkUpdate(t *testing.T) { 138 | srv, cleanup := testSetup(t) 139 | defer cleanup() 140 | 141 | br, _, _, cleanup2 := testBridgeSimpleSetup(t, "nsA", nil) 142 | defer cleanup2() 143 | 144 | testOne(t, testCase{ 145 | url: srv.URL + "/modules/b:" + br.Attrs().Name, 146 | method: "GET", 147 | }, nil) 148 | 149 | var t1 api.Module 150 | testOne(t, testCase{ 151 | url: srv.URL + "/modules/", 152 | body: wrapCodePolicy(t, policyC, []string{"b:" + br.Attrs().Name}), 153 | }, &t1) 154 | 155 | _, _, _, cleanup3 := testBridgeSimpleSetup(t, "nsB", br) 156 | defer cleanup3() 157 | } 158 | -------------------------------------------------------------------------------- /gbp/policy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gbp 16 | 17 | import ( 18 | "fmt" 19 | ) 20 | 21 | type Parameter struct { 22 | Name string `json:"name"` 23 | Value float64 `json:"int-value"` 24 | } 25 | 26 | func (x *Parameter) String() string { 27 | return fmt.Sprintf("{n=%s v=%d}", x.Name, int(x.Value)) 28 | } 29 | 30 | type Classifier struct { 31 | Name string `json:"name"` 32 | ParameterValues []*Parameter `json:"parameter-value"` 33 | Direction string `json:"direction"` 34 | Id string `json:"classifier-definition-id"` 35 | } 36 | 37 | func (x *Classifier) String() string { 38 | return fmt.Sprintf("{%s %s %s}", x.Name, x.ParameterValues, x.Id) 39 | } 40 | 41 | type Match struct { 42 | SourcePort uint16 43 | DestPort uint16 44 | Proto uint8 45 | Direction uint8 // 0=any, 1=in, 2=out 46 | } 47 | 48 | func (x *Classifier) ToMatch() Match { 49 | var m Match 50 | for _, param := range x.ParameterValues { 51 | switch param.Name { 52 | case "proto": 53 | m.Proto = uint8(param.Value) 54 | case "destport": 55 | m.DestPort = uint16(param.Value) 56 | case "sourceport": 57 | m.SourcePort = uint16(param.Value) 58 | } 59 | } 60 | switch x.Direction { 61 | case "", "bidirectional": 62 | m.Direction = 0 63 | case "in": 64 | m.Direction = 1 65 | case "out": 66 | m.Direction = 2 67 | } 68 | return m 69 | } 70 | 71 | type Action struct { 72 | Name string `json:"name"` 73 | Order float64 `json:"order"` 74 | ParameterValues []*Parameter `json:"parameter-value"` 75 | Id string `json:"action-definition-id"` 76 | } 77 | 78 | func (x *Action) String() string { 79 | return fmt.Sprintf("{n=%s o=%d i=%s}", x.Name, int(x.Order), x.Id) 80 | } 81 | 82 | func (x *Action) IsAllow() bool { 83 | for _, param := range x.ParameterValues { 84 | if param.Name == "allow" { 85 | return param.Value == 1 86 | } 87 | } 88 | return true 89 | } 90 | 91 | type Rule struct { 92 | Name string `json:"name"` 93 | Classifiers []*Classifier `json:"classifier"` 94 | Order float64 `json:"order"` 95 | Actions []*Action `json:"action"` 96 | } 97 | 98 | func (x *Rule) String() string { 99 | return fmt.Sprintf("{n=%s c=%s o=%d a=%s}", x.Name, x.Classifiers, int(x.Order), x.Actions) 100 | } 101 | 102 | func (x *Rule) IsAllow() bool { 103 | for _, action := range x.Actions { 104 | if !action.IsAllow() { 105 | return false 106 | } 107 | } 108 | return true 109 | } 110 | 111 | type PolicyRuleGroup struct { 112 | TenantId string `json:"tenant-id"` 113 | ContractId string `json:"contract-id"` 114 | SubjectName string `json:"subject-name"` 115 | ResolvedRules []*Rule `json:"resolved-rule"` 116 | } 117 | 118 | func (x *PolicyRuleGroup) String() string { 119 | return fmt.Sprintf("{ti=%s ci=%s sn=%s rr=%s}", 120 | x.TenantId, x.ContractId, x.SubjectName, x.ResolvedRules) 121 | } 122 | 123 | type PolicyRuleGroupConstrained struct { 124 | PolicyRuleGroups []*PolicyRuleGroup `json:"policy-rule-group"` 125 | } 126 | 127 | func (x *PolicyRuleGroupConstrained) String() string { 128 | return fmt.Sprintf("{prg=%s}", x.PolicyRuleGroups) 129 | } 130 | 131 | type Policy struct { 132 | ConsumerTenantId string `json:"consumer-tenant-id"` 133 | ConsumerEpgId string `json:"consumer-epg-id"` 134 | ProviderTenantId string `json:"provider-tenant-id"` 135 | ProviderEpgId string `json:"provider-epg-id"` 136 | PolicyRuleGroups []*PolicyRuleGroupConstrained `json:"policy-rule-group-with-endpoint-constraints"` 137 | } 138 | 139 | func (x *Policy) String() string { 140 | return fmt.Sprintf("{cti=%s cei=%s pti=%s pei=%s prg=%s}", 141 | x.ConsumerTenantId, x.ConsumerEpgId, x.ProviderTenantId, 142 | x.ProviderEpgId, x.PolicyRuleGroups) 143 | } 144 | 145 | type ResolvedPolicy struct { 146 | ResolvedPolicies []*Policy `json:"resolved-policy"` 147 | } 148 | 149 | func (x *ResolvedPolicy) String() string { 150 | return fmt.Sprintf("%s", x.ResolvedPolicies) 151 | } 152 | -------------------------------------------------------------------------------- /policy/database/database_test.go: -------------------------------------------------------------------------------- 1 | package database_test 2 | 3 | import ( 4 | "github.com/iovisor/iomodules/policy/database" 5 | "github.com/iovisor/iomodules/policy/models" 6 | 7 | . "github.com/onsi/ginkgo" 8 | . "github.com/onsi/gomega" 9 | ) 10 | 11 | var _ = Describe("Database", func() { 12 | var db database.Database 13 | BeforeEach(func() { 14 | var err error 15 | db, err = database.Init("test-db") 16 | Expect(err).NotTo(HaveOccurred()) 17 | }) 18 | Describe("Endpoints", func() { 19 | var endpoint = models.EndpointEntry{ 20 | Id: "some-uuid", 21 | Ip: "some-ip", 22 | EpgId: "some-epg", 23 | } 24 | BeforeEach(func() { 25 | err := db.AddEndpoint(endpoint) 26 | Expect(err).NotTo(HaveOccurred()) 27 | }) 28 | It("Gets endpoints from the database", func() { 29 | eps, err := db.Endpoints() 30 | Expect(err).NotTo(HaveOccurred()) 31 | Expect(eps[0]).To(Equal(endpoint)) 32 | }) 33 | }) 34 | Describe("Delete Endpoint", func() { 35 | BeforeEach(func() { 36 | endpoints := []models.EndpointEntry{ 37 | {Id: "some-uuid1"}, 38 | {Id: "some-uuid2"}, 39 | {Id: "some-uuid3"}, 40 | } 41 | for _, e := range endpoints { 42 | Expect(db.AddEndpoint(e)).To(Succeed()) 43 | } 44 | }) 45 | It("Deletes an endpoint from the database", func() { 46 | Expect(db.DeleteEndpoint("some-uuid1")).To(Succeed()) 47 | Expect(db.Endpoints()).To(ConsistOf( 48 | []models.EndpointEntry{ 49 | {Id: "some-uuid2"}, 50 | {Id: "some-uuid3"}, 51 | })) 52 | }) 53 | }) 54 | Describe("Policies", func() { 55 | policy := models.Policy{ 56 | SourceEPG: "some-epg", 57 | SourcePort: "some-port", 58 | DestEPG: "some-epg", 59 | DestPort: "some-port", 60 | Protocol: "some-protocol", 61 | Action: "some-action", 62 | } 63 | BeforeEach(func() { 64 | err := db.AddPolicy(policy) 65 | Expect(err).NotTo(HaveOccurred()) 66 | }) 67 | It("Gets policies from the database", func() { 68 | policies, err := db.Policies() 69 | Expect(err).NotTo(HaveOccurred()) 70 | Expect(policies[0]).To(Equal(policy)) 71 | }) 72 | }) 73 | Describe("Delete Policy", func() { 74 | BeforeEach(func() { 75 | policies := []models.Policy{ 76 | {Id: "some-uuid1"}, 77 | {Id: "some-uuid2"}, 78 | {Id: "some-uuid3"}, 79 | } 80 | for _, p := range policies { 81 | Expect(db.AddPolicy(p)).To(Succeed()) 82 | } 83 | }) 84 | It("Deletes a policy entry from the database", func() { 85 | Expect(db.DeletePolicy("some-uuid1")).To(Succeed()) 86 | Expect(db.Policies()).To(ConsistOf( 87 | []models.Policy{ 88 | {Id: "some-uuid2"}, 89 | {Id: "some-uuid3"}, 90 | })) 91 | }) 92 | }) 93 | Describe("Get Policy", func() { 94 | BeforeEach(func() { 95 | policies := []models.Policy{ 96 | {Id: "some-uuid1"}, 97 | {Id: "some-uuid2"}, 98 | {Id: "some-uuid3"}, 99 | } 100 | for _, p := range policies { 101 | Expect(db.AddPolicy(p)).To(Succeed()) 102 | } 103 | }) 104 | It("Gets a policy entry from the database", func() { 105 | p, err := db.GetPolicy("some-uuid1") 106 | Expect(err).NotTo(HaveOccurred()) 107 | Expect(p).To(Equal(models.Policy{Id: "some-uuid1"})) 108 | }) 109 | }) 110 | Describe("Get Endpoint", func() { 111 | BeforeEach(func() { 112 | endpoints := []models.EndpointEntry{ 113 | {Id: "some-uuid1"}, 114 | {Id: "some-uuid2"}, 115 | {Id: "some-uuid3"}, 116 | } 117 | for _, p := range endpoints { 118 | Expect(db.AddEndpoint(p)).To(Succeed()) 119 | } 120 | }) 121 | It("Gets an endpoint entry from the database", func() { 122 | e, err := db.GetEndpoint("some-uuid1") 123 | Expect(err).NotTo(HaveOccurred()) 124 | Expect(e).To(Equal(models.EndpointEntry{Id: "some-uuid1"})) 125 | }) 126 | }) 127 | Describe("EndpointGroups", func() { 128 | var epgs []models.EndpointGroup 129 | BeforeEach(func() { 130 | epgs = []models.EndpointGroup{ 131 | { 132 | Id: "some-uuid", 133 | Epg: "some-epg", 134 | WireId: "some-wire-id", 135 | }, 136 | } 137 | for _, e := range epgs { 138 | Expect(db.AddEndpointGroup(e)).To(Succeed()) 139 | } 140 | It("Gets endpoints from the databse", func() { 141 | gs, err := db.EndpointGroups() 142 | Expect(err).NotTo(HaveOccurred()) 143 | Expect(gs).To(Equal(epgs)) 144 | }) 145 | }) 146 | }) 147 | Describe("Get Endpoint Groups", func() { 148 | BeforeEach(func() { 149 | endpoints := []models.EndpointGroup{ 150 | {Id: "some-uuid1"}, 151 | {Id: "some-uuid2"}, 152 | {Id: "some-uuid3"}, 153 | } 154 | for _, e := range endpoints { 155 | Expect(db.AddEndpointGroup(e)).To(Succeed()) 156 | } 157 | }) 158 | It("Gets an endpoint entry from the database", func() { 159 | epgs, err := db.GetEndpointGroup("some-uuid1") 160 | Expect(err).NotTo(HaveOccurred()) 161 | Expect(epgs).To(Equal(models.EndpointGroup{Id: "some-uuid1"})) 162 | }) 163 | }) 164 | }) 165 | -------------------------------------------------------------------------------- /policy/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | 10 | "github.com/iovisor/iomodules/policy/log" 11 | "github.com/iovisor/iomodules/policy/models" 12 | ) 13 | 14 | //go: generginkgo bootstrap # set up a new ginkgo suite 15 | type PolicyClient interface { 16 | AddEndpoint(*models.EndpointEntry) error 17 | DeleteEndpoint(endpointId string) error 18 | GetEndpoint(endpointId string) (models.EndpointEntry, error) 19 | Endpoints() ([]models.EndpointEntry, error) 20 | AddPolicy(*models.Policy) error 21 | DeletePolicy(policyId string) error 22 | GetPolicy(policyId string) (models.Policy, error) 23 | Policies() ([]models.Policy, error) 24 | AddEndpointGroup(*models.EndpointGroup) error 25 | DeleteEndpointGroup(epgId string) error 26 | GetEndpointGroup(epgId string) (models.EndpointGroup, error) 27 | EndpointGroups() ([]models.EndpointGroup, error) 28 | } 29 | 30 | type policyclient struct { 31 | client *http.Client 32 | baseUrl string 33 | } 34 | 35 | func NewClient(baseUrl string) PolicyClient { 36 | httpclient := &http.Client{} 37 | return &policyclient{ 38 | client: httpclient, 39 | baseUrl: baseUrl, 40 | } 41 | } 42 | 43 | func (p *policyclient) GetObject(url string, responseObj interface{}) (err error) { 44 | resp, err := p.client.Get(p.baseUrl + url) 45 | if err != nil { 46 | return err 47 | } 48 | defer resp.Body.Close() 49 | if resp.StatusCode != http.StatusOK { 50 | var body []byte 51 | if body, err = ioutil.ReadAll(resp.Body); err != nil { 52 | log.Error.Print(string(body)) 53 | } 54 | return fmt.Errorf("module server returned %s", resp.Status) 55 | } 56 | if responseObj != nil { 57 | err = json.NewDecoder(resp.Body).Decode(responseObj) 58 | } 59 | return nil 60 | } 61 | 62 | func (p *policyclient) PostObject(url string, requestObj interface{}, responseObj interface{}) (err error) { 63 | b, err := json.Marshal(requestObj) 64 | if err != nil { 65 | return 66 | } 67 | resp, err := p.client.Post(p.baseUrl+url, "application/json", bytes.NewReader(b)) 68 | if err != nil { 69 | return 70 | } 71 | defer resp.Body.Close() 72 | if resp.StatusCode != http.StatusOK { 73 | var body []byte 74 | if body, err = ioutil.ReadAll(resp.Body); err != nil { 75 | log.Error.Print(string(body)) 76 | } 77 | return fmt.Errorf("module server returned %s", resp.Status) 78 | } 79 | if responseObj != nil { 80 | err = json.NewDecoder(resp.Body).Decode(responseObj) 81 | if err != nil { 82 | return fmt.Errorf("module server returned %s", resp.Status) 83 | } 84 | } 85 | return nil 86 | } 87 | 88 | func (p *policyclient) deleteObject(url string) error { 89 | req, err := http.NewRequest("DELETE", p.baseUrl+url, nil) 90 | if err != nil { 91 | return fmt.Errorf("module server returned: %s", err) 92 | } 93 | resp, err := p.client.Do(req) 94 | if err != nil { 95 | return fmt.Errorf("module server returned: %s", err) 96 | } 97 | defer resp.Body.Close() 98 | if resp.StatusCode != http.StatusOK { 99 | return fmt.Errorf("module server returned: %s", resp.Status) 100 | } 101 | return nil 102 | } 103 | 104 | func (p *policyclient) AddEndpoint(endpoint *models.EndpointEntry) error { 105 | err := p.PostObject("/endpoints/", endpoint, nil) 106 | if err != nil { 107 | return fmt.Errorf("Add Endpoint to server %s", err) 108 | } 109 | return nil 110 | } 111 | 112 | func (p *policyclient) DeleteEndpoint(endpointId string) error { 113 | 114 | err := p.deleteObject("/endpoints/" + endpointId) 115 | if err != nil { 116 | return fmt.Errorf("Delete endpoint from server %s", err) 117 | } 118 | return nil 119 | } 120 | 121 | func (p *policyclient) GetEndpoint(endpointId string) (models.EndpointEntry, error) { 122 | var endpoint models.EndpointEntry 123 | 124 | err := p.GetObject("/endpoints/"+endpointId, &endpoint) 125 | if err != nil { 126 | return endpoint, fmt.Errorf("Get Endpoint from server %s", err) 127 | } 128 | return endpoint, nil 129 | } 130 | 131 | func (p *policyclient) Endpoints() ([]models.EndpointEntry, error) { 132 | var epList []models.EndpointEntry 133 | err := p.GetObject("/endpoints/", &epList) 134 | if err != nil { 135 | return epList, fmt.Errorf("Get Endpoint from server %s", err) 136 | } 137 | return epList, nil 138 | 139 | } 140 | 141 | func (p *policyclient) AddPolicy(policy *models.Policy) error { 142 | err := p.PostObject("/policies/", policy, nil) 143 | if err != nil { 144 | return fmt.Errorf("Add policy to server %s", err) 145 | } 146 | return nil 147 | } 148 | 149 | func (p *policyclient) DeletePolicy(policyId string) error { 150 | err := p.deleteObject("/policies/" + policyId) 151 | if err != nil { 152 | return fmt.Errorf("Delete endpoint from server %s", err) 153 | } 154 | return nil 155 | } 156 | 157 | func (p *policyclient) GetPolicy(policyId string) (models.Policy, error) { 158 | var policy models.Policy 159 | 160 | err := p.GetObject("/policies/"+policyId, &policy) 161 | if err != nil { 162 | return policy, fmt.Errorf("Get Endpoint from server %s", err) 163 | } 164 | return policy, nil 165 | } 166 | 167 | func (p *policyclient) Policies() ([]models.Policy, error) { 168 | var policylist []models.Policy 169 | err := p.GetObject("/policies/", &policylist) 170 | if err != nil { 171 | return policylist, fmt.Errorf("Get policies from server %s", err) 172 | } 173 | return policylist, nil 174 | } 175 | 176 | func (p *policyclient) AddEndpointGroup(epg *models.EndpointGroup) error { 177 | err := p.PostObject("/epg/", epg, nil) 178 | if err != nil { 179 | return fmt.Errorf("Add epg to server %s", err) 180 | } 181 | return nil 182 | 183 | } 184 | 185 | func (p *policyclient) DeleteEndpointGroup(epgId string) error { 186 | err := p.deleteObject("/epg/" + epgId) 187 | if err != nil { 188 | return fmt.Errorf("Delete endpoint from server %s", err) 189 | } 190 | return nil 191 | } 192 | 193 | func (p *policyclient) GetEndpointGroup(epgId string) (models.EndpointGroup, error) { 194 | var epg models.EndpointGroup 195 | err := p.GetObject("/epg/"+epgId, &epg) 196 | if err != nil { 197 | return epg, fmt.Errorf("Get Endpoint from server %s", err) 198 | } 199 | return epg, nil 200 | } 201 | 202 | func (p *policyclient) EndpointGroups() ([]models.EndpointGroup, error) { 203 | var epgList []models.EndpointGroup 204 | err := p.GetObject("/epg/", &epgList) 205 | if err != nil { 206 | return epgList, fmt.Errorf("Get Endpoint from server %s", err) 207 | } 208 | return epgList, nil 209 | } 210 | -------------------------------------------------------------------------------- /policy/server/dataplane_test.go: -------------------------------------------------------------------------------- 1 | package server_test 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "net/http" 9 | 10 | "github.com/iovisor/iomodules/policy/models" 11 | . "github.com/iovisor/iomodules/policy/server" 12 | . "github.com/onsi/ginkgo" 13 | . "github.com/onsi/gomega" 14 | "github.com/onsi/gomega/ghttp" 15 | ) 16 | 17 | var _ = Describe("Dataplane", func() { 18 | var ( 19 | dataplane *Dataplane 20 | fakeserver *ghttp.Server 21 | module models.ModuleEntry 22 | ) 23 | BeforeEach(func() { 24 | fakeserver = ghttp.NewServer() 25 | dataplane = NewDataplane() 26 | Expect(dataplane).NotTo(BeNil()) 27 | module = models.ModuleEntry{ 28 | Id: "some-module-id", 29 | } 30 | }) 31 | Describe("Init Dataplane", func() { 32 | var statusCode int 33 | It("Instantiates a dataplane object", func() { 34 | statusCode := http.StatusOK 35 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 36 | ghttp.VerifyRequest("POST", "/modules/"), 37 | ghttp.RespondWithJSONEncodedPtr(&statusCode, &module), 38 | )) 39 | err := dataplane.Init(fakeserver.URL()) 40 | Expect(err).NotTo(HaveOccurred()) 41 | Expect(dataplane.Id()).To(Equal("some-module-id")) 42 | }) 43 | Context("When the post to server fails", func() { 44 | BeforeEach(func() { 45 | statusCode = http.StatusInternalServerError 46 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 47 | ghttp.VerifyRequest("POST", "/modules/"), 48 | ghttp.RespondWithJSONEncodedPtr(&statusCode, &models.ModuleEntry{}), 49 | )) 50 | }) 51 | It("Returns an error", func() { 52 | err := dataplane.Init(fakeserver.URL()) 53 | Expect(err).To(HaveOccurred()) 54 | ret := errors.New("module server returned: " + fmt.Sprintf("%d", statusCode) + " " + http.StatusText(statusCode)) 55 | Expect(err).To(Equal(ret)) 56 | }) 57 | }) 58 | }) 59 | Describe("Policy APIs", func() { 60 | var entry models.TableEntry 61 | var statusCode int 62 | BeforeEach(func() { 63 | statusCode = http.StatusOK 64 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 65 | ghttp.VerifyRequest("POST", "/modules/"), 66 | ghttp.RespondWithJSONEncodedPtr(&statusCode, &module), 67 | )) 68 | err := dataplane.Init(fakeserver.URL()) 69 | Expect(err).NotTo(HaveOccurred()) 70 | Expect(dataplane.Id()).To(Equal("some-module-id")) 71 | }) 72 | It("Adds a policy to the server", func() { 73 | entry = models.TableEntry{ 74 | Key: fmt.Sprintf("{ %s %s %s %s %s [ 0 0 0 ]}", "200", "200", "0", "0", "20"), 75 | Value: "0", 76 | } 77 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 78 | ghttp.VerifyRequest("POST", "/modules/"+dataplane.Id()+"/tables/rules/entries/"), 79 | ghttp.VerifyJSONRepresenting(&entry), 80 | ghttp.RespondWithJSONEncodedPtr(&statusCode, &entry), 81 | )) 82 | err := dataplane.AddPolicy("200", "", "200", "", "20", "allow") 83 | Expect(err).NotTo(HaveOccurred()) 84 | }) 85 | Context("when adding a policy to the server fails", func() { 86 | BeforeEach(func() { 87 | statusCode = http.StatusInternalServerError 88 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 89 | ghttp.VerifyRequest("POST", "/modules/"+dataplane.Id()+"/tables/rules/entries/"), 90 | ghttp.RespondWithJSONEncodedPtr(&statusCode, models.TableEntry{}), 91 | )) 92 | }) 93 | It("Returns an error", func() { 94 | err := dataplane.AddPolicy("200", "", "200", "", "20", "allow") 95 | Expect(err).To(HaveOccurred()) 96 | }) 97 | }) 98 | It("Deletes a policy from the server", func() { 99 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 100 | ghttp.VerifyRequest("DELETE", "/modules/"+dataplane.Id()+"/tables/rules/entries/"+entry.Key), 101 | ghttp.RespondWith(http.StatusOK, ""), 102 | )) 103 | err := dataplane.DeletePolicy("200", "0", "200", "0", "20") 104 | Expect(err).NotTo(HaveOccurred()) 105 | }) 106 | Context("when delete a policy from the server fails", func() { 107 | BeforeEach(func() { 108 | statusCode = http.StatusInternalServerError 109 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 110 | ghttp.VerifyRequest("DELETE", "/modules/"+dataplane.Id()+"/tables/rules/entries/"+entry.Key), 111 | ghttp.RespondWith(statusCode, ""), 112 | )) 113 | }) 114 | It("Returns an error", func() { 115 | err := dataplane.DeletePolicy("200", "0", "200", "0", "20") 116 | Expect(err).To(HaveOccurred()) 117 | }) 118 | }) 119 | }) 120 | Describe("Endpoint APIs", func() { 121 | var ( 122 | entry models.TableEntry 123 | ipStr string 124 | wireid string 125 | ipKey string 126 | epg string 127 | ip net.IP 128 | statusCode int 129 | ) 130 | BeforeEach(func() { 131 | ipStr = "10.1.1.1" 132 | epg = "some-epg" 133 | ip = net.ParseIP(ipStr) 134 | ipKey = fmt.Sprintf("%d", binary.BigEndian.Uint32(ip.To4())) 135 | wireid = "300" 136 | entry = models.TableEntry{ 137 | Key: ipKey, 138 | Value: wireid, 139 | } 140 | statusCode = http.StatusOK 141 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 142 | ghttp.VerifyRequest("POST", "/modules/"), 143 | ghttp.RespondWithJSONEncodedPtr(&statusCode, &module), 144 | )) 145 | err := dataplane.Init(fakeserver.URL()) 146 | Expect(err).NotTo(HaveOccurred()) 147 | Expect(dataplane.Id()).To(Equal("some-module-id")) 148 | }) 149 | It("Adds an endpoint to the server", func() { 150 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 151 | ghttp.VerifyRequest("POST", "/modules/"+dataplane.Id()+"/tables/endpoints/entries/"), 152 | ghttp.VerifyJSONRepresenting(&entry), 153 | ghttp.RespondWithJSONEncodedPtr(&statusCode, &entry), 154 | )) 155 | err := dataplane.AddEndpoint(ipStr, epg, wireid) 156 | Expect(err).NotTo(HaveOccurred()) 157 | }) 158 | Context("when add an endpoint from the server fails", func() { 159 | BeforeEach(func() { 160 | statusCode = http.StatusInternalServerError 161 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 162 | ghttp.VerifyRequest("POST", "/modules/"+dataplane.Id()+"/tables/endpoints/entries/"), 163 | ghttp.RespondWith(statusCode, ""), 164 | )) 165 | }) 166 | It("Returns an error", func() { 167 | err := dataplane.AddEndpoint(ipStr, epg, wireid) 168 | Expect(err).To(HaveOccurred()) 169 | }) 170 | }) 171 | It("Deletes an endpoint from the server", func() { 172 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 173 | ghttp.VerifyRequest("DELETE", "/modules/"+dataplane.Id()+"/tables/endpoints/entries/"+entry.Key), 174 | ghttp.RespondWith(http.StatusOK, ""), 175 | )) 176 | err := dataplane.DeleteEndpoint(ipStr) 177 | Expect(err).NotTo(HaveOccurred()) 178 | }) 179 | Context("when deleting an endpoint from the server fails", func() { 180 | BeforeEach(func() { 181 | statusCode = http.StatusInternalServerError 182 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 183 | ghttp.VerifyRequest("DELETE", "/modules/"+dataplane.Id()+"/tables/endpoints/entries/"+entry.Key), 184 | ghttp.RespondWith(statusCode, ""), 185 | )) 186 | }) 187 | It("Returns an error", func() { 188 | err := dataplane.DeleteEndpoint(ipStr) 189 | Expect(err).To(HaveOccurred()) 190 | }) 191 | }) 192 | }) 193 | }) 194 | -------------------------------------------------------------------------------- /policy/database/database.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/iovisor/iomodules/policy/log" 7 | "github.com/iovisor/iomodules/policy/models" 8 | "github.com/jmoiron/sqlx" 9 | "github.com/lib/pq" 10 | _ "github.com/mattn/go-sqlite3" 11 | ) 12 | 13 | //go:generate counterfeiter -o ../fakes/database.go --fake-name Database . Database 14 | type Database interface { 15 | Endpoints() ([]models.EndpointEntry, error) 16 | Policies() ([]models.Policy, error) 17 | EndpointGroups() ([]models.EndpointGroup, error) 18 | AddEndpoint(models.EndpointEntry) error 19 | AddPolicy(models.Policy) error 20 | DeleteEndpoint(EpId string) error 21 | DeletePolicy(PolicyId string) error 22 | GetPolicy(PolicyId string) (models.Policy, error) 23 | GetEndpoint(EndpointId string) (models.EndpointEntry, error) 24 | AddEndpointGroup(models.EndpointGroup) error 25 | DeleteEndpointGroup(GroupId string) error 26 | GetEndpointGroup(GroupId string) (models.EndpointGroup, error) 27 | } 28 | 29 | type database struct { 30 | db *sqlx.DB 31 | } 32 | 33 | func Init(sqlUrl string) (Database, error) { 34 | 35 | sqlxDb, err := sqlx.Connect("sqlite3", ":memory:") 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | schema := `CREATE TABLE Endpoints ( 41 | id text PRIMARY KEY, 42 | ip text, 43 | epgid text SECONDARY KEY)` 44 | 45 | _, err = sqlxDb.Exec(schema) 46 | if err != nil { 47 | return nil, fmt.Errorf("create endpoint db : %s", err) 48 | } 49 | 50 | schema = `CREATE TABLE Policies ( 51 | id text PRIMARY KEY, 52 | sourceepg text, 53 | sourceport text, 54 | destepg text, 55 | destport text, 56 | protocol text, 57 | action text)` 58 | 59 | _, err = sqlxDb.Exec(schema) 60 | if err != nil { 61 | return nil, fmt.Errorf("create policies db : %s", err) 62 | } 63 | 64 | schema = `CREATE TABLE Epgs ( 65 | id text PRIMARY KEY, 66 | epg text, 67 | wireid text)` 68 | 69 | _, err = sqlxDb.Exec(schema) 70 | if err != nil { 71 | return nil, fmt.Errorf("create endpoints db : %s", err) 72 | } 73 | return &database{ 74 | db: sqlxDb, 75 | }, nil 76 | } 77 | 78 | func (dbPtr *database) AddEndpointGroup(epg models.EndpointGroup) error { 79 | _, err := dbPtr.db.NamedExec(` 80 | INSERT INTO Epgs ( 81 | id, epg, wireid 82 | ) VALUES ( 83 | :id, :epg, :wireid 84 | )`, &epg) 85 | 86 | if err != nil { 87 | pqErr, ok := err.(*pq.Error) 88 | if !ok { 89 | return fmt.Errorf("insert: %s", err) 90 | } 91 | if pqErr.Code.Name() == "unique_violation" { 92 | return fmt.Errorf("add epg: record exists") 93 | } 94 | return fmt.Errorf("add epg to db: %s", pqErr.Code.Name()) 95 | } 96 | return nil 97 | } 98 | 99 | func (dbPtr *database) DeleteEndpointGroup(id string) error { 100 | 101 | log.Info.Println("ID IS: ", id) 102 | result, err := dbPtr.db.Exec("DELETE FROM Epgs where id=$1", id) 103 | if err != nil { 104 | return fmt.Errorf("database delete endoint group: %s", err) 105 | } 106 | rowsAffected, err := result.RowsAffected() 107 | if err != nil { 108 | return fmt.Errorf("database delete endpoint group : %s", err) 109 | } 110 | if rowsAffected == 0 { 111 | return fmt.Errorf("database delete endpoint group : record not found") 112 | } 113 | return nil 114 | } 115 | 116 | func (dbPtr *database) EndpointGroups() ([]models.EndpointGroup, error) { 117 | epgs := []models.EndpointGroup{} 118 | err := dbPtr.db.Select(&epgs, "SELECT * FROM Epgs") 119 | if err != nil { 120 | return nil, fmt.Errorf("database get epgs: %s", err) 121 | } 122 | return epgs, nil 123 | } 124 | 125 | func (dbPtr *database) Endpoints() ([]models.EndpointEntry, error) { 126 | endpoints := []models.EndpointEntry{} 127 | err := dbPtr.db.Select(&endpoints, "SELECT * FROM endpoints") 128 | if err != nil { 129 | return nil, fmt.Errorf("database get endpoints: %s", err) 130 | } 131 | return endpoints, nil 132 | } 133 | 134 | func (dbPtr *database) AddEndpoint(endpoint models.EndpointEntry) error { 135 | _, err := dbPtr.db.NamedExec(` 136 | INSERT INTO Endpoints ( 137 | id, ip, epgid 138 | ) VALUES ( 139 | :id, :ip, :epgid 140 | )`, &endpoint) 141 | 142 | if err != nil { 143 | pqErr, ok := err.(*pq.Error) 144 | if !ok { 145 | return fmt.Errorf("insert: %s", err) 146 | } 147 | if pqErr.Code.Name() == "unique_violation" { 148 | return fmt.Errorf("add endpoint: record exists") 149 | } 150 | return fmt.Errorf("add endpoint to db: %s", pqErr.Code.Name()) 151 | } 152 | return nil 153 | } 154 | func (dbPtr *database) GetEndpointGroup(id string) (models.EndpointGroup, error) { 155 | epg := models.EndpointGroup{} 156 | err := dbPtr.db.Get(&epg, "SELECT * from Epgs WHERE id=$1", id) 157 | if err != nil { 158 | return epg, fmt.Errorf("database get endpoints: %s", err) 159 | } 160 | return epg, nil 161 | } 162 | 163 | func (dbPtr *database) GetEndpoint(id string) (models.EndpointEntry, error) { 164 | endpoints := models.EndpointEntry{} 165 | 166 | err := dbPtr.db.Get(&endpoints, "SELECT * from Endpoints WHERE id=$1", id) 167 | 168 | if err != nil { 169 | return endpoints, fmt.Errorf("database get endpoints: %s", err) 170 | } 171 | return endpoints, nil 172 | } 173 | 174 | func (dbPtr *database) DeleteEndpoint(id string) error { 175 | result, err := dbPtr.db.Exec("DELETE FROM Endpoints WHERE id=$1", id) 176 | if err != nil { 177 | return fmt.Errorf("database delete endpoint: %s", err) 178 | } 179 | rowsAffected, err := result.RowsAffected() 180 | if err != nil { 181 | return fmt.Errorf("database delete endpoint : %s", err) 182 | } 183 | if rowsAffected == 0 { 184 | return fmt.Errorf("database delete endpoint : record not found") 185 | } 186 | return nil 187 | } 188 | 189 | func (dbPtr *database) AddPolicy(policy models.Policy) error { 190 | _, err := dbPtr.db.NamedExec(` 191 | INSERT INTO Policies ( 192 | id, sourceepg, sourceport, destepg, destport, protocol, action 193 | ) VALUES ( 194 | :id, :sourceepg, :sourceport, :destepg, :destport, :protocol, :action 195 | )`, &policy) 196 | 197 | if err != nil { 198 | pqErr, ok := err.(*pq.Error) 199 | if !ok { 200 | return fmt.Errorf("insert: %s", err) 201 | } 202 | if pqErr.Code.Name() == "unique_violation" { 203 | return fmt.Errorf("add policies: record exists") 204 | } 205 | return fmt.Errorf("add policies: %s", pqErr.Code.Name()) 206 | } 207 | return nil 208 | } 209 | 210 | func (dbPtr *database) Policies() ([]models.Policy, error) { 211 | policies := []models.Policy{} 212 | err := dbPtr.db.Select(&policies, "SELECT * FROM policies") 213 | if err != nil { 214 | return nil, fmt.Errorf("database get policies: %s", err) 215 | } 216 | return policies, nil 217 | } 218 | 219 | func (dbPtr *database) DeletePolicy(id string) error { 220 | result, err := dbPtr.db.Exec("DELETE FROM Policies WHERE id=$1", id) 221 | if err != nil { 222 | fmt.Errorf("database delete endpoint: %s", err) 223 | } 224 | rowsAffected, err := result.RowsAffected() 225 | if err != nil { 226 | return fmt.Errorf("database delete policies : %s", err) 227 | } 228 | if rowsAffected == 0 { 229 | return fmt.Errorf("database delete policies : record not found") 230 | } 231 | return nil 232 | } 233 | 234 | func (dbPtr *database) GetPolicy(id string) (models.Policy, error) { 235 | policy := models.Policy{} 236 | err := dbPtr.db.Get(&policy, "SELECT * from Policies WHERE id=$1", id) 237 | if err != nil { 238 | return policy, fmt.Errorf("database get policy: %s", err) 239 | } 240 | return policy, nil 241 | } 242 | -------------------------------------------------------------------------------- /policy/client/client_test.go: -------------------------------------------------------------------------------- 1 | package client_test 2 | 3 | import ( 4 | "net/http" 5 | 6 | . "github.com/iovisor/iomodules/policy/client" 7 | "github.com/iovisor/iomodules/policy/models" 8 | 9 | . "github.com/onsi/ginkgo" 10 | . "github.com/onsi/gomega" 11 | "github.com/onsi/gomega/ghttp" 12 | ) 13 | 14 | var _ = Describe("Client", func() { 15 | var ( 16 | fakeserver *ghttp.Server 17 | p PolicyClient 18 | ) 19 | BeforeEach(func() { 20 | fakeserver = ghttp.NewServer() 21 | p = NewClient(fakeserver.URL()) 22 | }) 23 | Describe("Endpoint APIs", func() { 24 | var endpoint models.EndpointEntry 25 | BeforeEach(func() { 26 | endpoint = models.EndpointEntry{ 27 | Ip: "some-ip", 28 | EpgId: "some-id", 29 | } 30 | }) 31 | Describe("Add Endpoint", func() { 32 | It("Adds an endpoint to the server", func() { 33 | statusCode := http.StatusOK 34 | Expect(p).NotTo(BeNil()) 35 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 36 | ghttp.VerifyRequest("POST", "/endpoints/"), 37 | ghttp.RespondWithJSONEncodedPtr(&statusCode, &endpoint), 38 | )) 39 | err := p.AddEndpoint(&endpoint) 40 | Expect(err).NotTo(HaveOccurred()) 41 | }) 42 | }) 43 | Describe("Delete Endpoint", func() { 44 | It("Deletes an endpint from the server", func() { 45 | someId := "some-id" 46 | statusCode := http.StatusOK 47 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 48 | ghttp.VerifyRequest("DELETE", "/endpoints/"+someId), 49 | ghttp.RespondWith(statusCode, ""), 50 | )) 51 | err := p.DeleteEndpoint(someId) 52 | Expect(err).NotTo(HaveOccurred()) 53 | }) 54 | }) 55 | Describe("Get Endpoint", func() { 56 | It("Gets an endpoint from the server", func() { 57 | someId := "some-id" 58 | statusCode := http.StatusOK 59 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 60 | ghttp.VerifyRequest("GET", "/endpoints/"+someId), 61 | ghttp.RespondWithJSONEncodedPtr(&statusCode, &endpoint), 62 | )) 63 | ep, err := p.GetEndpoint(someId) 64 | Expect(err).NotTo(HaveOccurred()) 65 | Expect(ep).To(Equal(endpoint)) 66 | }) 67 | }) 68 | Describe("Get Endpoint List", func() { 69 | var eplist []models.EndpointEntry 70 | BeforeEach(func() { 71 | eplist = []models.EndpointEntry{ 72 | { 73 | Ip: "some-ip", 74 | EpgId: "some-id", 75 | }, 76 | } 77 | }) 78 | It("Gets endpoint list from the server", func() { 79 | statusCode := http.StatusOK 80 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 81 | ghttp.VerifyRequest("GET", "/endpoints/"), 82 | ghttp.RespondWithJSONEncodedPtr(&statusCode, &eplist), 83 | )) 84 | epl, err := p.Endpoints() 85 | Expect(err).NotTo(HaveOccurred()) 86 | Expect(epl).To(Equal(eplist)) 87 | }) 88 | }) 89 | }) 90 | Describe("Policy APIs", func() { 91 | var policy models.Policy 92 | BeforeEach(func() { 93 | policy = models.Policy{ 94 | SourceEPG: "some-epg", 95 | SourcePort: "some-id", 96 | DestEPG: "some-epg", 97 | DestPort: "some-port", 98 | Protocol: "some-protocol", 99 | Action: "some-action", 100 | } 101 | }) 102 | Describe("Add Policy", func() { 103 | It("Adds a policy to the server", func() { 104 | statusCode := http.StatusOK 105 | Expect(p).NotTo(BeNil()) 106 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 107 | ghttp.VerifyRequest("POST", "/policies/"), 108 | ghttp.RespondWithJSONEncodedPtr(&statusCode, &policy), 109 | )) 110 | err := p.AddPolicy(&policy) 111 | Expect(err).NotTo(HaveOccurred()) 112 | }) 113 | }) 114 | Describe("Delete Policy", func() { 115 | It("Deletes a policy from the server", func() { 116 | someId := "some-id" 117 | statusCode := http.StatusOK 118 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 119 | ghttp.VerifyRequest("DELETE", "/policies/"+someId), 120 | ghttp.RespondWith(statusCode, ""), 121 | )) 122 | err := p.DeletePolicy(someId) 123 | Expect(err).NotTo(HaveOccurred()) 124 | }) 125 | }) 126 | Describe("Get Policy", func() { 127 | It("Get a policy from the server", func() { 128 | someId := "some-id" 129 | statusCode := http.StatusOK 130 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 131 | ghttp.VerifyRequest("GET", "/policies/"+someId), 132 | ghttp.RespondWithJSONEncodedPtr(&statusCode, &policy), 133 | )) 134 | p, err := p.GetPolicy(someId) 135 | Expect(err).NotTo(HaveOccurred()) 136 | Expect(p).To(Equal(policy)) 137 | }) 138 | }) 139 | Describe("Get Policy Group List", func() { 140 | var policylist []models.Policy 141 | BeforeEach(func() { 142 | policylist = []models.Policy{ 143 | { 144 | SourceEPG: "some-epg", 145 | SourcePort: "some-id", 146 | DestEPG: "some-epg", 147 | DestPort: "some-port", 148 | Protocol: "some-protocol", 149 | Action: "some-action", 150 | }, 151 | } 152 | }) 153 | It("Gets policy list from the server", func() { 154 | statusCode := http.StatusOK 155 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 156 | ghttp.VerifyRequest("GET", "/policies/"), 157 | ghttp.RespondWithJSONEncodedPtr(&statusCode, &policylist), 158 | )) 159 | policyl, err := p.Policies() 160 | Expect(err).NotTo(HaveOccurred()) 161 | Expect(policyl).To(Equal(policylist)) 162 | }) 163 | }) 164 | 165 | }) 166 | Describe("Endpoint Group APIs", func() { 167 | var epg models.EndpointGroup 168 | BeforeEach(func() { 169 | epg = models.EndpointGroup{ 170 | Epg: "some-epg", 171 | WireId: "some-wire-id", 172 | } 173 | }) 174 | Describe("Add Endpoint Group", func() { 175 | It("Adds endpoint group to the server", func() { 176 | statusCode := http.StatusOK 177 | Expect(p).NotTo(BeNil()) 178 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 179 | ghttp.VerifyRequest("POST", "/epg/"), 180 | ghttp.RespondWithJSONEncodedPtr(&statusCode, &epg), 181 | )) 182 | err := p.AddEndpointGroup(&epg) 183 | Expect(err).NotTo(HaveOccurred()) 184 | }) 185 | }) 186 | Describe("Delete Endpoint Group", func() { 187 | It("Deletes an endpoint group from the server", func() { 188 | someId := "some-id" 189 | statusCode := http.StatusOK 190 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 191 | ghttp.VerifyRequest("DELETE", "/epg/"+someId), 192 | ghttp.RespondWith(statusCode, ""), 193 | )) 194 | err := p.DeleteEndpointGroup(someId) 195 | Expect(err).NotTo(HaveOccurred()) 196 | }) 197 | }) 198 | Describe("Get Endpoint Group", func() { 199 | It("Get a epg from the server", func() { 200 | someId := "some-id" 201 | statusCode := http.StatusOK 202 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 203 | ghttp.VerifyRequest("GET", "/epg/"+someId), 204 | ghttp.RespondWithJSONEncodedPtr(&statusCode, &epg), 205 | )) 206 | epgrp, err := p.GetEndpointGroup(someId) 207 | Expect(err).NotTo(HaveOccurred()) 208 | Expect(epgrp).To(Equal(epg)) 209 | }) 210 | }) 211 | Describe("Get Endpoint Group List", func() { 212 | var epglist []models.EndpointGroup 213 | BeforeEach(func() { 214 | epglist = []models.EndpointGroup{ 215 | { 216 | Epg: "some-epg", 217 | WireId: "some-wire-id", 218 | }, 219 | } 220 | }) 221 | It("Gets an epg list from the server", func() { 222 | statusCode := http.StatusOK 223 | fakeserver.AppendHandlers(ghttp.CombineHandlers( 224 | ghttp.VerifyRequest("GET", "/epg/"), 225 | ghttp.RespondWithJSONEncodedPtr(&statusCode, &epglist), 226 | )) 227 | epgl, err := p.EndpointGroups() 228 | Expect(err).NotTo(HaveOccurred()) 229 | Expect(epgl).To(Equal(epglist)) 230 | }) 231 | }) 232 | }) 233 | }) 234 | -------------------------------------------------------------------------------- /hover/renderer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package hover 16 | 17 | import ( 18 | "fmt" 19 | 20 | "golang.org/x/tools/container/intsets" 21 | 22 | "github.com/iovisor/iomodules/hover/canvas" 23 | "github.com/iovisor/iomodules/hover/util" 24 | ) 25 | 26 | var ( 27 | Debug = util.Debug 28 | Info = util.Info 29 | Warn = util.Warn 30 | Error = util.Error 31 | ) 32 | 33 | type Renderer struct { 34 | } 35 | 36 | func NewRenderer() *Renderer { 37 | return &Renderer{} 38 | } 39 | 40 | func filterInterfaceNode(e canvas.Edge) bool { 41 | _, ok := e.To().(InterfaceNode) 42 | return !ok 43 | } 44 | 45 | func computeChainFrom(from, to canvas.Node) (chain []canvas.NodeIfc) { 46 | // For each link, there is a chain of modules to be invoked: the 47 | // ingress policy modules, the egress policy modules, and the final 48 | // forwarding nexthop. 49 | // 50 | // To compute the chain in each direction, the following algorithm is 51 | // followed: 52 | // Let T and F represent the set of groups for the 'to' and 'from' 53 | // nodes, respectively. 54 | // The leaving set L is the set difference between F and T. 55 | // L := F - T 56 | // The entering set E is the set difference between T and F 57 | // E := T - F 58 | // 59 | // For the directed edge from:to, the chain is built as follows: 60 | // For each module e in E, invoke the ingress policy (e.ifc[1]) 61 | // For each module l in L, invoke the egress policy (l.ifc[2]) 62 | // 63 | // The directed edge to:from is calculated by calling this function 64 | // with to/from reversed. 65 | 66 | var e, l, x intsets.Sparse 67 | l.Difference(from.Groups(), to.Groups()) 68 | e.Difference(to.Groups(), from.Groups()) 69 | 70 | var id int 71 | 72 | x.Copy(&e) 73 | for x.TakeMin(&id) { 74 | chain = append(chain, canvas.NodeIfc{id, 1}) 75 | } 76 | x.Copy(&l) 77 | for x.TakeMin(&id) { 78 | chain = append(chain, canvas.NodeIfc{id, 2}) 79 | } 80 | return chain 81 | } 82 | 83 | // provisionNode allocates the IDs for one node, meant to be called from a tree 84 | // traversal. If allocation fails, panic and expect to be recovered. The 85 | // allocated IDs are stored in newIds so as to be collected in the recover 86 | // routine. 87 | func provisionNode(g canvas.Graph, this canvas.Node, newIds *[]canvas.NodeIfc) { 88 | Info.Printf("Provisioning %s (%d)\n", this, this.ID()) 89 | for _, t := range g.From(this) { 90 | e := g.E(this, t) 91 | target := t.(canvas.Node) 92 | chain := computeChainFrom(this, target) 93 | fid, tid := e.F().Ifc(), e.T().Ifc() 94 | var err error 95 | if fid < 0 { 96 | if fid, err = e.From().(canvas.Node).NewInterfaceID(); err != nil { 97 | Error.Printf("Provisioning %s failed %s\n", e.From().(canvas.Node), err) 98 | panic(err) 99 | } 100 | *newIds = append(*newIds, canvas.NodeIfc{e.From().ID(), fid}) 101 | } 102 | if tid < 0 { 103 | if tid, err = e.To().(canvas.Node).NewInterfaceID(); err != nil { 104 | Error.Printf("Provisioning %s failed %s\n", e.To().(canvas.Node), err) 105 | panic(err) 106 | } 107 | *newIds = append(*newIds, canvas.NodeIfc{e.To().ID(), tid}) 108 | } 109 | if e.Update(chain, fid, tid) { 110 | } 111 | } 112 | } 113 | 114 | func (h *Renderer) Provision(g canvas.Graph, nodes []InterfaceNode) (err error) { 115 | newIds := []canvas.NodeIfc{} 116 | visitFn := func(prev, this canvas.Node) { 117 | provisionNode(g, this, &newIds) 118 | } 119 | t := canvas.NewDepthFirst(visitFn, filterInterfaceNode) 120 | 121 | defer func() { 122 | if r := recover(); r != nil { 123 | switch r := r.(type) { 124 | case error: 125 | err = r 126 | for _, ni := range newIds { 127 | g.Node(ni.ID()).ReleaseInterfaceID(ni.Ifc()) 128 | } 129 | // rollback 130 | default: 131 | panic(r) 132 | } 133 | } 134 | }() 135 | 136 | // Find all of the Adapter (internal) nodes reachable from an external interface. 137 | // Collect the ID of each node and update the modules table. 138 | for _, node := range nodes { 139 | if node.ID() < 0 { 140 | continue 141 | } 142 | provisionNode(g, node, &newIds) 143 | t.Walk(g, node, nil) 144 | } 145 | return 146 | } 147 | 148 | func (h *Renderer) Run(g canvas.Graph, nodes []InterfaceNode) { 149 | visitFn := func(prev, this canvas.Node) { 150 | Info.Printf("visit: %d :: %s\n", this.ID(), this.Path()) 151 | for _, t := range g.From(this) { 152 | e := g.E(this, t) 153 | adapter := this.(*canvas.AdapterNode).Adapter() 154 | if e.IsDeleted() { 155 | fc := adapter.Table("forward_chain") 156 | if fc == nil { 157 | panic(fmt.Errorf("Could not find forward_chain in adapter")) 158 | } 159 | 160 | // find and delete the reverse edge as well 161 | e2 := g.E(t, this) 162 | if !e2.IsDeleted() { 163 | panic(fmt.Errorf("Reverse edge %d->%d is not deleted", t.ID(), this.ID())) 164 | } 165 | 166 | // clear previous entry (if any) in the ifc table 167 | key := fmt.Sprintf("%d", e.F().Ifc()) 168 | if err := fc.Set(key, "{ [ 0x0 0x0 0x0 0x0 ] }"); err != nil { 169 | panic(err) 170 | } 171 | 172 | g.RemoveEdge(e) 173 | g.RemoveEdge(e2) 174 | 175 | // do not perform any further processing on node 't' 176 | continue 177 | } 178 | target := t.(canvas.Node) 179 | if adapter.Type() == "bridge" { 180 | if target, ok := target.(*ExtInterface); ok { 181 | if e.Serialize()[0] == 0 { 182 | continue 183 | } 184 | chain, err := NewEgressChain(e.Serialize()) 185 | if err != nil { 186 | panic(err) 187 | } 188 | defer chain.Close() 189 | Info.Printf(" %4d: %-11s%s\n", e.F().Ifc(), target.Path(), e) 190 | if err := ensureEgressFd(target.Link(), chain.FD()); err != nil { 191 | panic(err) 192 | } 193 | } 194 | } else { 195 | fc := adapter.Table("forward_chain") 196 | if fc == nil { 197 | panic(fmt.Errorf("Could not find forward_chain in adapter")) 198 | } 199 | key := fmt.Sprintf("%d", e.F().Ifc()) 200 | val := fmt.Sprintf("{%#x}", e.Serialize()) 201 | Info.Printf(" %4s: %-11s%s\n", key, target.Path(), val) 202 | if err := fc.Set(key, val); err != nil { 203 | panic(err) 204 | } 205 | //Debug.Printf(" %s:%d -> %s:%d\n", this.Path(), i, target.Path(), target.ID()) 206 | } 207 | } 208 | } 209 | t := canvas.NewDepthFirst(visitFn, filterInterfaceNode) 210 | // Find all of the Adapter (internal) nodes reachable from an external interface. 211 | // Collect the ID of each node and update the modules table. 212 | for _, node := range nodes { 213 | if node.ID() < 0 { 214 | continue 215 | } 216 | t.Walk(g, node, nil) 217 | } 218 | 219 | 220 | // reset interfaces with degree 0; these interfaces are now unreachable 221 | for _, node := range nodes { 222 | //Debug.Printf("Run cleanup: considering node %d\n", node.ID()) 223 | if node.ID() < 0 { 224 | continue 225 | } 226 | degree := g.Degree(node) 227 | //Debug.Printf("Run cleanup: node %d has degree %d\n", node.ID(), degree) 228 | if degree != 0 { 229 | continue 230 | } 231 | g.RemoveNode(node) 232 | 233 | // release and reset ID allocated for this interface 234 | node.ReleaseInterfaceID(node.ID()) 235 | node.SetID(-1) 236 | } 237 | 238 | } 239 | -------------------------------------------------------------------------------- /hover/bpf/bpf_module.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bpf 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "regexp" 21 | "runtime" 22 | "sync" 23 | "syscall" 24 | "unsafe" 25 | 26 | "github.com/iovisor/iomodules/hover/util" 27 | ) 28 | 29 | /* 30 | #cgo CFLAGS: -I/usr/include/bcc/compat 31 | #cgo LDFLAGS: -lbcc 32 | #include 33 | #include 34 | void perf_reader_free(void *ptr); 35 | */ 36 | import "C" 37 | 38 | var ( 39 | Debug = util.Debug 40 | Info = util.Info 41 | Warn = util.Warn 42 | Error = util.Error 43 | ) 44 | 45 | // BpfModule type 46 | type BpfModule struct { 47 | p unsafe.Pointer 48 | funcs map[string]int 49 | kprobes map[string]unsafe.Pointer 50 | } 51 | 52 | type compileRequest struct { 53 | code string 54 | cflags []string 55 | rspCh chan *BpfModule 56 | } 57 | 58 | const ( 59 | MAX_MODULES uint = 1024 60 | MAX_INTERFACES uint = 128 61 | ) 62 | 63 | var ( 64 | defaultCflags []string 65 | compileCh chan compileRequest 66 | bpfInitOnce sync.Once 67 | ) 68 | 69 | func bpfInit() { 70 | defaultCflags = []string{ 71 | fmt.Sprintf("-DNUMCPUS=%d", runtime.NumCPU()), 72 | fmt.Sprintf("-DMAX_INTERFACES=%d", MAX_INTERFACES), 73 | fmt.Sprintf("-DMAX_MODULES=%d", MAX_MODULES), 74 | "-DMAX_METADATA=10240", 75 | } 76 | compileCh = make(chan compileRequest) 77 | go compile() 78 | } 79 | 80 | // NewBpfModule constructor 81 | func newBpfModule(code string, cflags []string) *BpfModule { 82 | //Debug.Println("Creating BpfModule from string") 83 | cflagsC := make([]*C.char, len(defaultCflags)+len(cflags)) 84 | defer func() { 85 | for _, cflag := range cflagsC { 86 | C.free(unsafe.Pointer(cflag)) 87 | } 88 | }() 89 | for i, cflag := range cflags { 90 | cflagsC[i] = C.CString(cflag) 91 | } 92 | for i, cflag := range defaultCflags { 93 | cflagsC[len(cflags)+i] = C.CString(cflag) 94 | } 95 | cs := C.CString(code) 96 | defer C.free(unsafe.Pointer(cs)) 97 | c := C.bpf_module_create_c_from_string(cs, 2, (**C.char)(&cflagsC[0]), C.int(len(cflagsC))) 98 | if c == nil { 99 | return nil 100 | } 101 | return &BpfModule{ 102 | p: c, 103 | funcs: make(map[string]int), 104 | kprobes: make(map[string]unsafe.Pointer), 105 | } 106 | } 107 | 108 | func NewBpfModule(code string, cflags []string) *BpfModule { 109 | bpfInitOnce.Do(bpfInit) 110 | ch := make(chan *BpfModule) 111 | compileCh <- compileRequest{code, cflags, ch} 112 | return <-ch 113 | } 114 | 115 | func compile() { 116 | for { 117 | req := <-compileCh 118 | req.rspCh <- newBpfModule(req.code, req.cflags) 119 | } 120 | } 121 | 122 | func (bpf *BpfModule) Close() { 123 | //Debug.Println("Closing BpfModule") 124 | C.bpf_module_destroy(bpf.p) 125 | // close the kprobes opened by this module 126 | for k, v := range bpf.kprobes { 127 | C.perf_reader_free(v) 128 | desc := fmt.Sprintf("-:kprobes/%s", k) 129 | descCS := C.CString(desc) 130 | C.bpf_detach_kprobe(descCS) 131 | C.free(unsafe.Pointer(descCS)) 132 | } 133 | for _, fd := range bpf.funcs { 134 | err := syscall.Close(fd) 135 | if err != nil { 136 | Error.Printf("Error closing bpf fd: %s", err) 137 | } 138 | } 139 | } 140 | 141 | func (bpf *BpfModule) InitRxHandler() (int, error) { 142 | fd, err := bpf.LoadNet("handle_rx_wrapper") 143 | if err != nil { 144 | return -1, err 145 | } 146 | return fd, nil 147 | } 148 | 149 | func (bpf *BpfModule) initTxHandler() (int, error) { 150 | fd, err := bpf.LoadNet("handle_tx_wrapper") 151 | if err != nil { 152 | return -1, err 153 | } 154 | return fd, nil 155 | } 156 | 157 | func (bpf *BpfModule) LoadNet(name string) (int, error) { 158 | return bpf.Load(name, C.BPF_PROG_TYPE_SCHED_ACT) 159 | } 160 | func (bpf *BpfModule) LoadKprobe(name string) (int, error) { 161 | return bpf.Load(name, C.BPF_PROG_TYPE_KPROBE) 162 | } 163 | func (bpf *BpfModule) Load(name string, progType int) (int, error) { 164 | fd, ok := bpf.funcs[name] 165 | if ok { 166 | return fd, nil 167 | } 168 | fd, err := bpf.load(name, progType) 169 | if err != nil { 170 | return -1, err 171 | } 172 | bpf.funcs[name] = fd 173 | return fd, nil 174 | } 175 | 176 | func (bpf *BpfModule) load(name string, progType int) (int, error) { 177 | nameCS := C.CString(name) 178 | defer C.free(unsafe.Pointer(nameCS)) 179 | start := (*C.struct_bpf_insn)(C.bpf_function_start(bpf.p, nameCS)) 180 | size := C.int(C.bpf_function_size(bpf.p, nameCS)) 181 | license := C.bpf_module_license(bpf.p) 182 | version := C.bpf_module_kern_version(bpf.p) 183 | if start == nil { 184 | return -1, fmt.Errorf("BpfModule: unable to find %s", name) 185 | } 186 | logbuf := make([]byte, 65536) 187 | logbufP := (*C.char)(unsafe.Pointer(&logbuf[0])) 188 | fd := C.bpf_prog_load(uint32(progType), start, size, license, version, logbufP, C.uint(len(logbuf))) 189 | if fd < 0 { 190 | msg := string(logbuf[:bytes.IndexByte(logbuf, 0)]) 191 | return -1, fmt.Errorf("Error loading bpf program:\n%s", msg) 192 | } 193 | return int(fd), nil 194 | } 195 | 196 | var kprobeRegexp = regexp.MustCompile("[+.]") 197 | 198 | func (bpf *BpfModule) attachProbe(evName, desc string, fd int) error { 199 | if _, ok := bpf.kprobes[evName]; ok { 200 | return nil 201 | } 202 | 203 | evNameCS := C.CString(evName) 204 | descCS := C.CString(desc) 205 | res := C.bpf_attach_kprobe(C.int(fd), evNameCS, descCS, -1, 0, -1, nil, nil) 206 | C.free(unsafe.Pointer(evNameCS)) 207 | C.free(unsafe.Pointer(descCS)) 208 | 209 | if res == nil { 210 | return fmt.Errorf("Failed to attach BPF kprobe") 211 | } 212 | bpf.kprobes[evName] = res 213 | return nil 214 | } 215 | 216 | func (bpf *BpfModule) AttachKprobe(event string, fd int) error { 217 | evName := "p_" + kprobeRegexp.ReplaceAllString(event, "_") 218 | desc := fmt.Sprintf("p:kprobes/%s %s", evName, event) 219 | 220 | return bpf.attachProbe(evName, desc, fd) 221 | } 222 | 223 | func (bpf *BpfModule) AttachKretprobe(event string, fd int) error { 224 | evName := "r_" + kprobeRegexp.ReplaceAllString(event, "_") 225 | desc := fmt.Sprintf("r:kprobes/%s %s", evName, event) 226 | 227 | return bpf.attachProbe(evName, desc, fd) 228 | } 229 | 230 | func (bpf *BpfModule) TableSize() uint64 { 231 | size := C.bpf_num_tables(bpf.p) 232 | return uint64(size) 233 | } 234 | 235 | func (bpf *BpfModule) TableId(name string) C.size_t { 236 | cs := C.CString(name) 237 | defer C.free(unsafe.Pointer(cs)) 238 | return C.bpf_table_id(bpf.p, cs) 239 | } 240 | 241 | func (bpf *BpfModule) TableDesc(id uint64) map[string]interface{} { 242 | i := C.size_t(id) 243 | return map[string]interface{}{ 244 | "name": C.GoString(C.bpf_table_name(bpf.p, i)), 245 | "fd": int(C.bpf_table_fd_id(bpf.p, i)), 246 | "key_size": uint64(C.bpf_table_key_size_id(bpf.p, i)), 247 | "leaf_size": uint64(C.bpf_table_leaf_size_id(bpf.p, i)), 248 | "key_desc": C.GoString(C.bpf_table_key_desc_id(bpf.p, i)), 249 | "leaf_desc": C.GoString(C.bpf_table_leaf_desc_id(bpf.p, i)), 250 | } 251 | } 252 | 253 | func (bpf *BpfModule) TableIter() <-chan map[string]interface{} { 254 | ch := make(chan map[string]interface{}) 255 | go func() { 256 | size := C.bpf_num_tables(bpf.p) 257 | for i := C.size_t(0); i < size; i++ { 258 | ch <- bpf.TableDesc(uint64(i)) 259 | } 260 | close(ch) 261 | }() 262 | return ch 263 | } 264 | -------------------------------------------------------------------------------- /hover/netlink_monitor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package hover 16 | 17 | import ( 18 | "fmt" 19 | "strconv" 20 | "sync" 21 | "syscall" 22 | 23 | "github.com/vishvananda/netlink" 24 | 25 | "github.com/iovisor/iomodules/hover/bpf" 26 | "github.com/iovisor/iomodules/hover/canvas" 27 | ) 28 | 29 | // NetlinkMonitor keeps track of the interfaces on this host. It can invoke a 30 | // callback when an interface is added/deleted. 31 | type NetlinkMonitor struct { 32 | // receive LinkUpdates from nl.Subscribe 33 | updates chan netlink.LinkUpdate 34 | 35 | // close(nlDone) to terminate Subscribe loop 36 | done chan struct{} 37 | flush chan struct{} 38 | 39 | // nodes tracks netlink ifindex to graph Node mapping 40 | nodes map[int]*ExtInterface 41 | 42 | g canvas.Graph 43 | r *Renderer 44 | modules *bpf.BpfTable 45 | mtx sync.RWMutex 46 | } 47 | 48 | func NewNetlinkMonitor(g canvas.Graph, r *Renderer, modules *bpf.BpfTable) (res *NetlinkMonitor, err error) { 49 | nlmon := &NetlinkMonitor{ 50 | updates: make(chan netlink.LinkUpdate), 51 | done: make(chan struct{}), 52 | flush: make(chan struct{}), 53 | nodes: make(map[int]*ExtInterface), 54 | g: g, 55 | r: r, 56 | modules: modules, 57 | } 58 | err = netlink.LinkSubscribe(nlmon.updates, nlmon.done) 59 | defer func() { 60 | if err != nil { 61 | nlmon.Close() 62 | } 63 | }() 64 | if err != nil { 65 | return 66 | } 67 | links, err := netlink.LinkList() 68 | if err != nil { 69 | return 70 | } 71 | for _, link := range links { 72 | nlmon.handleNewlink(link) 73 | } 74 | Debug.Println("NewNetlinkMonitor DONE") 75 | go nlmon.ParseLinkUpdates() 76 | res = nlmon 77 | return 78 | } 79 | 80 | func (nm *NetlinkMonitor) Close() { 81 | close(nm.done) 82 | } 83 | 84 | func (nm *NetlinkMonitor) ensureBridge(link *netlink.Bridge) canvas.Node { 85 | b := nm.g.NodeByPath("b:" + link.Attrs().Name) 86 | if b == nil { 87 | a := canvas.NewBridgeAdapter(link) 88 | node := canvas.NewAdapterNode(a) 89 | node.SetID(nm.g.NewNodeID()) 90 | nm.g.AddNode(node) 91 | b = node 92 | } 93 | return b 94 | } 95 | 96 | func (nm *NetlinkMonitor) handleMasterChange(node *ExtInterface, link netlink.Link, isAdd bool) error { 97 | newMasterIdx := link.Attrs().MasterIndex 98 | Debug.Printf("link %s master %d\n", node.Link().Attrs().Name, newMasterIdx) 99 | if newMasterIdx != node.Link().Attrs().MasterIndex || isAdd { 100 | if newMasterIdx != 0 { 101 | // add case 102 | masterLink, err := netlink.LinkByIndex(newMasterIdx) 103 | if err != nil { 104 | return err 105 | } 106 | bridge, ok := masterLink.(*netlink.Bridge) 107 | if !ok { 108 | return fmt.Errorf("unsupported non-bridge master") 109 | } 110 | master := nm.ensureBridge(bridge) 111 | if node.ID() < 0 { 112 | node.SetID(nm.g.NewNodeID()) 113 | nm.g.AddNode(node) 114 | } 115 | // set to 0 so that the normal egress path is taken 116 | fid, tid := 0, 0 117 | nm.g.SetEdge(canvas.NewEdgeChain(node, master, &fid, &tid)) 118 | nm.g.SetEdge(canvas.NewEdgeChain(master, node, &tid, &fid)) 119 | if err := nm.r.Provision(nm.g, []InterfaceNode{node}); err != nil { 120 | return err 121 | } 122 | if err := nm.ensureInterface(nm.g, node); err != nil { 123 | return err 124 | } 125 | nm.r.Run(nm.g, []InterfaceNode{node}) 126 | } else { 127 | // remove case 128 | } 129 | } 130 | return nil 131 | } 132 | 133 | func (nm *NetlinkMonitor) handleNewlink(link netlink.Link) { 134 | nm.mtx.Lock() 135 | defer nm.mtx.Unlock() 136 | switch link := link.(type) { 137 | case *netlink.Bridge: 138 | nm.ensureBridge(link) 139 | default: 140 | if node, ok := nm.nodes[link.Attrs().Index]; ok { 141 | if err := nm.handleMasterChange(node, link, false); err != nil { 142 | Error.Println("Newlink failed master change", err) 143 | return 144 | } 145 | node.SetLink(link) 146 | } else { 147 | node := NewExtInterface(link) 148 | nm.nodes[link.Attrs().Index] = node 149 | if err := nm.handleMasterChange(node, link, true); err != nil { 150 | Error.Println("Newlink failed master change", err) 151 | return 152 | } 153 | } 154 | } 155 | } 156 | 157 | func (nm *NetlinkMonitor) handleDellink(link netlink.Link) { 158 | nm.mtx.Lock() 159 | defer nm.mtx.Unlock() 160 | switch link := link.(type) { 161 | case *netlink.Bridge: 162 | node := nm.g.NodeByPath("b:" + link.Attrs().Name) 163 | if node != nil { 164 | Warn.Println("TODO: remove resources for edges from " + node.Path()) 165 | node.Close() 166 | nm.g.RemoveNode(node) 167 | } 168 | default: 169 | if _, ok := nm.nodes[link.Attrs().Index]; ok { 170 | delete(nm.nodes, link.Attrs().Index) 171 | } 172 | } 173 | } 174 | 175 | func (nm *NetlinkMonitor) ParseLinkUpdates() { 176 | for { 177 | select { 178 | case update, ok := <-nm.updates: 179 | if !ok { 180 | // channel closed 181 | Info.Printf("nm %p updates closed\n", nm) 182 | return 183 | } 184 | switch update.Header.Type { 185 | case syscall.RTM_NEWLINK: 186 | nm.handleNewlink(update.Link) 187 | case syscall.RTM_DELLINK: 188 | nm.handleDellink(update.Link) 189 | } 190 | case _ = <-nm.flush: 191 | // when nm.nodes is queried, ensures that all pending updates have been processed 192 | } 193 | } 194 | } 195 | 196 | func (nm *NetlinkMonitor) Interfaces() (nodes []InterfaceNode) { 197 | nm.flush <- struct{}{} 198 | nm.mtx.RLock() 199 | defer nm.mtx.RUnlock() 200 | for _, node := range nm.nodes { 201 | nodes = append(nodes, node) 202 | } 203 | return 204 | } 205 | func (nm *NetlinkMonitor) InterfaceByName(name string) (node InterfaceNode, err error) { 206 | nm.flush <- struct{}{} 207 | nm.mtx.RLock() 208 | defer nm.mtx.RUnlock() 209 | link, err := netlink.LinkByName(name) 210 | if err != nil { 211 | return 212 | } 213 | var ok bool 214 | if node, ok = nm.nodes[link.Attrs().Index]; !ok { 215 | err = fmt.Errorf("No interface %s found", name) 216 | return 217 | } 218 | return 219 | } 220 | 221 | func (nm *NetlinkMonitor) ensureInterface(g canvas.Graph, node InterfaceNode) error { 222 | if node.ID() < 0 { 223 | return nil 224 | } 225 | Info.Printf("visit: id=%d :: fd=%d :: %s :: %d\n", node.ID(), node.FD(), node.Path(), node.Link().Attrs().Index) 226 | nm.modules.Set(strconv.Itoa(node.ID()), strconv.Itoa(node.FD())) 227 | switch deg := g.Degree(node); deg { 228 | case 2: 229 | //Debug.Printf("Adding ingress for %s\n", node.Link().Attrs().Name) 230 | next := g.From(node)[0].(canvas.Node) 231 | e := g.E(node, next) 232 | if e.Serialize()[0] == 0 { 233 | return nil 234 | } 235 | chain, err := NewIngressChain(e.Serialize()) 236 | if err != nil { 237 | return err 238 | } 239 | defer chain.Close() 240 | Info.Printf(" %4d: %-11s%s\n", e.F().Ifc(), next.Path(), e) 241 | if err := ensureIngressFd(node.Link(), chain.FD()); err != nil { 242 | return err 243 | } 244 | default: 245 | return fmt.Errorf("Invalid # edges for node %s, must be 2, got %d", node.Path(), deg) 246 | } 247 | return nil 248 | } 249 | 250 | func (nm *NetlinkMonitor) EnsureInterfaces(g canvas.Graph) { 251 | nm.flush <- struct{}{} 252 | nm.mtx.Lock() 253 | defer nm.mtx.Unlock() 254 | for _, node := range nm.nodes { 255 | if err := nm.ensureInterface(g, node); err != nil { 256 | panic(err) 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /hover/bpf/bpf_table.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package bpf 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "unsafe" 21 | 22 | "github.com/iovisor/iomodules/hover/api" 23 | ) 24 | 25 | /* 26 | #cgo CFLAGS: -I/usr/include/bcc/compat 27 | #cgo LDFLAGS: -lbcc 28 | #include 29 | #include 30 | */ 31 | import "C" 32 | 33 | type BpfTable struct { 34 | id C.size_t 35 | module *BpfModule 36 | } 37 | 38 | func NewBpfTable(id C.size_t, module *BpfModule) *BpfTable { 39 | return &BpfTable{ 40 | id: id, 41 | module: module, 42 | } 43 | } 44 | 45 | func (table *BpfTable) ID() string { 46 | return C.GoString(C.bpf_table_name(table.module.p, table.id)) 47 | } 48 | func (table *BpfTable) Name() string { 49 | return C.GoString(C.bpf_table_name(table.module.p, table.id)) 50 | } 51 | func (table *BpfTable) Config() map[string]interface{} { 52 | mod := table.module.p 53 | return map[string]interface{}{ 54 | "name": C.GoString(C.bpf_table_name(mod, table.id)), 55 | "fd": int(C.bpf_table_fd_id(mod, table.id)), 56 | "key_size": uint64(C.bpf_table_key_size_id(mod, table.id)), 57 | "leaf_size": uint64(C.bpf_table_leaf_size_id(mod, table.id)), 58 | "key_desc": C.GoString(C.bpf_table_key_desc_id(mod, table.id)), 59 | "leaf_desc": C.GoString(C.bpf_table_leaf_desc_id(mod, table.id)), 60 | } 61 | } 62 | func (table *BpfTable) keyToString(key []byte) string { 63 | key_size := C.bpf_table_key_size_id(table.module.p, table.id) 64 | keyP := unsafe.Pointer(&key[0]) 65 | keyStr := make([]byte, key_size*8) 66 | keyStrP := (*C.char)(unsafe.Pointer(&keyStr[0])) 67 | r := C.bpf_table_key_snprintf(table.module.p, table.id, keyStrP, C.size_t(len(keyStr)), keyP) 68 | if r == 0 { 69 | return string(keyStr) 70 | } 71 | return "" 72 | } 73 | func (table *BpfTable) leafToString(leaf []byte) string { 74 | leaf_size := C.bpf_table_leaf_size_id(table.module.p, table.id) 75 | leafP := unsafe.Pointer(&leaf[0]) 76 | leafStr := make([]byte, leaf_size*8) 77 | leafStrP := (*C.char)(unsafe.Pointer(&leafStr[0])) 78 | r := C.bpf_table_leaf_snprintf(table.module.p, table.id, leafStrP, C.size_t(len(leafStr)), leafP) 79 | if r == 0 { 80 | return string(leafStr) 81 | } 82 | return "" 83 | } 84 | 85 | func (table *BpfTable) keyToBytes(keyStr string) ([]byte, error) { 86 | mod := table.module.p 87 | key_size := C.bpf_table_key_size_id(mod, table.id) 88 | key := make([]byte, key_size) 89 | keyP := unsafe.Pointer(&key[0]) 90 | keyCS := C.CString(keyStr) 91 | defer C.free(unsafe.Pointer(keyCS)) 92 | r := C.bpf_table_key_sscanf(mod, table.id, keyCS, keyP) 93 | if r != 0 { 94 | //Warn.Printf("key type is \"%s\"\n", C.GoString(C.bpf_table_key_desc_id(mod, table.id))) 95 | return nil, fmt.Errorf("error scanning key (%v) from string", keyStr) 96 | } 97 | return key, nil 98 | } 99 | 100 | func (table *BpfTable) leafToBytes(leafStr string) ([]byte, error) { 101 | mod := table.module.p 102 | leaf_size := C.bpf_table_leaf_size_id(mod, table.id) 103 | leaf := make([]byte, leaf_size) 104 | leafP := unsafe.Pointer(&leaf[0]) 105 | leafCS := C.CString(leafStr) 106 | defer C.free(unsafe.Pointer(leafCS)) 107 | r := C.bpf_table_leaf_sscanf(mod, table.id, leafCS, leafP) 108 | if r != 0 { 109 | //Warn.Printf("leaf type is \"%s\"\n", C.GoString(C.bpf_table_leaf_desc_id(mod, table.id))) 110 | return nil, fmt.Errorf("error scanning leaf (%v) from string", leafStr) 111 | } 112 | return leaf, nil 113 | } 114 | 115 | // Get takes a key and returns the value or nil, and an 'ok' style indicator 116 | func (table *BpfTable) Get(keyStr string) (interface{}, bool) { 117 | mod := table.module.p 118 | fd := C.bpf_table_fd_id(mod, table.id) 119 | leaf_size := C.bpf_table_leaf_size_id(mod, table.id) 120 | key, err := table.keyToBytes(keyStr) 121 | if err != nil { 122 | return nil, false 123 | } 124 | leaf := make([]byte, leaf_size) 125 | keyP := unsafe.Pointer(&key[0]) 126 | leafP := unsafe.Pointer(&leaf[0]) 127 | r := C.bpf_lookup_elem(fd, keyP, leafP) 128 | if r != 0 { 129 | return nil, false 130 | } 131 | leafStr := make([]byte, leaf_size*8) 132 | leafStrP := (*C.char)(unsafe.Pointer(&leafStr[0])) 133 | r = C.bpf_table_leaf_snprintf(mod, table.id, leafStrP, C.size_t(len(leafStr)), leafP) 134 | if r != 0 { 135 | return nil, false 136 | } 137 | return api.ModuleTableEntry{ 138 | Key: keyStr, 139 | Value: string(leafStr[:bytes.IndexByte(leafStr, 0)]), 140 | }, true 141 | } 142 | 143 | func (table *BpfTable) Set(keyStr, leafStr string) error { 144 | if table == nil || table.module.p == nil { 145 | panic("table is nil") 146 | } 147 | fd := C.bpf_table_fd_id(table.module.p, table.id) 148 | key, err := table.keyToBytes(keyStr) 149 | if err != nil { 150 | return err 151 | } 152 | leaf, err := table.leafToBytes(leafStr) 153 | if err != nil { 154 | return err 155 | } 156 | keyP := unsafe.Pointer(&key[0]) 157 | leafP := unsafe.Pointer(&leaf[0]) 158 | r, err := C.bpf_update_elem(fd, keyP, leafP, 0) 159 | if r != 0 { 160 | return fmt.Errorf("BpfTable.Set: unable to update element (%s=%s): %s", keyStr, leafStr, err) 161 | } 162 | return nil 163 | } 164 | func (table *BpfTable) Delete(keyStr string) error { 165 | fd := C.bpf_table_fd_id(table.module.p, table.id) 166 | key, err := table.keyToBytes(keyStr) 167 | if err != nil { 168 | return err 169 | } 170 | keyP := unsafe.Pointer(&key[0]) 171 | r, err := C.bpf_delete_elem(fd, keyP) 172 | if r != 0 { 173 | return fmt.Errorf("BpfTable.Delete: unable to delete element (%s): %s", keyStr, err) 174 | } 175 | return nil 176 | } 177 | func (table *BpfTable) Iter() <-chan api.ModuleTableEntry { 178 | mod := table.module.p 179 | ch := make(chan api.ModuleTableEntry, 128) 180 | go func() { 181 | defer close(ch) 182 | fd := C.bpf_table_fd_id(mod, table.id) 183 | key_size := C.bpf_table_key_size_id(mod, table.id) 184 | leaf_size := C.bpf_table_leaf_size_id(mod, table.id) 185 | key := make([]byte, key_size) 186 | leaf := make([]byte, leaf_size) 187 | keyP := unsafe.Pointer(&key[0]) 188 | leafP := unsafe.Pointer(&leaf[0]) 189 | alternateKeys := []byte{0xff, 0x55} 190 | res := C.bpf_lookup_elem(fd, keyP, leafP) 191 | // make sure the start iterator is an invalid key 192 | for i := 0; i <= len(alternateKeys); i++ { 193 | if res < 0 { 194 | break 195 | } 196 | for j := range key { 197 | key[j] = alternateKeys[i] 198 | } 199 | res = C.bpf_lookup_elem(fd, keyP, leafP) 200 | } 201 | if res == 0 { 202 | Info.Println("BpfTable.Iter: No valid initial key found") 203 | return 204 | } 205 | keyStr := make([]byte, key_size*8) 206 | leafStr := make([]byte, leaf_size*8) 207 | keyStrP := (*C.char)(unsafe.Pointer(&keyStr[0])) 208 | leafStrP := (*C.char)(unsafe.Pointer(&leafStr[0])) 209 | for res = C.bpf_get_next_key(fd, keyP, keyP); res == 0; res = C.bpf_get_next_key(fd, keyP, keyP) { 210 | r := C.bpf_lookup_elem(fd, keyP, leafP) 211 | if r != 0 { 212 | continue 213 | } 214 | r = C.bpf_table_key_snprintf(mod, table.id, keyStrP, C.size_t(len(keyStr)), keyP) 215 | if r != 0 { 216 | break 217 | } 218 | r = C.bpf_table_leaf_snprintf(mod, table.id, leafStrP, C.size_t(len(leafStr)), leafP) 219 | if r != 0 { 220 | break 221 | } 222 | ch <- api.ModuleTableEntry{ 223 | Key: string(keyStr[:bytes.IndexByte(keyStr, 0)]), 224 | Value: string(leafStr[:bytes.IndexByte(leafStr, 0)]), 225 | } 226 | } 227 | }() 228 | return ch 229 | } 230 | -------------------------------------------------------------------------------- /gbp/test_basic.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | S=gbptest 4 | hoversock=127.0.0.1:5000 5 | gbpsock=127.0.0.1:5001 6 | upstreamsock=127.0.0.1:5002 7 | files=() 8 | 9 | tmux has-session -t $S &> /dev/null 10 | if [[ $? == 0 ]]; then 11 | echo "tmux session $S is already running" 12 | exit 1 13 | fi 14 | 15 | function cleanup() { 16 | trap - EXIT 17 | for f in ${files[@]}; do 18 | rm -f $f 19 | done 20 | tmux kill-session -t $S 21 | docker rm -f ${S}1 ${S}2 &> /dev/null 22 | } 23 | 24 | trap cleanup EXIT 25 | 26 | tmux new-session -s $S -n "source" -d 27 | tmux new-window -t $S:1 -n "hoverd" 28 | tmux new-window -t $S:2 -n "upstream" 29 | tmux new-window -t $S:3 -n "gbp" 30 | tmux new-window -t $S:4 -n "test1" 31 | tmux new-window -t $S:5 -n "test2" 32 | 33 | # pull and build the dependencies of this test, stop and exit if any one command fails 34 | sleep 20 & 35 | w=$! 36 | tmux send -t $S:0 'go get github.com/iovisor/iomodules/hover/hoverd; x=$?' C-m 37 | tmux send -t $S:0 'go get github.com/iovisor/iomodules/gbp/gbp; x=$[$x+$?]' C-m 38 | tmux send -t $S:0 'go install github.com/iovisor/iomodules/hover/hoverd; x=$[$x+$?]' C-m 39 | tmux send -t $S:0 'go install github.com/iovisor/iomodules/gbp/gbp; x=$[$x+$?]' C-m 40 | tmux send -t $S:0 'docker pull gliderlabs/alpine; x=$[$x+$?]' C-m 41 | tmux send -t $S:0 "[[ \$x -eq 0 ]] && kill $w" C-m 42 | wait $w &> /dev/null && { echo "source fetch took too long"; exit 1; } 43 | 44 | # Start the hover server, a stub upstream server, and finally the gbp server. 45 | # The upstream server should be replaced with a real GBP implementation. 46 | sleep 10 & 47 | w=$! 48 | tmux send -t $S:1 "sudo -E hoverd -listen $hoversock" C-m 49 | sleep 1 50 | tmux send -t $S:2 'echo -en "HTTP/1.0 200 OK\r\n\r\n" | nc -l 5002' C-m 51 | tmux send -t $S:2 "kill $w" C-m 52 | sleep 1 53 | tmux send -t $S:3 "gbp -upstream http://$upstreamsock -listen $gbpsock -dataplane http://$hoversock" C-m 54 | sleep 1 55 | wait $w &> /dev/null && { echo "server startup took too long"; exit 1; } 56 | 57 | # find the new uuid of the gbp server in the hover db 58 | id=$(http GET 127.0.0.1:5001/info | jq -r .id) 59 | echo $id 60 | 61 | links1=$(ip link show | awk -F'[ @\t:]*' '/master docker0/ {print $2}' | sort) 62 | # start two test clients 63 | tmux send -t $S:4 "docker run --rm -ti --name gbptest1 gliderlabs/alpine sh" C-m 64 | sleep 1 65 | links2=$(ip link show | awk -F'[ @\t:]*' '/master docker0/ {print $2}' | sort) 66 | tmux send -t $S:5 "docker run --rm -ti --name gbptest2 gliderlabs/alpine sh" C-m 67 | sleep 1 68 | links3=$(ip link show | awk -F'[ @\t:]*' '/master docker0/ {print $2}' | sort) 69 | 70 | oldIFS=$IFS IFS=$'\n\t' 71 | if1=$(comm -3 <(echo "${links1[*]}") <(echo "${links2[*]}") | xargs) 72 | if2=$(comm -3 <(echo "${links2[*]}") <(echo "${links3[*]}") | xargs) 73 | IFS=$oldIFS 74 | 75 | ip1=$(docker inspect -f {{.NetworkSettings.IPAddress}} gbptest1) 76 | ip2=$(docker inspect -f {{.NetworkSettings.IPAddress}} gbptest2) 77 | echo $ip1 $ip2 78 | if [[ "$ip1" = "" || "$ip2" = "" ]]; then 79 | echo "could not determine IPs of test containers" 80 | exit 1 81 | fi 82 | 83 | f=$(mktemp /tmp/gbpserver_XXXXXX.py) 84 | files+=($f) 85 | 86 | cat > $f <<'DELIM__' 87 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer 88 | class handler(BaseHTTPRequestHandler): 89 | def do_GET(self): 90 | self.send_response(200) 91 | self.send_header('Content-Type', 'application/json') 92 | self.end_headers() 93 | self.wfile.write(""" 94 | { 95 | "resolved-policy": [ 96 | { 97 | "consumer-tenant-id": "tenant-red", 98 | "consumer-epg-id": "clients", 99 | "provider-tenant-id": "tenant-red", 100 | "provider-epg-id": "webservers", 101 | "policy-rule-group-with-endpoint-constraints": [ 102 | { 103 | "policy-rule-group": [ 104 | { 105 | "tenant-id": "tenant-red", 106 | "contract-id": "icmp-http-contract", 107 | "subject-name": "allow-http-subject", 108 | "resolved-rule": [ 109 | { 110 | "name": "allow-http-rule", 111 | "classifier": [ 112 | { 113 | "name": "http-dest", 114 | "connection-tracking": "normal", 115 | "parameter-value": [ 116 | { 117 | "name": "destport", 118 | "int-value": 5001 119 | }, 120 | { 121 | "name": "proto", 122 | "int-value": 6 123 | } 124 | ], 125 | "direction": "in", 126 | "classifier-definition-id": "Classifier-L4" 127 | }, 128 | { 129 | "name": "http-src", 130 | "connection-tracking": "normal", 131 | "parameter-value": [ 132 | { 133 | "name": "proto", 134 | "int-value": 6 135 | }, 136 | { 137 | "name": "sourceport", 138 | "int-value": 5001 139 | } 140 | ], 141 | "direction": "out", 142 | "classifier-definition-id": "Classifier-L4" 143 | } 144 | ], 145 | "order": 0, 146 | "action": [ 147 | { 148 | "name": "allow1", 149 | "order": 0, 150 | "action-definition-id": "Action-Allow" 151 | } 152 | ] 153 | } 154 | ] 155 | }, 156 | { 157 | "tenant-id": "tenant-red", 158 | "contract-id": "icmp-http-contract", 159 | "subject-name": "allow-icmp-subject", 160 | "resolved-rule": [ 161 | { 162 | "name": "allow-icmp-rule", 163 | "classifier": [ 164 | { 165 | "name": "icmp", 166 | "connection-tracking": "normal", 167 | "parameter-value": [ 168 | { 169 | "name": "proto", 170 | "int-value": 1 171 | } 172 | ], 173 | "direction": "bidirectional", 174 | "classifier-definition-id": "Classifier-IP-Protocol" 175 | } 176 | ], 177 | "order": 0, 178 | "action": [ 179 | { 180 | "name": "allow1", 181 | "order": 0, 182 | "action-definition-id": "Action-Allow" 183 | } 184 | ] 185 | } 186 | ] 187 | } 188 | ] 189 | } 190 | ] 191 | } 192 | ] 193 | } 194 | """) 195 | return 196 | 197 | try: 198 | server = HTTPServer(('', 5002), handler) 199 | server.serve_forever() 200 | except KeyboardInterrupt: 201 | server.socket.close() 202 | DELIM__ 203 | 204 | tmux send -t $S:2 "python2 $f" C-m 205 | echo '{"resolved-policy-uri": "/restconf/operational/resolved-policy:resolved-policies/resolved-policy/tenant-red/clients/tenant-red/webservers"}' | http POST http://$gbpsock/policies/ 206 | echo '{"module": "'$id'"}' | http POST http://$hoversock/modules/host/interfaces/$if1/policies/ 207 | echo '{"module": "'$id'"}' | http POST http://$hoversock/modules/host/interfaces/$if2/policies/ 208 | echo '{"ip": "'$ip1'", "tenant": "tenant-red", "epg": "webservers"}' | http POST http://$gbpsock/endpoints/ 209 | echo '{"ip": "'$ip2'", "tenant": "tenant-red", "epg": "clients"}' | http POST http://$gbpsock/endpoints/ 210 | 211 | read -p "tmux ready, run 'tmux attach -t $S' from another shell: " 212 | -------------------------------------------------------------------------------- /gbp/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 PLUMgrid and others 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gbp 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "github.com/gorilla/mux" 21 | "net/http" 22 | "regexp" 23 | "runtime" 24 | ) 25 | 26 | var reqUriRegex = regexp.MustCompile(`^/restconf/operational/resolved-policy:resolved-policies/resolved-policy(/[[:graph:]]+){4}`) 27 | 28 | type routeResponse struct { 29 | statusCode int 30 | contentType string 31 | body interface{} 32 | } 33 | 34 | type GbpServer struct { 35 | handler http.Handler 36 | upstreamUri string 37 | dataplane *Dataplane 38 | } 39 | 40 | type handlerFunc func(r *http.Request) routeResponse 41 | 42 | func makeHandler(fn handlerFunc) http.HandlerFunc { 43 | return func(w http.ResponseWriter, r *http.Request) { 44 | defer func() { 45 | if r := recover(); r != nil { 46 | switch err := r.(type) { 47 | case runtime.Error: 48 | http.Error(w, "Internal error", http.StatusBadRequest) 49 | panic(err) 50 | case error: 51 | Error.Println(err.Error()) 52 | http.Error(w, err.Error(), http.StatusBadRequest) 53 | default: 54 | http.Error(w, "Internal error", http.StatusBadRequest) 55 | panic(r) 56 | } 57 | } 58 | }() 59 | 60 | rsp := fn(r) 61 | sendReply(w, r, &rsp) 62 | Info.Printf("%s %s %d\n", r.Method, r.URL, rsp.statusCode) 63 | return 64 | } 65 | } 66 | 67 | func redirect(url string, code int) routeResponse { 68 | return routeResponse{statusCode: code, body: url} 69 | } 70 | func notFound() routeResponse { 71 | return routeResponse{statusCode: http.StatusNotFound} 72 | } 73 | 74 | func sendReply(w http.ResponseWriter, r *http.Request, rsp *routeResponse) { 75 | if rsp.body != nil { 76 | if len(rsp.contentType) != 0 { 77 | w.Header().Set("Content-Type", rsp.contentType) 78 | } else { 79 | w.Header().Set("Content-Type", "application/json") 80 | } 81 | } 82 | switch { 83 | case rsp.statusCode == 0: 84 | w.WriteHeader(http.StatusOK) 85 | rsp.statusCode = http.StatusOK 86 | case 100 <= rsp.statusCode && rsp.statusCode < 300: 87 | w.WriteHeader(rsp.statusCode) 88 | case 300 <= rsp.statusCode && rsp.statusCode < 400: 89 | loc := "" 90 | if x, ok := rsp.body.(string); ok { 91 | loc = x 92 | } 93 | http.Redirect(w, r, loc, rsp.statusCode) 94 | case 400 <= rsp.statusCode: 95 | if rsp.statusCode == http.StatusNotFound { 96 | Info.Printf("Not Found: %s\n", r.URL) 97 | http.NotFound(w, r) 98 | } else { 99 | msg := "" 100 | if x, ok := rsp.body.(string); ok { 101 | msg = x 102 | } 103 | http.Error(w, msg, rsp.statusCode) 104 | } 105 | default: 106 | } 107 | if rsp.body != nil { 108 | if err := json.NewEncoder(w).Encode(rsp.body); err != nil { 109 | panic(err) 110 | } 111 | } 112 | } 113 | 114 | func getRequestVar(r *http.Request, key string) string { 115 | vars := mux.Vars(r) 116 | if vars == nil { 117 | panic(fmt.Errorf("Missing parameters in module request")) 118 | } 119 | value, ok := vars[key] 120 | if !ok { 121 | panic(fmt.Errorf("Missing parameter moduleId in request")) 122 | } 123 | return value 124 | } 125 | 126 | type infoEntry struct { 127 | Id string `json:"id"` 128 | } 129 | 130 | func (g *GbpServer) handleInfoGet(r *http.Request) routeResponse { 131 | return routeResponse{ 132 | body: &infoEntry{ 133 | Id: g.dataplane.Id(), 134 | }, 135 | } 136 | } 137 | 138 | func (g *GbpServer) handlePolicyList(r *http.Request) routeResponse { 139 | return notFound() 140 | } 141 | 142 | type createPolicyRequestUri struct { 143 | Uri string `json:"resolved-policy-uri"` 144 | } 145 | 146 | type createPolicyRequest struct { 147 | ResolvedPolicy *ResolvedPolicy `json:"resolved-policies"` 148 | } 149 | 150 | func (g *GbpServer) handlePolicyPost(r *http.Request) routeResponse { 151 | var req createPolicyRequestUri 152 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 153 | panic(err) 154 | } 155 | 156 | /* Received URI to fetch policy for relevant endpoints to this Agent */ 157 | reqUri := req.Uri 158 | if reqUriRegex.MatchString(reqUri) == false { 159 | panic(fmt.Errorf("Uri returned: %s is not in format `^/restconf/operational/resolved-policy:resolved-policies/resolved-policy/////`", reqUri)) 160 | } 161 | 162 | /* Authenticate */ 163 | Debug.Printf(g.upstreamUri + reqUri) 164 | reqGet, err := http.NewRequest("GET", g.upstreamUri+reqUri, nil) 165 | reqGet.SetBasicAuth("admin", "admin") 166 | 167 | client := http.Client{} 168 | resp, err := client.Do(reqGet) 169 | if err != nil { 170 | Error.Printf("Error : %s", err) 171 | } 172 | var req2 ResolvedPolicy 173 | 174 | if err := json.NewDecoder(resp.Body).Decode(&req2); err != nil { 175 | panic(err) 176 | } 177 | 178 | for _, policy := range req2.ResolvedPolicies { 179 | if err := g.dataplane.ParsePolicy(policy); err != nil { 180 | panic(err) 181 | } 182 | } 183 | return routeResponse{} 184 | } 185 | func (g *GbpServer) handlePolicyGet(r *http.Request) routeResponse { 186 | return notFound() 187 | } 188 | func (g *GbpServer) handlePolicyPut(r *http.Request) routeResponse { 189 | return notFound() 190 | } 191 | func (g *GbpServer) handlePolicyDelete(r *http.Request) routeResponse { 192 | return notFound() 193 | } 194 | 195 | func (g *GbpServer) handleEndpointList(r *http.Request) routeResponse { 196 | entries := []*EndpointEntry{} 197 | for endpoint := range g.dataplane.Endpoints() { 198 | entries = append(entries, endpoint) 199 | } 200 | return routeResponse{body: entries} 201 | } 202 | 203 | func (g *GbpServer) handleEndpointPost(r *http.Request) routeResponse { 204 | var req EndpointEntry 205 | if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 206 | panic(err) 207 | } 208 | if err := g.dataplane.AddEndpoint(req.Ip, req.Tenant, req.Epg); err != nil { 209 | panic(err) 210 | } 211 | return routeResponse{body: req} 212 | } 213 | func (g *GbpServer) handleEndpointGet(r *http.Request) routeResponse { 214 | return notFound() 215 | } 216 | func (g *GbpServer) handleEndpointPut(r *http.Request) routeResponse { 217 | return notFound() 218 | } 219 | func (g *GbpServer) handleEndpointDelete(r *http.Request) routeResponse { 220 | return notFound() 221 | } 222 | 223 | func (g *GbpServer) Handler() http.Handler { 224 | return g.handler 225 | } 226 | 227 | func NewServer(upstreamUri, dataplaneUri string) (*GbpServer, error) { 228 | Info.Println("GBP module starting") 229 | rtr := mux.NewRouter() 230 | 231 | g := &GbpServer{ 232 | handler: rtr, 233 | upstreamUri: upstreamUri, 234 | dataplane: NewDataplane(":memory:"), 235 | } 236 | if err := g.dataplane.Init(dataplaneUri); err != nil { 237 | return nil, err 238 | } 239 | 240 | rtr.Methods("GET").Path("/info").HandlerFunc(makeHandler(g.handleInfoGet)) 241 | 242 | pol := rtr.PathPrefix("/policies").Subrouter() 243 | pol.Methods("POST").Path("/").HandlerFunc(makeHandler(g.handlePolicyPost)) 244 | pol.Methods("GET").Path("/").HandlerFunc(makeHandler(g.handlePolicyList)) 245 | pol.Methods("GET").Path("/{policyId}").HandlerFunc(makeHandler(g.handlePolicyGet)) 246 | pol.Methods("PUT").Path("/{policyId}").HandlerFunc(makeHandler(g.handlePolicyPut)) 247 | pol.Methods("DELETE").Path("/{policyId}").HandlerFunc(makeHandler(g.handlePolicyDelete)) 248 | 249 | end := rtr.PathPrefix("/endpoints").Subrouter() 250 | end.Methods("POST").Path("/").HandlerFunc(makeHandler(g.handleEndpointPost)) 251 | end.Methods("GET").Path("/").HandlerFunc(makeHandler(g.handleEndpointList)) 252 | end.Methods("GET").Path("/{endpointId}").HandlerFunc(makeHandler(g.handleEndpointGet)) 253 | end.Methods("PUT").Path("/{endpointId}").HandlerFunc(makeHandler(g.handleEndpointPut)) 254 | end.Methods("DELETE").Path("/{endpointId}").HandlerFunc(makeHandler(g.handleEndpointDelete)) 255 | 256 | // new routes go here 257 | 258 | return g, nil 259 | } 260 | -------------------------------------------------------------------------------- /hover/canvas/graph.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package canvas 16 | 17 | import ( 18 | "fmt" 19 | "io/ioutil" 20 | "math" 21 | 22 | "github.com/gonum/graph" 23 | "github.com/gonum/graph/encoding/dot" 24 | "github.com/gonum/graph/simple" 25 | "github.com/gonum/graph/traverse" 26 | "golang.org/x/tools/container/intsets" 27 | 28 | "github.com/iovisor/iomodules/hover/util" 29 | ) 30 | 31 | type Node interface { 32 | graph.Node 33 | FD() int 34 | String() string 35 | Path() string 36 | DOTID() string 37 | SetID(id int) 38 | NewInterfaceID() (int, error) 39 | ReleaseInterfaceID(id int) 40 | Groups() *intsets.Sparse 41 | Close() 42 | } 43 | 44 | type NodeBase struct { 45 | id int 46 | fd int 47 | uuid string 48 | prefix string 49 | handles *util.HandlePool 50 | groups *intsets.Sparse 51 | } 52 | 53 | func NewNodeBase(id, fd int, uuid, prefix string, nhandles uint) NodeBase { 54 | return NodeBase{ 55 | id: id, 56 | fd: fd, 57 | uuid: uuid, 58 | prefix: prefix, 59 | handles: util.NewHandlePool(nhandles), 60 | groups: &intsets.Sparse{}, 61 | } 62 | } 63 | 64 | func (n *NodeBase) ID() int { return n.id } 65 | func (n *NodeBase) SetID(id int) { n.id = id } 66 | func (n *NodeBase) FD() int { return n.fd } 67 | func (n *NodeBase) SetFD(fd int) { n.fd = fd } 68 | func (n *NodeBase) DOTID() string { return fmt.Sprintf("%q", n.Path()) } 69 | func (n *NodeBase) Path() string { return n.prefix + n.uuid } 70 | func (n *NodeBase) String() string { return n.Path() } 71 | func (n *NodeBase) Groups() *intsets.Sparse { return n.groups } 72 | func (n *NodeBase) NewInterfaceID() (int, error) { return n.handles.Acquire() } 73 | func (n *NodeBase) ReleaseInterfaceID(id int) { n.handles.Release(id) } 74 | func (n *NodeBase) Close() {} 75 | 76 | type NodeIfc struct { 77 | N int 78 | I int 79 | } 80 | 81 | func (ni NodeIfc) ID() int { return ni.N } 82 | func (ni NodeIfc) Ifc() int { return ni.I } 83 | func (ni NodeIfc) Serialize() int { return ni.I<<16 | ni.N } 84 | 85 | type Edge interface { 86 | graph.Edge 87 | F() NodeIfc 88 | T() NodeIfc 89 | Update([]NodeIfc, int, int) bool 90 | ChainEquals([]NodeIfc) bool 91 | Serialize() [4]int 92 | ID() string 93 | MarkDeleted() 94 | IsDeleted() bool 95 | } 96 | 97 | type EdgeChain struct { 98 | id string 99 | f, t Node 100 | w [3]NodeIfc 101 | fromIfc, toIfc *int 102 | dirty bool 103 | deleted bool 104 | } 105 | 106 | func NewEdgeChain(f, t Node, fp, tp *int) *EdgeChain { 107 | return &EdgeChain{ 108 | id: util.Encrypter.EncodePair(f.ID(), t.ID()), 109 | f: f, 110 | t: t, 111 | w: [3]NodeIfc{}, 112 | fromIfc: fp, 113 | toIfc: tp, 114 | } 115 | } 116 | func (e *EdgeChain) ID() string { return e.id } 117 | func (e *EdgeChain) From() graph.Node { return e.f } 118 | func (e *EdgeChain) To() graph.Node { return e.t } 119 | func (e *EdgeChain) F() NodeIfc { return NodeIfc{e.f.ID(), *e.fromIfc} } 120 | func (e *EdgeChain) T() NodeIfc { return NodeIfc{e.t.ID(), *e.toIfc} } 121 | func (e *EdgeChain) Weight() float64 { return float64(2) } 122 | func (e *EdgeChain) MarkDeleted() { e.deleted = true } 123 | func (e *EdgeChain) IsDeleted() bool { return e.deleted } 124 | 125 | func (e *EdgeChain) ChainEquals(dst []NodeIfc) bool { 126 | if len(e.w) != len(dst) { 127 | return false 128 | } 129 | for i, v := range e.w { 130 | if dst[i] != v { 131 | return false 132 | } 133 | } 134 | return true 135 | } 136 | func (e *EdgeChain) Update(chain []NodeIfc, fromIfc, toIfc int) bool { 137 | if !e.ChainEquals(chain) { 138 | if len(chain) > len(e.w) { 139 | panic("EdgeChain.Update: chain too long") 140 | } 141 | for i, _ := range e.w { 142 | e.w[i] = NodeIfc{} 143 | } 144 | for i, v := range chain { 145 | e.w[i] = v 146 | } 147 | e.dirty = true 148 | } 149 | if *e.fromIfc != fromIfc { 150 | *e.fromIfc = fromIfc 151 | e.dirty = true 152 | } 153 | if *e.toIfc != toIfc { 154 | *e.toIfc = toIfc 155 | e.dirty = true 156 | } 157 | return e.dirty 158 | } 159 | 160 | func (e *EdgeChain) serialize() [4]int { 161 | buf := [4]int{} 162 | if e.deleted { 163 | return buf 164 | } 165 | chain := buf[:0] 166 | for _, ni := range e.w { 167 | if ni.Ifc() == 0 { 168 | break 169 | } 170 | chain = append(chain, ni.Serialize()) 171 | } 172 | if *e.toIfc > 0 { 173 | chain = append(chain, NodeIfc{e.t.ID(), *e.toIfc}.Serialize()) 174 | } 175 | return buf 176 | } 177 | func (e *EdgeChain) Serialize() [4]int { 178 | e.dirty = false 179 | return e.serialize() 180 | } 181 | func (e *EdgeChain) String() string { 182 | // compatible with scanf bcc key reader 183 | return fmt.Sprintf("{%#x}", e.serialize()) 184 | } 185 | 186 | type Graph interface { 187 | graph.Directed 188 | graph.NodeAdder 189 | graph.NodeRemover 190 | SetEdge(Edge) 191 | RemoveEdge(Edge) 192 | Degree(graph.Node) int 193 | Node(int) Node 194 | E(u, v graph.Node) Edge 195 | HasPath(path string) bool 196 | NodeByPath(path string) Node 197 | } 198 | 199 | type DirectedGraph struct { 200 | simple.DirectedGraph 201 | paths map[string]int 202 | } 203 | 204 | func NewGraph() Graph { 205 | return &DirectedGraph{ 206 | DirectedGraph: *simple.NewDirectedGraph(0, math.Inf(1)), 207 | paths: make(map[string]int), 208 | } 209 | } 210 | 211 | func (g *DirectedGraph) Copy(src Graph) { 212 | g.DirectedGraph = *simple.NewDirectedGraph(0, math.Inf(1)) 213 | graph.Copy(&g.DirectedGraph, src) 214 | g.paths = make(map[string]int) 215 | for k, v := range src.(*DirectedGraph).paths { 216 | g.paths[k] = v 217 | } 218 | } 219 | 220 | func (g *DirectedGraph) Node(id int) Node { return g.DirectedGraph.Node(id).(Node) } 221 | func (g *DirectedGraph) E(u, v graph.Node) Edge { return g.Edge(u, v).(Edge) } 222 | 223 | func (g *DirectedGraph) NodeByPath(path string) Node { 224 | if id, ok := g.paths[path]; ok { 225 | return g.DirectedGraph.Node(id).(Node) 226 | } 227 | return nil 228 | } 229 | 230 | func (g *DirectedGraph) HasPath(path string) bool { 231 | if id, ok := g.paths[path]; ok { 232 | return g.Has(simple.Node(id)) 233 | } 234 | return false 235 | } 236 | 237 | func (g *DirectedGraph) SetEdge(e Edge) { 238 | g.DirectedGraph.SetEdge(e) 239 | } 240 | func (g *DirectedGraph) RemoveEdge(e Edge) { 241 | g.DirectedGraph.RemoveEdge(e) 242 | } 243 | 244 | func (g *DirectedGraph) AddNode(node graph.Node) { 245 | Debug.Printf("AddNode %s %d\n", node.(Node).Path(), node.ID()) 246 | g.DirectedGraph.AddNode(node) 247 | g.paths[node.(Node).Path()] = node.ID() 248 | } 249 | func (g *DirectedGraph) RemoveNode(node graph.Node) { 250 | Debug.Printf("RemoveNode %s %d\n", node.(Node).Path(), node.ID()) 251 | g.DirectedGraph.RemoveNode(node) 252 | delete(g.paths, node.(Node).Path()) 253 | } 254 | 255 | func DumpDotFile(g Graph) { 256 | b, err := dot.Marshal(g, "dump", "", " ", true) 257 | if err != nil { 258 | Error.Println(err) 259 | return 260 | } 261 | err = ioutil.WriteFile("/tmp/hover.dot", b, 0644) 262 | if err != nil { 263 | Error.Println(err) 264 | } 265 | } 266 | 267 | func NewDepthFirst(visit func(u, v Node), filter func(e Edge) bool) *traverse.DepthFirst { 268 | return &traverse.DepthFirst{ 269 | Visit: func(u, v graph.Node) { 270 | visit(u.(Node), v.(Node)) 271 | }, 272 | EdgeFilter: func(e graph.Edge) bool { 273 | return filter(e.(Edge)) 274 | }, 275 | } 276 | } 277 | 278 | func NewBreadthFirst(visit func(u, v Node), filter func(e Edge) bool) *traverse.BreadthFirst { 279 | return &traverse.BreadthFirst{ 280 | Visit: func(u, v graph.Node) { 281 | visit(u.(Node), v.(Node)) 282 | }, 283 | EdgeFilter: func(e graph.Edge) bool { 284 | return filter(e.(Edge)) 285 | }, 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /hover/bpf/cfiles.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // vim: set ts=8:sts=8:sw=8:noet 16 | 17 | package bpf 18 | 19 | var IomoduleH string = ` 20 | #include 21 | #include 22 | 23 | enum { 24 | RX_OK, 25 | RX_REDIRECT, 26 | RX_DROP, 27 | RX_RECIRCULATE, 28 | RX_ERROR, 29 | }; 30 | 31 | struct chain { 32 | u32 hops[4]; 33 | }; 34 | static inline u16 chain_ifc(struct chain *c, int id) { 35 | return c->hops[id] >> 16; 36 | } 37 | static inline u16 chain_module(struct chain *c, int id) { 38 | return c->hops[id] & 0xffff; 39 | } 40 | 41 | struct type_value { 42 | u64 type:8; 43 | u64 value:56; 44 | }; 45 | struct metadata { 46 | // An array of type/value pairs for the module to do with as it pleases. The 47 | // array is initialized to zero when the event first enters the module chain. 48 | // The values are preserved across modules. 49 | struct type_value data[4]; 50 | 51 | // A field reserved for use by the wrapper and helper functions. 52 | u32 is_egress:1; 53 | u32 flags:31; 54 | 55 | // The length of the packet currently being processed. Read-only. 56 | u32 pktlen; 57 | 58 | // The module id currently processing the packet. 59 | int module_id; 60 | 61 | // The interface on which a packet was received. Numbering is local to the 62 | // module. 63 | int in_ifc; 64 | 65 | // If the module intends to forward the packet, it must call pkt_redirect to 66 | // set this field to determine the next-hop. 67 | int redir_ifc; 68 | 69 | int clone_ifc; 70 | }; 71 | 72 | // iomodule must implement this function to attach to the networking stack 73 | static int handle_rx(void *pkt, struct metadata *md); 74 | static int handle_tx(void *pkt, struct metadata *md); 75 | 76 | static int pkt_redirect(void *pkt, struct metadata *md, int ifc); 77 | static int pkt_mirror(void *pkt, struct metadata *md, int ifc); 78 | static int pkt_drop(void *pkt, struct metadata *md); 79 | ` 80 | 81 | var NetdevRxC string = ` 82 | BPF_TABLE("extern", int, int, modules, MAX_MODULES); 83 | int ingress(struct __sk_buff *skb) { 84 | //bpf_trace_printk("ingress %d %x\n", skb->ifindex, CHAIN_VALUE0); 85 | skb->cb[0] = CHAIN_VALUE0 >> 16; 86 | skb->cb[1] = CHAIN_VALUE1; 87 | skb->cb[2] = CHAIN_VALUE2; 88 | skb->cb[3] = CHAIN_VALUE3; 89 | modules.call(skb, CHAIN_VALUE0 & 0xffff); 90 | //bpf_trace_printk("ingress drop\n"); 91 | return 2; 92 | } 93 | ` 94 | 95 | var NetdevTxC string = ` 96 | int egress(struct __sk_buff *skb) { 97 | //bpf_trace_printk("egress %d\n", INTERFACE_ID); 98 | bpf_redirect(INTERFACE_ID, 0); 99 | return 7; 100 | } 101 | ` 102 | 103 | var NetdevEgressC string = ` 104 | BPF_TABLE("extern", int, int, modules, MAX_MODULES); 105 | int egress(struct __sk_buff *skb) { 106 | //bpf_trace_printk("egress %d %x\n", skb->ifindex, CHAIN_VALUE0); 107 | skb->cb[0] = CHAIN_VALUE0 >> 16; 108 | skb->cb[1] = CHAIN_VALUE1; 109 | skb->cb[2] = CHAIN_VALUE2; 110 | skb->cb[3] = CHAIN_VALUE3; 111 | modules.call(skb, CHAIN_VALUE0 & 0xffff); 112 | //bpf_trace_printk("egress drop\n"); 113 | return 0; 114 | } 115 | ` 116 | 117 | var PatchC string = ` 118 | #include 119 | 120 | //BPF_TABLE_PUBLIC("array", int, struct metadata, metadata, NUMCPUS); 121 | 122 | BPF_TABLE_PUBLIC("prog", int, int, modules, MAX_MODULES); 123 | 124 | // table for tracking metadata in skbs when packet is in-kernel 125 | //BPF_TABLE("hash", uintptr_t, struct metadata, skb_metadata, MAX_METADATA); 126 | 127 | #if 0 128 | // Attach to kfree_skbmem kprobe to reclaim metadata. 129 | // This is a bit of a hack, but all of the skb fields are written over when 130 | // traversing some parts of the kernel, like nft or netns boundary. 131 | int metadata_kfree_skbmem(struct pt_regs *ctx, struct sk_buff *skb) { 132 | uintptr_t skbkey = (uintptr_t)skb | 1; 133 | skb_metadata.delete(&skbkey); 134 | return 0; 135 | } 136 | 137 | // Invoke the next module in the chain. 138 | // When next module is a bpf function and is successfully invoked, this 139 | // function never returns. 140 | static int invoke_module(struct __sk_buff *skb, struct metadata *md, struct link *link) { 141 | if (link->module_id != 0) { 142 | int md_id = bpf_get_smp_processor_id(); 143 | skb->cb[0] = md_id; 144 | md->in_ifc = link->ifc; 145 | md->module_id = link->module_id; 146 | metadata.update(&md_id, md); 147 | modules.call(skb, md->module_id); 148 | } else { 149 | uintptr_t skbkey = (uintptr_t)skb | 1; 150 | skb_metadata.update(&skbkey, md); 151 | //bpf_trace_printk("set metadata for 0x%lx\n", skbkey); 152 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,4,0) 153 | bpf_redirect(link->ifc, 0); 154 | return TC_ACT_REDIRECT; 155 | #else 156 | bpf_clone_redirect(skb, link->ifc, 0); 157 | return TC_ACT_SHOT; 158 | #endif 159 | } 160 | bpf_trace_printk("drop %d\n", link->ifc); 161 | return TC_ACT_SHOT; 162 | } 163 | 164 | static int recv_netdev(struct __sk_buff *skb, bool is_egress) { 165 | int md_id = bpf_get_smp_processor_id(); 166 | 167 | struct metadata md; 168 | // recover metadata from the skb lookaside table 169 | // (will not occur for packets that are only rx'd from a netdev once) 170 | uintptr_t skbkey = (uintptr_t)skb | 1; 171 | struct metadata *skb_md = skb_metadata.lookup(&skbkey); 172 | if (skb_md) 173 | md = *skb_md; 174 | else 175 | md = (struct metadata){}; 176 | 177 | struct link_key lkey = { 178 | .module_id = 0, 179 | .ifc = skb->ifindex, 180 | .is_egress = is_egress, 181 | }; 182 | struct link *link = links.lookup(&lkey); 183 | if (!link) { 184 | bpf_trace_printk("recv_netdev: miss\n"); 185 | return TC_ACT_SHOT; 186 | } 187 | 188 | md.pktlen = skb->len; 189 | md.is_egress = is_egress; 190 | return invoke_module(skb, &md, link); 191 | } 192 | 193 | int recv_tailcall(struct __sk_buff *skb) { 194 | int md_id = skb->cb[0]; 195 | struct metadata md; 196 | struct metadata *md_ptr = metadata.lookup(&md_id); 197 | if (!md_ptr) 198 | return TC_ACT_SHOT; 199 | md = *md_ptr; 200 | 201 | struct link_key lkey = { 202 | .module_id = md.module_id, 203 | }; 204 | // Either a module should invoke a redirect action, or we need to 205 | // lookup the next module in the chain. 206 | if (md.redir_ifc) { 207 | lkey.ifc = md.redir_ifc; 208 | } else { 209 | lkey.ifc = md.in_ifc; 210 | } 211 | struct link *link = links.lookup(&lkey); 212 | if (!link) { 213 | uintptr_t skbkey = (uintptr_t)skb | 1; 214 | skb_metadata.update(&skbkey, &md); 215 | //bpf_trace_printk("set metadata for 0x%lx\n", skbkey); 216 | //if (md.is_egress) 217 | return TC_ACT_OK; 218 | //return TC_ACT_SHOT; 219 | } 220 | return invoke_module(skb, &md, link); 221 | } 222 | #endif 223 | ` 224 | 225 | var WrapperC string = ` 226 | //BPF_TABLE("extern", int, struct metadata, metadata, NUMCPUS); 227 | BPF_TABLE("extern", int, int, modules, MAX_MODULES); 228 | BPF_TABLE("array", int, struct chain, forward_chain, MAX_INTERFACES); 229 | 230 | static int forward(struct __sk_buff *skb, int out_ifc) { 231 | struct chain *cur = (struct chain *)skb->cb; 232 | struct chain *next = forward_chain.lookup(&out_ifc); 233 | if (next) { 234 | cur->hops[0] = chain_ifc(next, 0); 235 | cur->hops[1] = next->hops[1]; 236 | cur->hops[2] = next->hops[2]; 237 | cur->hops[3] = next->hops[3]; 238 | //bpf_trace_printk("fwd:%d=0x%x %d\n", out_ifc, next->hops[0], chain_module(next, 0)); 239 | modules.call(skb, chain_module(next, 0)); 240 | } 241 | //bpf_trace_printk("fwd:%d=0\n", out_ifc); 242 | return TC_ACT_SHOT; 243 | } 244 | 245 | static int chain_pop(struct __sk_buff *skb) { 246 | struct chain *cur = (struct chain *)skb->cb; 247 | struct chain orig = *cur; 248 | cur->hops[0] = chain_ifc(&orig, 1); 249 | cur->hops[1] = cur->hops[2]; 250 | cur->hops[2] = cur->hops[3]; 251 | cur->hops[3] = 0; 252 | if (cur->hops[0]) { 253 | modules.call(skb, chain_module(&orig, 1)); 254 | } 255 | 256 | //bpf_trace_printk("pop empty\n"); 257 | return TC_ACT_OK; 258 | } 259 | 260 | int handle_rx_wrapper(struct __sk_buff *skb) { 261 | //bpf_trace_printk("" MODULE_UUID_SHORT ": rx:%d\n", skb->cb[0]); 262 | struct metadata md = {}; 263 | md.in_ifc = skb->cb[0]; 264 | 265 | int rc = handle_rx(skb, &md); 266 | 267 | // TODO: implementation 268 | switch (rc) { 269 | case RX_OK: 270 | return chain_pop(skb); 271 | case RX_REDIRECT: 272 | return forward(skb, md.redir_ifc); 273 | //case RX_RECIRCULATE: 274 | // modules.call(skb, 1); 275 | // break; 276 | case RX_DROP: 277 | return TC_ACT_SHOT; 278 | } 279 | return TC_ACT_SHOT; 280 | } 281 | 282 | static int pkt_redirect(void *pkt, struct metadata *md, int ifc) { 283 | md->redir_ifc = ifc; 284 | return TC_ACT_OK; 285 | } 286 | 287 | static int pkt_mirror(void *pkt, struct metadata *md, int ifc) { 288 | md->clone_ifc = ifc; 289 | return TC_ACT_OK; 290 | } 291 | ` 292 | -------------------------------------------------------------------------------- /dnsmon/dataplane.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dnsmon 16 | 17 | import ( 18 | "bytes" 19 | "encoding/json" 20 | "fmt" 21 | "io/ioutil" 22 | _ "net" 23 | "net/http" 24 | ) 25 | 26 | var filterImplC string = ` 27 | struct Counter { 28 | u64 rx_pkts; 29 | u64 tx_pkts; 30 | u64 rx_bytes; 31 | u64 tx_bytes; 32 | }; 33 | 34 | struct Ip4Key { 35 | u32 src; 36 | u32 dst; 37 | }; 38 | 39 | struct Ip6Key { 40 | u64 src_hi; 41 | u64 src_lo; 42 | u64 dst_hi; 43 | u64 dst_lo; 44 | }; 45 | 46 | BPF_TABLE("hash", struct Ip4Key, struct Counter, ip4_flows, 10240); 47 | BPF_TABLE("hash", struct Ip6Key, struct Counter, ip6_flows, 10240); 48 | 49 | enum direction { 50 | DIRECTION_IN, 51 | DIRECTION_OUT, 52 | }; 53 | 54 | static u8 pop_ipv6_headers(void *skb, u8 **cursor, u8 next_header) { 55 | struct ip6_opt_t *opt = (void *)(*cursor); 56 | 57 | switch (next_header) { 58 | case 0: goto opt0; 59 | case 60: goto opt60_1; 60 | case 43: goto opt43; 61 | case 44: goto opt44; 62 | //case 51: goto opt51; 63 | //case 50: goto opt50; 64 | //case 135: goto opt135; 65 | default: goto DONE; 66 | } 67 | 68 | opt0: { 69 | opt = cursor_advance(*cursor, sizeof(*opt)); 70 | u8 ext_len = opt->ext_len << 3; 71 | cursor_advance(*cursor, ext_len); 72 | switch (opt->next_header) { 73 | case 60: goto opt60_1; 74 | case 43: goto opt43; 75 | case 44: goto opt44; 76 | //case 51: goto opt51; 77 | //case 50: goto opt50; 78 | //case 135: goto opt135; 79 | default: goto DONE; 80 | } 81 | } 82 | 83 | opt60_1: { 84 | opt = cursor_advance(*cursor, sizeof(*opt)); 85 | u8 ext_len = opt->ext_len << 3; 86 | cursor_advance(*cursor, ext_len); 87 | switch (opt->next_header) { 88 | case 43: goto opt43; 89 | case 44: goto opt44; 90 | //case 51: goto opt51; 91 | //case 50: goto opt50; 92 | //case 135: goto opt135; 93 | default: goto DONE; 94 | } 95 | } 96 | 97 | 98 | opt43: { 99 | opt = cursor_advance(*cursor, sizeof(*opt)); 100 | u8 ext_len = opt->ext_len << 3; 101 | cursor_advance(*cursor, ext_len); 102 | switch (opt->next_header) { 103 | case 44: goto opt44; 104 | //case 51: goto opt51; 105 | //case 50: goto opt50; 106 | case 60: goto opt60_2; 107 | //case 135: goto opt135; 108 | default: goto DONE; 109 | } 110 | } 111 | 112 | opt44: { 113 | opt = cursor_advance(*cursor, sizeof(*opt)); 114 | switch (opt->next_header) { 115 | //case 51: goto opt51; 116 | //case 50: goto opt50; 117 | case 60: goto opt60_2; 118 | //case 135: goto opt135; 119 | default: goto DONE; 120 | } 121 | } 122 | 123 | #if 0 124 | opt51: { 125 | opt = cursor_advance(*cursor, sizeof(*opt)); 126 | u8 ext_len = opt->ext_len << 2; 127 | cursor_advance(*cursor, ext_len); 128 | switch (opt->next_header) { 129 | case 50: goto opt50; 130 | //case 135: goto opt135; 131 | case 60: goto opt60_2; 132 | default: goto DONE; 133 | } 134 | } 135 | 136 | opt50: { 137 | opt = cursor_advance(*cursor, sizeof(*opt)); 138 | u8 ext_len = opt->ext_len << 2; 139 | cursor_advance(*cursor, ext_len); 140 | switch (opt->next_header) { 141 | //case 135: goto opt135; 142 | case 60: goto opt60_2; 143 | default: goto DONE; 144 | } 145 | } 146 | #endif 147 | 148 | opt60_2: { 149 | opt = cursor_advance(*cursor, sizeof(*opt)); 150 | u8 ext_len = opt->ext_len << 3; 151 | cursor_advance(*cursor, ext_len); 152 | switch (opt->next_header) { 153 | //case 135: goto opt135; 154 | default: goto DONE; 155 | } 156 | } 157 | 158 | #if 0 159 | opt135: { 160 | opt = cursor_advance(*cursor, sizeof(*opt)); 161 | u8 ext_len = opt->ext_len << 3; 162 | cursor_advance(*cursor, ext_len); 163 | switch (opt->next_header) { 164 | case 60: goto opt60_2; 165 | //case 135: goto opt135; 166 | default: goto DONE; 167 | } 168 | } 169 | #endif 170 | 171 | DONE: 172 | return opt->next_header; 173 | } 174 | 175 | static int handle_any(void *skb, struct metadata *md, enum direction direction) { 176 | u8 *cursor = 0; 177 | int ret = RX_OK; 178 | 179 | struct Counter *val = NULL; 180 | struct Ip4Key ip4key = {}; 181 | struct Ip6Key ip6key = {}; 182 | bool v4valid = false; 183 | bool v6valid = false; 184 | 185 | ethernet: { 186 | struct ethernet_t *ethernet = cursor_advance(cursor, sizeof(*ethernet)); 187 | u16 ethertype = ethernet->type; 188 | switch (ethertype) { 189 | case ETH_P_IP: goto ip; 190 | case ETH_P_IPV6: goto ip6; 191 | default: goto DONE; 192 | } 193 | } 194 | 195 | ip: { 196 | struct ip_t *ip = cursor_advance(cursor, sizeof(*ip)); 197 | if (direction == DIRECTION_OUT) { 198 | ip4key.src = ip->src; 199 | ip4key.dst = ip->dst; 200 | } else { 201 | ip4key.dst = ip->src; 202 | ip4key.src = ip->dst; 203 | } 204 | v4valid = true; 205 | u8 hlen_bytes = ip->hlen << 2; 206 | cursor_advance(cursor, hlen_bytes - sizeof(*ip)); 207 | u8 nextp = ip->nextp; 208 | switch (nextp) { 209 | case 6: goto tcp; 210 | case 17: goto udp; 211 | default: goto DONE; 212 | } 213 | } 214 | 215 | ip6: { 216 | struct ip6_t *ip6 = cursor_advance(cursor, sizeof(*ip6)); 217 | if (direction == DIRECTION_OUT) { 218 | ip6key.src_lo = ip6->src_lo; 219 | ip6key.src_hi = ip6->src_hi; 220 | ip6key.dst_lo = ip6->dst_lo; 221 | ip6key.dst_hi = ip6->dst_hi; 222 | } else { 223 | ip6key.dst_lo = ip6->src_lo; 224 | ip6key.dst_hi = ip6->src_hi; 225 | ip6key.src_lo = ip6->dst_lo; 226 | ip6key.src_hi = ip6->dst_hi; 227 | } 228 | v6valid = true; 229 | u8 next_header = pop_ipv6_headers(skb, &cursor, ip6->next_header); 230 | switch (next_header) { 231 | case 6: goto tcp; 232 | case 17: goto udp; 233 | default: goto DONE; 234 | } 235 | } 236 | 237 | udp: { 238 | struct udp_t *udp = cursor_advance(cursor, sizeof(*udp)); 239 | if ((udp->dport == 53 && direction == DIRECTION_IN) || 240 | (udp->sport == 53 && direction == DIRECTION_OUT)) { 241 | goto LOOKUP; 242 | } 243 | goto DONE; 244 | } 245 | 246 | tcp: { 247 | struct tcp_t *tcp = cursor_advance(cursor, sizeof(*tcp)); 248 | if ((tcp->dst_port == 53 && direction == DIRECTION_IN) || 249 | (tcp->src_port == 53 && direction == DIRECTION_OUT)) { 250 | goto LOOKUP; 251 | } 252 | goto DONE; 253 | } 254 | 255 | LOOKUP: { 256 | struct Counter zero = {}; 257 | struct Counter *counter = NULL; 258 | if (v4valid) { 259 | counter = ip4_flows.lookup_or_init(&ip4key, &zero); 260 | } else if (v6valid) { 261 | counter = ip6_flows.lookup_or_init(&ip6key, &zero); 262 | } else { 263 | goto DONE; 264 | } 265 | if (direction == DIRECTION_OUT) { 266 | counter->tx_pkts++; 267 | counter->tx_bytes += md->pktlen; 268 | } else { 269 | counter->rx_pkts++; 270 | counter->rx_bytes += md->pktlen; 271 | } 272 | } 273 | 274 | DONE: 275 | return ret; 276 | } 277 | 278 | static int handle_rx(void *skb, struct metadata *md) { 279 | if (md->in_ifc == 1) 280 | return handle_any(skb, md, DIRECTION_IN); 281 | else if (md->in_ifc == 2) 282 | return handle_any(skb, md, DIRECTION_OUT); 283 | return RX_OK; 284 | } 285 | ` 286 | 287 | type Dataplane struct { 288 | client *http.Client 289 | baseUrl string 290 | id string 291 | } 292 | 293 | func NewDataplane() *Dataplane { 294 | client := &http.Client{} 295 | d := &Dataplane{ 296 | client: client, 297 | } 298 | 299 | return d 300 | } 301 | 302 | func (d *Dataplane) postObject(url string, requestObj interface{}, responseObj interface{}) (err error) { 303 | b, err := json.Marshal(requestObj) 304 | if err != nil { 305 | return 306 | } 307 | resp, err := d.client.Post(d.baseUrl+url, "application/json", bytes.NewReader(b)) 308 | if err != nil { 309 | return 310 | } 311 | defer resp.Body.Close() 312 | if resp.StatusCode != http.StatusOK { 313 | var body []byte 314 | if body, err = ioutil.ReadAll(resp.Body); err != nil { 315 | Error.Print(string(body)) 316 | } 317 | return fmt.Errorf("module server returned %s", resp.Status) 318 | } 319 | if responseObj != nil { 320 | err = json.NewDecoder(resp.Body).Decode(responseObj) 321 | } 322 | return 323 | } 324 | 325 | type moduleEntry struct { 326 | Id string `json:"id"` 327 | ModuleType string `json:"module_type"` 328 | DisplayName string `json:"display_name"` 329 | Perm string `json:"permissions"` 330 | Config map[string]interface{} `json:"config"` 331 | } 332 | 333 | func (d *Dataplane) Init(baseUrl string) error { 334 | d.baseUrl = baseUrl 335 | req := map[string]interface{}{ 336 | "module_type": "bpf", 337 | "display_name": "dnsmon", 338 | "config": map[string]interface{}{ 339 | "code": filterImplC, 340 | }, 341 | } 342 | var module moduleEntry 343 | err := d.postObject("/modules/", req, &module) 344 | if err != nil { 345 | return err 346 | } 347 | d.id = module.Id 348 | return nil 349 | } 350 | 351 | func (d *Dataplane) Id() string { 352 | return d.id 353 | } 354 | 355 | func (d *Dataplane) Close() error { 356 | return nil 357 | } 358 | -------------------------------------------------------------------------------- /policy/fakes/dataplane.go: -------------------------------------------------------------------------------- 1 | // This file was generated by counterfeiter 2 | package fakes 3 | 4 | import "sync" 5 | 6 | type Dataplane struct { 7 | AddEndpointStub func(ip string, epg string, wireid string) error 8 | addEndpointMutex sync.RWMutex 9 | addEndpointArgsForCall []struct { 10 | ip string 11 | epg string 12 | wireid string 13 | } 14 | addEndpointReturns struct { 15 | result1 error 16 | } 17 | DeleteEndpointStub func(ip string) error 18 | deleteEndpointMutex sync.RWMutex 19 | deleteEndpointArgsForCall []struct { 20 | ip string 21 | } 22 | deleteEndpointReturns struct { 23 | result1 error 24 | } 25 | AddPolicyStub func(sepgId, sourcePort, depgId, destPort, protocol, action string) (err error) 26 | addPolicyMutex sync.RWMutex 27 | addPolicyArgsForCall []struct { 28 | sepgId string 29 | sourcePort string 30 | depgId string 31 | destPort string 32 | protocol string 33 | action string 34 | } 35 | addPolicyReturns struct { 36 | result1 error 37 | } 38 | DeletePolicyStub func(sepgId, sourcePort, depgId, destPort, protocol string) error 39 | deletePolicyMutex sync.RWMutex 40 | deletePolicyArgsForCall []struct { 41 | sepgId string 42 | sourcePort string 43 | depgId string 44 | destPort string 45 | protocol string 46 | } 47 | deletePolicyReturns struct { 48 | result1 error 49 | } 50 | InitStub func(Url string) error 51 | initMutex sync.RWMutex 52 | initArgsForCall []struct { 53 | Url string 54 | } 55 | initReturns struct { 56 | result1 error 57 | } 58 | IdStub func() string 59 | idMutex sync.RWMutex 60 | idArgsForCall []struct{} 61 | idReturns struct { 62 | result1 string 63 | } 64 | invocations map[string][][]interface{} 65 | invocationsMutex sync.RWMutex 66 | } 67 | 68 | func (fake *Dataplane) AddEndpoint(ip string, epg string, wireid string) error { 69 | fake.addEndpointMutex.Lock() 70 | fake.addEndpointArgsForCall = append(fake.addEndpointArgsForCall, struct { 71 | ip string 72 | epg string 73 | wireid string 74 | }{ip, epg, wireid}) 75 | fake.recordInvocation("AddEndpoint", []interface{}{ip, epg, wireid}) 76 | fake.addEndpointMutex.Unlock() 77 | if fake.AddEndpointStub != nil { 78 | return fake.AddEndpointStub(ip, epg, wireid) 79 | } else { 80 | return fake.addEndpointReturns.result1 81 | } 82 | } 83 | 84 | func (fake *Dataplane) AddEndpointCallCount() int { 85 | fake.addEndpointMutex.RLock() 86 | defer fake.addEndpointMutex.RUnlock() 87 | return len(fake.addEndpointArgsForCall) 88 | } 89 | 90 | func (fake *Dataplane) AddEndpointArgsForCall(i int) (string, string, string) { 91 | fake.addEndpointMutex.RLock() 92 | defer fake.addEndpointMutex.RUnlock() 93 | return fake.addEndpointArgsForCall[i].ip, fake.addEndpointArgsForCall[i].epg, fake.addEndpointArgsForCall[i].wireid 94 | } 95 | 96 | func (fake *Dataplane) AddEndpointReturns(result1 error) { 97 | fake.AddEndpointStub = nil 98 | fake.addEndpointReturns = struct { 99 | result1 error 100 | }{result1} 101 | } 102 | 103 | func (fake *Dataplane) DeleteEndpoint(ip string) error { 104 | fake.deleteEndpointMutex.Lock() 105 | fake.deleteEndpointArgsForCall = append(fake.deleteEndpointArgsForCall, struct { 106 | ip string 107 | }{ip}) 108 | fake.recordInvocation("DeleteEndpoint", []interface{}{ip}) 109 | fake.deleteEndpointMutex.Unlock() 110 | if fake.DeleteEndpointStub != nil { 111 | return fake.DeleteEndpointStub(ip) 112 | } else { 113 | return fake.deleteEndpointReturns.result1 114 | } 115 | } 116 | 117 | func (fake *Dataplane) DeleteEndpointCallCount() int { 118 | fake.deleteEndpointMutex.RLock() 119 | defer fake.deleteEndpointMutex.RUnlock() 120 | return len(fake.deleteEndpointArgsForCall) 121 | } 122 | 123 | func (fake *Dataplane) DeleteEndpointArgsForCall(i int) string { 124 | fake.deleteEndpointMutex.RLock() 125 | defer fake.deleteEndpointMutex.RUnlock() 126 | return fake.deleteEndpointArgsForCall[i].ip 127 | } 128 | 129 | func (fake *Dataplane) DeleteEndpointReturns(result1 error) { 130 | fake.DeleteEndpointStub = nil 131 | fake.deleteEndpointReturns = struct { 132 | result1 error 133 | }{result1} 134 | } 135 | 136 | func (fake *Dataplane) AddPolicy(sepgId string, sourcePort string, depgId string, destPort string, protocol string, action string) (err error) { 137 | fake.addPolicyMutex.Lock() 138 | fake.addPolicyArgsForCall = append(fake.addPolicyArgsForCall, struct { 139 | sepgId string 140 | sourcePort string 141 | depgId string 142 | destPort string 143 | protocol string 144 | action string 145 | }{sepgId, sourcePort, depgId, destPort, protocol, action}) 146 | fake.recordInvocation("AddPolicy", []interface{}{sepgId, sourcePort, depgId, destPort, protocol, action}) 147 | fake.addPolicyMutex.Unlock() 148 | if fake.AddPolicyStub != nil { 149 | return fake.AddPolicyStub(sepgId, sourcePort, depgId, destPort, protocol, action) 150 | } else { 151 | return fake.addPolicyReturns.result1 152 | } 153 | } 154 | 155 | func (fake *Dataplane) AddPolicyCallCount() int { 156 | fake.addPolicyMutex.RLock() 157 | defer fake.addPolicyMutex.RUnlock() 158 | return len(fake.addPolicyArgsForCall) 159 | } 160 | 161 | func (fake *Dataplane) AddPolicyArgsForCall(i int) (string, string, string, string, string, string) { 162 | fake.addPolicyMutex.RLock() 163 | defer fake.addPolicyMutex.RUnlock() 164 | return fake.addPolicyArgsForCall[i].sepgId, fake.addPolicyArgsForCall[i].sourcePort, fake.addPolicyArgsForCall[i].depgId, fake.addPolicyArgsForCall[i].destPort, fake.addPolicyArgsForCall[i].protocol, fake.addPolicyArgsForCall[i].action 165 | } 166 | 167 | func (fake *Dataplane) AddPolicyReturns(result1 error) { 168 | fake.AddPolicyStub = nil 169 | fake.addPolicyReturns = struct { 170 | result1 error 171 | }{result1} 172 | } 173 | 174 | func (fake *Dataplane) DeletePolicy(sepgId string, sourcePort string, depgId string, destPort string, protocol string) error { 175 | fake.deletePolicyMutex.Lock() 176 | fake.deletePolicyArgsForCall = append(fake.deletePolicyArgsForCall, struct { 177 | sepgId string 178 | sourcePort string 179 | depgId string 180 | destPort string 181 | protocol string 182 | }{sepgId, sourcePort, depgId, destPort, protocol}) 183 | fake.recordInvocation("DeletePolicy", []interface{}{sepgId, sourcePort, depgId, destPort, protocol}) 184 | fake.deletePolicyMutex.Unlock() 185 | if fake.DeletePolicyStub != nil { 186 | return fake.DeletePolicyStub(sepgId, sourcePort, depgId, destPort, protocol) 187 | } else { 188 | return fake.deletePolicyReturns.result1 189 | } 190 | } 191 | 192 | func (fake *Dataplane) DeletePolicyCallCount() int { 193 | fake.deletePolicyMutex.RLock() 194 | defer fake.deletePolicyMutex.RUnlock() 195 | return len(fake.deletePolicyArgsForCall) 196 | } 197 | 198 | func (fake *Dataplane) DeletePolicyArgsForCall(i int) (string, string, string, string, string) { 199 | fake.deletePolicyMutex.RLock() 200 | defer fake.deletePolicyMutex.RUnlock() 201 | return fake.deletePolicyArgsForCall[i].sepgId, fake.deletePolicyArgsForCall[i].sourcePort, fake.deletePolicyArgsForCall[i].depgId, fake.deletePolicyArgsForCall[i].destPort, fake.deletePolicyArgsForCall[i].protocol 202 | } 203 | 204 | func (fake *Dataplane) DeletePolicyReturns(result1 error) { 205 | fake.DeletePolicyStub = nil 206 | fake.deletePolicyReturns = struct { 207 | result1 error 208 | }{result1} 209 | } 210 | 211 | func (fake *Dataplane) Init(Url string) error { 212 | fake.initMutex.Lock() 213 | fake.initArgsForCall = append(fake.initArgsForCall, struct { 214 | Url string 215 | }{Url}) 216 | fake.recordInvocation("Init", []interface{}{Url}) 217 | fake.initMutex.Unlock() 218 | if fake.InitStub != nil { 219 | return fake.InitStub(Url) 220 | } else { 221 | return fake.initReturns.result1 222 | } 223 | } 224 | 225 | func (fake *Dataplane) InitCallCount() int { 226 | fake.initMutex.RLock() 227 | defer fake.initMutex.RUnlock() 228 | return len(fake.initArgsForCall) 229 | } 230 | 231 | func (fake *Dataplane) InitArgsForCall(i int) string { 232 | fake.initMutex.RLock() 233 | defer fake.initMutex.RUnlock() 234 | return fake.initArgsForCall[i].Url 235 | } 236 | 237 | func (fake *Dataplane) InitReturns(result1 error) { 238 | fake.InitStub = nil 239 | fake.initReturns = struct { 240 | result1 error 241 | }{result1} 242 | } 243 | 244 | func (fake *Dataplane) Id() string { 245 | fake.idMutex.Lock() 246 | fake.idArgsForCall = append(fake.idArgsForCall, struct{}{}) 247 | fake.recordInvocation("Id", []interface{}{}) 248 | fake.idMutex.Unlock() 249 | if fake.IdStub != nil { 250 | return fake.IdStub() 251 | } else { 252 | return fake.idReturns.result1 253 | } 254 | } 255 | 256 | func (fake *Dataplane) IdCallCount() int { 257 | fake.idMutex.RLock() 258 | defer fake.idMutex.RUnlock() 259 | return len(fake.idArgsForCall) 260 | } 261 | 262 | func (fake *Dataplane) IdReturns(result1 string) { 263 | fake.IdStub = nil 264 | fake.idReturns = struct { 265 | result1 string 266 | }{result1} 267 | } 268 | 269 | func (fake *Dataplane) Invocations() map[string][][]interface{} { 270 | fake.invocationsMutex.RLock() 271 | defer fake.invocationsMutex.RUnlock() 272 | fake.addEndpointMutex.RLock() 273 | defer fake.addEndpointMutex.RUnlock() 274 | fake.deleteEndpointMutex.RLock() 275 | defer fake.deleteEndpointMutex.RUnlock() 276 | fake.addPolicyMutex.RLock() 277 | defer fake.addPolicyMutex.RUnlock() 278 | fake.deletePolicyMutex.RLock() 279 | defer fake.deletePolicyMutex.RUnlock() 280 | fake.initMutex.RLock() 281 | defer fake.initMutex.RUnlock() 282 | fake.idMutex.RLock() 283 | defer fake.idMutex.RUnlock() 284 | return fake.invocations 285 | } 286 | 287 | func (fake *Dataplane) recordInvocation(key string, args []interface{}) { 288 | fake.invocationsMutex.Lock() 289 | defer fake.invocationsMutex.Unlock() 290 | if fake.invocations == nil { 291 | fake.invocations = map[string][][]interface{}{} 292 | } 293 | if fake.invocations[key] == nil { 294 | fake.invocations[key] = [][]interface{}{} 295 | } 296 | fake.invocations[key] = append(fake.invocations[key], args) 297 | } 298 | -------------------------------------------------------------------------------- /policy/policy-ctl/policy-ctl.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/hokaccha/go-prettyjson" 8 | "github.com/iovisor/iomodules/policy/client" 9 | "github.com/iovisor/iomodules/policy/models" 10 | "github.com/urfave/cli" 11 | ) 12 | 13 | func main() { 14 | app := cli.NewApp() 15 | app.Name = "policy-cli" 16 | app.EnableBashCompletion = true 17 | app.Usage = "cli to configure policy iomodule" 18 | app.Action = func(c *cli.Context) error { 19 | fmt.Println("IOVisor -- CLI to configure policy iomodule") 20 | return nil 21 | } 22 | app.Commands = []cli.Command{ 23 | { 24 | Name: "endpoint-group", 25 | Usage: "endpoint group commands", 26 | Subcommands: []cli.Command{ 27 | { 28 | Name: "create", 29 | Usage: "create an endpoint group", 30 | Action: func(c *cli.Context) error { 31 | var epg models.EndpointGroup 32 | epg = models.EndpointGroup{ 33 | Epg: c.String("endpoint-group-name"), 34 | WireId: c.String("wire-id"), 35 | } 36 | p := client.NewClient("http://localhost:5001") 37 | err := p.AddEndpointGroup(&epg) 38 | if err != nil { 39 | return err 40 | } 41 | s, _ := prettyjson.Marshal(epg) 42 | fmt.Printf("%+v\n", string(s)) 43 | return nil 44 | }, 45 | Flags: []cli.Flag{ 46 | cli.StringFlag{ 47 | Name: "endpoint-group-name", 48 | Value: "", 49 | Usage: "name identifying endpoint group", 50 | }, 51 | cli.StringFlag{ 52 | Name: "wire-id", 53 | Value: "", 54 | Usage: "identifier used on the wire", 55 | }, 56 | }, 57 | }, 58 | { 59 | Name: "delete", 60 | Usage: "delete an endpoint group", 61 | Action: func(c *cli.Context) error { 62 | p := client.NewClient("http://localhost:5001") 63 | err := p.DeleteEndpointGroup(c.String("endpoint-group-id")) 64 | if err != nil { 65 | fmt.Println(err) 66 | } 67 | return nil 68 | }, 69 | Flags: []cli.Flag{ 70 | cli.StringFlag{ 71 | Name: "endpoint-group-id", 72 | Value: "", 73 | Usage: "uuid of endpoint group", 74 | }, 75 | }, 76 | }, 77 | { 78 | Name: "show", 79 | Usage: "show an endpoint group", 80 | Action: func(c *cli.Context) error { 81 | p := client.NewClient("http://localhost:5001") 82 | epg, err := p.GetEndpointGroup(c.String("endpoint-group-id")) 83 | if err != nil { 84 | fmt.Println(err) 85 | } 86 | s, _ := prettyjson.Marshal(epg) 87 | fmt.Printf("%+v\n", string(s)) 88 | return nil 89 | }, 90 | Flags: []cli.Flag{ 91 | cli.StringFlag{ 92 | Name: "endpoint-group-id", 93 | Value: "", 94 | Usage: "uuid of endpoint group", 95 | }, 96 | }, 97 | }, 98 | { 99 | Name: "list", 100 | Usage: "list endpoint groups", 101 | Action: func(c *cli.Context) error { 102 | p := client.NewClient("http://localhost:5001") 103 | epgs, err := p.EndpointGroups() 104 | if err != nil { 105 | fmt.Println(err) 106 | } 107 | for _, epg := range epgs { 108 | s, _ := prettyjson.Marshal(epg) 109 | fmt.Printf("%+v\n", string(s)) 110 | } 111 | return nil 112 | }, 113 | }, 114 | }, 115 | }, 116 | { 117 | Name: "endpoint", 118 | Usage: "endpoint commands", 119 | Subcommands: []cli.Command{ 120 | { 121 | Name: "create", 122 | Usage: "create an endpoint", 123 | Action: func(c *cli.Context) error { 124 | var id string 125 | p := client.NewClient("http://localhost:5001") 126 | epgs, _ := p.EndpointGroups() 127 | for _, epg := range epgs { 128 | if epg.Epg == c.String("endpoint-group-name") { 129 | id = epg.Id 130 | } 131 | } 132 | ep := models.EndpointEntry{ 133 | Ip: c.String("ipaddress"), 134 | EpgId: id, 135 | } 136 | err := p.AddEndpoint(&ep) 137 | if err != nil { 138 | fmt.Println(err) 139 | return err 140 | } 141 | s, _ := prettyjson.Marshal(ep) 142 | fmt.Printf("%+v\n", string(s)) 143 | return err 144 | }, 145 | Flags: []cli.Flag{ 146 | cli.StringFlag{ 147 | Name: "endpoint-group-name", 148 | Value: "", 149 | Usage: "name of endpoint group", 150 | }, 151 | cli.StringFlag{ 152 | Name: "ipaddress", 153 | Value: "", 154 | Usage: "ip address identifying endpoint", 155 | }, 156 | }, 157 | }, 158 | { 159 | Name: "delete", 160 | Usage: "delete an endpoint", 161 | Action: func(c *cli.Context) error { 162 | fmt.Println("removed task template: ", c.Args().First()) 163 | p := client.NewClient("http://localhost:5001") 164 | err := p.DeleteEndpoint(c.String("endpoint-id")) 165 | if err != nil { 166 | fmt.Println(err) 167 | } 168 | return nil 169 | }, 170 | Flags: []cli.Flag{ 171 | cli.StringFlag{ 172 | Name: "endpoint-id", 173 | Value: "", 174 | Usage: "uuid of endpoint", 175 | }, 176 | }, 177 | }, 178 | { 179 | Name: "show", 180 | Usage: "show an endpoint", 181 | Action: func(c *cli.Context) error { 182 | p := client.NewClient("http://localhost:5001") 183 | ep, err := p.GetEndpoint(c.String("endpoint-id")) 184 | if err != nil { 185 | fmt.Println(err) 186 | } 187 | s, _ := prettyjson.Marshal(ep) 188 | fmt.Printf("%+v\n", string(s)) 189 | return nil 190 | }, 191 | Flags: []cli.Flag{ 192 | cli.StringFlag{ 193 | Name: "endpoint-id", 194 | Value: "", 195 | Usage: "uuid of endpoint", 196 | }, 197 | }, 198 | }, 199 | { 200 | Name: "list", 201 | Usage: "list endpoints", 202 | Action: func(c *cli.Context) error { 203 | p := client.NewClient("http://localhost:5001") 204 | eps, err := p.Endpoints() 205 | if err != nil { 206 | fmt.Println(err) 207 | return err 208 | } 209 | for _, e := range eps { 210 | s, _ := prettyjson.Marshal(e) 211 | fmt.Printf("%+v\n", string(s)) 212 | } 213 | return nil 214 | }, 215 | }, 216 | }, 217 | }, 218 | { 219 | Name: "policy-rule", 220 | Usage: "policy rule commands", 221 | Subcommands: []cli.Command{ 222 | { 223 | Name: "create", 224 | Usage: "create a policy rule", 225 | Action: func(c *cli.Context) error { 226 | var destid, srcid string 227 | p := client.NewClient("http://localhost:5001") 228 | epgs, _ := p.EndpointGroups() 229 | for _, epg := range epgs { 230 | if epg.Epg == c.String("dest-endpoint-group") { 231 | destid = epg.Id 232 | } 233 | if epg.Epg == c.String("source-endpoint-group") { 234 | srcid = epg.Id 235 | } 236 | } 237 | policy := models.Policy{ 238 | SourceEPG: srcid, 239 | SourcePort: c.String("source-port"), 240 | DestEPG: destid, 241 | DestPort: c.String("dest-port"), 242 | Protocol: c.String("protocol"), 243 | Action: c.String("action"), 244 | } 245 | err := p.AddPolicy(&policy) 246 | if err != nil { 247 | fmt.Println(err) 248 | return err 249 | } 250 | s, _ := prettyjson.Marshal(policy) 251 | fmt.Printf("%+v\n", string(s)) 252 | return nil 253 | }, 254 | Flags: []cli.Flag{ 255 | cli.StringFlag{ 256 | Name: "source-endpoint-group", 257 | Value: "", 258 | Usage: "name of source endpoint group", 259 | }, 260 | cli.StringFlag{ 261 | Name: "source-port", 262 | Value: "0", 263 | Usage: "source port", 264 | }, 265 | cli.StringFlag{ 266 | Name: "dest-endpoint-group", 267 | Value: "", 268 | Usage: "name of destination endpoint group", 269 | }, 270 | cli.StringFlag{ 271 | Name: "dest-port", 272 | Value: "0", 273 | Usage: "destination port", 274 | }, 275 | cli.StringFlag{ 276 | Name: "protocol", 277 | Value: "17", 278 | Usage: "l4 protocol", 279 | }, 280 | cli.StringFlag{ 281 | Name: "action", 282 | Value: "allow", 283 | Usage: "policy rule action", 284 | }, 285 | }, 286 | }, 287 | { 288 | Name: "delete", 289 | Usage: "delete a policy rule", 290 | Action: func(c *cli.Context) error { 291 | p := client.NewClient("http://localhost:5001") 292 | err := p.DeletePolicy(c.String("policy-rule-id")) 293 | if err != nil { 294 | fmt.Println(err) 295 | return err 296 | } 297 | return nil 298 | }, 299 | Flags: []cli.Flag{ 300 | cli.StringFlag{ 301 | Name: "policy-rule-id", 302 | Value: "", 303 | Usage: "uuid of policy rule", 304 | }, 305 | }, 306 | }, 307 | { 308 | Name: "show", 309 | Usage: "show a policy rule", 310 | Action: func(c *cli.Context) error { 311 | p := client.NewClient("http://localhost:5001") 312 | policy, err := p.GetPolicy(c.String("policy-rule-id")) 313 | if err != nil { 314 | fmt.Println(err) 315 | return err 316 | } 317 | s, _ := prettyjson.Marshal(policy) 318 | fmt.Printf("%+v\n", string(s)) 319 | return nil 320 | }, 321 | Flags: []cli.Flag{ 322 | cli.StringFlag{ 323 | Name: "policy-rule-id", 324 | Value: "", 325 | Usage: "uuid of policy rule", 326 | }, 327 | }, 328 | }, 329 | { 330 | Name: "list", 331 | Usage: "list policy rule", 332 | Action: func(c *cli.Context) error { 333 | p := client.NewClient("http://localhost:5001") 334 | policies, err := p.Policies() 335 | if err != nil { 336 | fmt.Println(err) 337 | return err 338 | } 339 | for _, pl := range policies { 340 | s, _ := prettyjson.Marshal(pl) 341 | fmt.Printf("%+v\n", string(s)) 342 | } 343 | return nil 344 | }, 345 | }, 346 | }, 347 | }, 348 | } 349 | app.Run(os.Args) 350 | } 351 | -------------------------------------------------------------------------------- /gbp/dataplane.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 PLUMgrid 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gbp 16 | 17 | import ( 18 | "bytes" 19 | "encoding/binary" 20 | "encoding/json" 21 | "fmt" 22 | "io/ioutil" 23 | "net" 24 | "net/http" 25 | 26 | "github.com/jmoiron/sqlx" 27 | _ "github.com/mattn/go-sqlite3" 28 | ) 29 | 30 | var filterImplC string = ` 31 | BPF_TABLE("hash", u32, u32, endpoints, 1024); 32 | 33 | struct match { 34 | u32 src_tag; 35 | u32 dst_tag; 36 | u16 sport; 37 | u16 dport; 38 | u8 proto; 39 | u8 direction; 40 | u8 pad[2]; 41 | }; 42 | BPF_TABLE("hash", struct match, int, rules, 1024); 43 | 44 | static int handle_egress(void *skb, struct metadata *md) { 45 | u8 *cursor = 0; 46 | struct match m = {}; 47 | int ret = RX_DROP; 48 | u32 dst_tag = 0, src_tag = 0; 49 | if (md->data[0].type == 1) 50 | src_tag = md->data[0].value; 51 | 52 | ethernet: { 53 | struct ethernet_t *ethernet = cursor_advance(cursor, sizeof(*ethernet)); 54 | u16 ethertype = ethernet->type; 55 | switch (ethertype) { 56 | case ETH_P_IP: goto ip; 57 | case ETH_P_IPV6: goto ip6; 58 | case ETH_P_ARP: goto arp; 59 | default: goto DONE; 60 | } 61 | } 62 | 63 | ip: { 64 | struct ip_t *ip = cursor_advance(cursor, sizeof(*ip)); 65 | u32 dst_ip = ip->dst; 66 | m.proto = ip->nextp; 67 | 68 | u32 *tag = endpoints.lookup(&dst_ip); 69 | if (!tag) 70 | goto DONE; 71 | dst_tag = *tag; 72 | switch (m.proto) { 73 | case 1: goto icmp; 74 | case 6: goto tcp; 75 | case 17: goto udp; 76 | default: goto DONE; 77 | } 78 | } 79 | 80 | ip6: { 81 | goto DONE; 82 | } 83 | 84 | icmp: { 85 | struct icmp_t *icmp = cursor_advance(cursor, sizeof(*icmp)); 86 | goto EOP; 87 | } 88 | 89 | tcp: { 90 | struct tcp_t *tcp = cursor_advance(cursor, sizeof(*tcp)); 91 | m.dport = tcp->dst_port; 92 | m.sport = tcp->src_port; 93 | goto EOP; 94 | } 95 | 96 | udp: { 97 | struct udp_t *udp = cursor_advance(cursor, sizeof(*udp)); 98 | m.dport = udp->dport; 99 | m.sport = udp->sport; 100 | goto EOP; 101 | } 102 | 103 | arp: { 104 | return RX_OK; 105 | } 106 | 107 | EOP: ; 108 | int *result; 109 | struct match m1 = {src_tag, dst_tag, m.sport, m.dport, m.proto, 0}; 110 | result = rules.lookup(&m1); 111 | if (result) { 112 | ret = *result; 113 | goto DONE; 114 | } 115 | struct match m2 = {src_tag, dst_tag, m.sport, 0, m.proto, 1/*2*/}; 116 | result = rules.lookup(&m2); 117 | if (result) { 118 | ret = *result; 119 | goto DONE; 120 | } 121 | struct match m3 = {src_tag, dst_tag, 0, m.dport, m.proto, 1}; 122 | result = rules.lookup(&m3); 123 | if (result) { 124 | ret = *result; 125 | goto DONE; 126 | } 127 | 128 | DONE: 129 | return ret; 130 | } 131 | 132 | static int handle_ingress(void *skb, struct metadata *md) { 133 | u8 *cursor = 0; 134 | u32 src_tag = 0; 135 | int ret = RX_OK; 136 | 137 | ethernet: { 138 | struct ethernet_t *ethernet = cursor_advance(cursor, sizeof(*ethernet)); 139 | u16 ethertype = ethernet->type; 140 | switch (ethertype) { 141 | case ETH_P_IP: goto ip; 142 | case ETH_P_IPV6: goto ip6; 143 | case ETH_P_ARP: goto arp; 144 | default: goto DONE; 145 | } 146 | } 147 | 148 | ip: { 149 | struct ip_t *ip = cursor_advance(cursor, sizeof(*ip)); 150 | u32 src_ip = ip->src; 151 | 152 | u32 *tag = endpoints.lookup(&src_ip); 153 | if (!tag) 154 | goto DONE; 155 | src_tag = *tag; 156 | goto DONE; 157 | } 158 | 159 | ip6: { 160 | goto DONE; 161 | } 162 | 163 | arp: { 164 | goto DONE; 165 | } 166 | 167 | DONE: 168 | if (src_tag != 0) { 169 | md->data[0].type = 1; 170 | md->data[0].value = src_tag; 171 | } 172 | 173 | return ret; 174 | } 175 | 176 | static int handle_rx(void *skb, struct metadata *md) { 177 | if (md->in_ifc == 1) 178 | return handle_ingress(skb, md); 179 | else if (md->in_ifc == 2) 180 | return handle_egress(skb, md); 181 | return RX_OK; 182 | } 183 | ` 184 | 185 | type moduleEntry struct { 186 | Id string `json:"id"` 187 | ModuleType string `json:"module_type"` 188 | DisplayName string `json:"display_name"` 189 | Perm string `json:"permissions"` 190 | Config map[string]interface{} `json:"config"` 191 | } 192 | 193 | type tableEntry struct { 194 | Key string `json:"key"` 195 | Value string `json:"value"` 196 | } 197 | 198 | type Dataplane struct { 199 | client *http.Client 200 | baseUrl string 201 | id string 202 | db *sqlx.DB 203 | } 204 | 205 | func NewDataplane(sqlUrl string) *Dataplane { 206 | client := &http.Client{} 207 | d := &Dataplane{ 208 | client: client, 209 | db: sqlx.MustConnect("sqlite3", sqlUrl), 210 | } 211 | d.db.Exec(` 212 | CREATE TABLE endpoints ( 213 | ip CHAR(50) PRIMARY KEY NOT NULL, 214 | tenant CHAR(40) NOT NULL, 215 | epg CHAR(40) NOT NULL 216 | ); 217 | `) 218 | return d 219 | } 220 | 221 | func (d *Dataplane) postObject(url string, requestObj interface{}, responseObj interface{}) (err error) { 222 | b, err := json.Marshal(requestObj) 223 | if err != nil { 224 | return 225 | } 226 | resp, err := d.client.Post(d.baseUrl+url, "application/json", bytes.NewReader(b)) 227 | if err != nil { 228 | return 229 | } 230 | defer resp.Body.Close() 231 | if resp.StatusCode != http.StatusOK { 232 | var body []byte 233 | if body, err = ioutil.ReadAll(resp.Body); err != nil { 234 | Error.Print(string(body)) 235 | } 236 | return fmt.Errorf("module server returned %s", resp.Status) 237 | } 238 | if responseObj != nil { 239 | err = json.NewDecoder(resp.Body).Decode(responseObj) 240 | } 241 | return 242 | } 243 | 244 | func (d *Dataplane) Init(baseUrl string) error { 245 | d.baseUrl = baseUrl 246 | req := map[string]interface{}{ 247 | "module_type": "bpf", 248 | "display_name": "gbp", 249 | "config": map[string]interface{}{ 250 | "code": filterImplC, 251 | }, 252 | } 253 | var module moduleEntry 254 | err := d.postObject("/modules/", req, &module) 255 | if err != nil { 256 | return err 257 | } 258 | d.id = module.Id 259 | return nil 260 | } 261 | 262 | func (d *Dataplane) Id() string { 263 | return d.id 264 | } 265 | 266 | func (d *Dataplane) Close() error { 267 | return nil 268 | } 269 | 270 | func epgToId(epgName string) int { 271 | // TODO: store in the DB a name->id mapping 272 | switch epgName { 273 | case "clients": 274 | return 1 275 | case "webservers": 276 | return 2 277 | } 278 | return 0 279 | } 280 | 281 | func (d *Dataplane) ParsePolicy(policy *Policy) (err error) { 282 | Debug.Println("ParsePolicy") 283 | consumerId := epgToId(policy.ConsumerEpgId) 284 | providerId := epgToId(policy.ProviderEpgId) 285 | for _, ruleGroupConstrained := range policy.PolicyRuleGroups { 286 | for _, ruleGroup := range ruleGroupConstrained.PolicyRuleGroups { 287 | for _, rule := range ruleGroup.ResolvedRules { 288 | for _, classifier := range rule.Classifiers { 289 | m := classifier.ToMatch() 290 | id1, id2 := consumerId, providerId 291 | if m.Direction == 2 { 292 | id1, id2 = providerId, consumerId 293 | } 294 | k := fmt.Sprintf("{ %d %d %d %d %d %d [0 0]}", 295 | id1, id2, m.SourcePort, m.DestPort, m.Proto, 1 /*m.Direction*/) 296 | v := "2" // drop 297 | if rule.IsAllow() { 298 | v = "0" 299 | } 300 | Debug.Printf("rule %s %s\n", k, v) 301 | obj := &tableEntry{Key: k, Value: v} 302 | err = d.postObject("/modules/"+d.id+"/tables/rules/entries/", obj, nil) 303 | if err != nil { 304 | return 305 | } 306 | } 307 | } 308 | } 309 | } 310 | return 311 | } 312 | 313 | type EndpointEntry struct { 314 | Ip string `db:"ip" json:"ip"` 315 | Tenant string `db:"tenant" json:"tenant"` 316 | Epg string `db:"epg" json:"epg"` 317 | } 318 | 319 | func (d *Dataplane) Endpoints() <-chan *EndpointEntry { 320 | ch := make(chan *EndpointEntry) 321 | rows, err := d.db.Queryx(`SELECT * FROM endpoints`) 322 | if err != nil { 323 | return nil 324 | } 325 | go func() { 326 | defer close(ch) 327 | for rows.Next() { 328 | var e EndpointEntry 329 | err = rows.StructScan(&e) 330 | if err != nil { 331 | return 332 | } 333 | ch <- &e 334 | } 335 | }() 336 | return ch 337 | } 338 | 339 | func ipStrToKey(ipStr string) (string, error) { 340 | ip := net.ParseIP(ipStr) 341 | if ip == nil { 342 | return "", fmt.Errorf("ipStrToKey: ipStr is not a valid IP address") 343 | } 344 | if ip.To4() != nil { 345 | return fmt.Sprintf("%d", binary.BigEndian.Uint32(ip.To4())), nil 346 | } 347 | return "", fmt.Errorf("ipStrToKey: IPv6 support not implemented") 348 | } 349 | func (d *Dataplane) AddEndpoint(ipStr, tenant, epg string) (err error) { 350 | ipKey, err := ipStrToKey(ipStr) 351 | if err != nil { 352 | return 353 | } 354 | 355 | tx, err := d.db.Begin() 356 | if err != nil { 357 | return 358 | } 359 | defer func() { 360 | if err != nil { 361 | Info.Printf("AddEndpoint: rolling back %s\n", ipStr) 362 | tx.Rollback() 363 | } else { 364 | err = tx.Commit() 365 | } 366 | }() 367 | _, err = tx.Exec(`INSERT INTO endpoints (ip, tenant, epg) VALUES (?, ?, ?)`, ipStr, tenant, epg) 368 | if err != nil { 369 | return 370 | } 371 | obj := &tableEntry{ 372 | Key: ipKey, 373 | Value: fmt.Sprintf("%d", epgToId(epg)), 374 | } 375 | err = d.postObject("/modules/"+d.id+"/tables/endpoints/entries/", obj, nil) 376 | if err != nil { 377 | return 378 | } 379 | return 380 | } 381 | 382 | func (d *Dataplane) DeleteEndpoint(ip string) (err error) { 383 | tx, err := d.db.Begin() 384 | if err != nil { 385 | return 386 | } 387 | defer func() { 388 | if err != nil { 389 | tx.Rollback() 390 | } else { 391 | err = tx.Commit() 392 | } 393 | }() 394 | _, err = tx.Exec(`DELETE FROM endpoints WHERE ip=?`, ip) 395 | if err != nil { 396 | return 397 | } 398 | //err = d.deleteObject("/modules/"+d.id+"/tables/endpoints/entries/"+ip, nil, nil) 399 | return 400 | } 401 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------