├── openflow ├── nom.go ├── ofmeta │ └── ofmeta.go ├── feature.go ├── errormsg.go ├── role.go ├── conv.go ├── ofmeta.packet ├── echo.go ├── driver_test.go ├── listener.go ├── bh.go ├── stats.go ├── of.packet ├── packetin.go ├── conn.go ├── handshake.go └── of │ └── of.go ├── .travis.yml ├── AUTHORS ├── net ├── meta │ └── meta.go ├── lldp │ ├── size.go │ └── lldp.go ├── meta.packet ├── ethernet.packet ├── lldp.packet └── ethernet │ └── ethernet.go ├── Docs └── assets │ └── beehive-optimization.gif ├── kandoo ├── local.go ├── nonlocal.go ├── reroute.go ├── bh.go ├── README.md ├── main │ └── main.go └── detect.go ├── nom ├── doc.go ├── ping.go ├── object.go ├── id.go ├── net.go ├── driver.go ├── role.go ├── packet.go ├── query.go ├── trigger.go ├── link.go ├── node.go ├── addr_test.go ├── path.go ├── port.go ├── flow_test.go ├── addr.go └── flow.go ├── .gitignore ├── controller ├── batch.go ├── pkt.go ├── query.go ├── ctrl.go ├── flow.go ├── trigger.go ├── health.go ├── port.go ├── consolidator.go ├── node.go └── dicts.go ├── path ├── dict.go ├── id.go ├── topo.go ├── flow.go ├── path_test.go └── path.go ├── README.md ├── switching ├── hub.go └── learning.go ├── main.go ├── discovery ├── lldp_test.go ├── graph_test.go ├── lldp.go ├── graph.go └── discovery.go └── LICENSE /openflow/nom.go: -------------------------------------------------------------------------------- 1 | package openflow 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.4 5 | 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Soheil Hassas Yeganeh 2 | -------------------------------------------------------------------------------- /net/meta/meta.go: -------------------------------------------------------------------------------- 1 | // Automatically generated by Packet Go code generator. 2 | package meta 3 | -------------------------------------------------------------------------------- /openflow/ofmeta/ofmeta.go: -------------------------------------------------------------------------------- 1 | // Automatically generated by Packet Go code generator. 2 | package ofmeta 3 | -------------------------------------------------------------------------------- /Docs/assets/beehive-optimization.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kandoo/beehive-netctrl/HEAD/Docs/assets/beehive-optimization.gif -------------------------------------------------------------------------------- /kandoo/local.go: -------------------------------------------------------------------------------- 1 | package kandoo 2 | 3 | import ( 4 | bh "github.com/kandoo/beehive" 5 | ) 6 | 7 | // Implements the map function for local handlers. 8 | type Local struct{} 9 | 10 | func (h Local) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 11 | return ctx.LocalMappedCells() 12 | } 13 | -------------------------------------------------------------------------------- /kandoo/nonlocal.go: -------------------------------------------------------------------------------- 1 | package kandoo 2 | 3 | import ( 4 | bh "github.com/kandoo/beehive" 5 | ) 6 | 7 | // Implements the map function for nonLocal handlers. 8 | type NonLocal struct{} 9 | 10 | func (h NonLocal) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 11 | return bh.MappedCells{{"__D__", "__0__"}} 12 | } 13 | -------------------------------------------------------------------------------- /nom/doc.go: -------------------------------------------------------------------------------- 1 | // Package nom contains the Network Object Model: an abstraction for computer 2 | // networks. In essence it is a graph of networking nodes similar to what is 3 | // proposed in ONIX. 4 | // 5 | // NOM models a network as a graph of nodes. Each node has a set of ports with 6 | // outgoing links that are connected to other ports. 7 | package nom 8 | -------------------------------------------------------------------------------- /nom/ping.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import "encoding/gob" 4 | 5 | // Ping represents a ping message sent to a driver. It is used to 6 | // health-check the driver. 7 | type Ping struct{} 8 | 9 | // Pong represents a reply to a ping message sent by the driver. 10 | type Pong struct{} 11 | 12 | func init() { 13 | gob.Register(Ping{}) 14 | gob.Register(Pong{}) 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /controller/batch.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import "github.com/kandoo/beehive-netctrl/nom" 4 | 5 | // Batch represents a collection of flow entries to be added to and removed from 6 | // the network. The controller ensures that these changes are either all applied 7 | // or all failed. 8 | type Batch struct { 9 | Adds []nom.AddFlowEntry // The flows to be added. 10 | Dels []nom.DelFlowEntry // The flows to be removed. 11 | } 12 | -------------------------------------------------------------------------------- /nom/object.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | // Object is the interface of all structs in the network object model. 4 | type Object interface { 5 | // JSONDecode decodes the object from a byte array using the JSON encoding. 6 | JSONDecode(b []byte) error 7 | // JSONEncode encodes the object into a byte array using the JSON encoding. 8 | JSONEncode() ([]byte, error) 9 | // UID returns a unique ID of this object. This ID is unique in the network 10 | // among all other objects. 11 | UID() UID 12 | } 13 | -------------------------------------------------------------------------------- /controller/pkt.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | bh "github.com/kandoo/beehive" 5 | "github.com/kandoo/beehive-netctrl/nom" 6 | ) 7 | 8 | type pktOutHandler struct{} 9 | 10 | func (h pktOutHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 11 | pkt := msg.Data().(nom.PacketOut) 12 | return sendToMaster(pkt, pkt.Node, ctx) 13 | } 14 | 15 | func (h pktOutHandler) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 16 | return nodeDriversMap(msg.Data().(nom.PacketOut).Node) 17 | } 18 | -------------------------------------------------------------------------------- /path/dict.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | bh "github.com/kandoo/beehive" 5 | ) 6 | 7 | const ( 8 | centralizedD = "D" 9 | centralizedK = "0" 10 | 11 | dictFlow = "FlowDict" 12 | dictPath = "PathDict" 13 | dictID = "IDDict" 14 | ) 15 | 16 | var centralizedMap = bh.MappedCells{{Dict: centralizedD, Key: centralizedK}} 17 | 18 | func centralizedAppCellKey(app string) bh.AppCellKey { 19 | return bh.AppCellKey{ 20 | App: app, 21 | Dict: centralizedD, 22 | Key: centralizedK, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /controller/query.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | bh "github.com/kandoo/beehive" 5 | "github.com/kandoo/beehive-netctrl/nom" 6 | ) 7 | 8 | type queryHandler struct{} 9 | 10 | func (h queryHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 11 | query := msg.Data().(nom.FlowStatsQuery) 12 | return sendToMaster(query, query.Node, ctx) 13 | } 14 | 15 | func (h queryHandler) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 16 | return nodeDriversMap(msg.Data().(nom.FlowStatsQuery).Node) 17 | } 18 | -------------------------------------------------------------------------------- /path/id.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | bh "github.com/kandoo/beehive" 5 | ) 6 | 7 | func reserveFlowID(ctx bh.RcvContext, cnt int) uint64 { 8 | d := ctx.Dict(dictID) 9 | var id uint64 10 | if v, err := d.Get("flow"); err == nil { 11 | id = v.(uint64) 12 | } 13 | id += uint64(cnt) 14 | d.Put("flow", id) 15 | return id - uint64(cnt) 16 | } 17 | 18 | func reservePathID(ctx bh.RcvContext) uint64 { 19 | d := ctx.Dict(dictID) 20 | var id uint64 21 | if v, err := d.Get("path"); err == nil { 22 | id = v.(uint64) 23 | } 24 | id++ 25 | d.Put("path", id) 26 | return id - 1 27 | } 28 | -------------------------------------------------------------------------------- /openflow/feature.go: -------------------------------------------------------------------------------- 1 | package openflow 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/kandoo/beehive-netctrl/openflow/of10" 7 | "github.com/kandoo/beehive-netctrl/openflow/of12" 8 | ) 9 | 10 | func (of *of10Driver) handleFeaturesReply(rep of10.FeaturesReply, 11 | c *ofConn) error { 12 | return lateFeaturesReplyError() 13 | } 14 | 15 | func (of *of12Driver) handleFeaturesReply(rep of12.FeaturesReply, 16 | c *ofConn) error { 17 | return lateFeaturesReplyError() 18 | } 19 | 20 | func lateFeaturesReplyError() error { 21 | return errors.New("Cannot receive a features reply after handshake.") 22 | } 23 | -------------------------------------------------------------------------------- /kandoo/reroute.go: -------------------------------------------------------------------------------- 1 | package kandoo 2 | 3 | import ( 4 | "fmt" 5 | 6 | bh "github.com/kandoo/beehive" 7 | "github.com/kandoo/beehive-netctrl/nom" 8 | ) 9 | 10 | // ElephantDetected is a message emitted when an elephant flow is detected. 11 | type ElephantDetected struct { 12 | Match nom.Match 13 | } 14 | 15 | // Rerouter implements the sample rerouting application in kandoo. 16 | type Rerouter struct { 17 | NonLocal 18 | } 19 | 20 | func (r Rerouter) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 21 | fmt.Printf("reroute: reroute an elephant flow %v\n", 22 | msg.Data().(ElephantDetected).Match) 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /net/lldp/size.go: -------------------------------------------------------------------------------- 1 | package lldp 2 | 3 | func (this LinkDiscoveryTLV) Size() int { 4 | tl := this.TypeAndLen() 5 | return int(tl&0x01FF + 2) 6 | } 7 | 8 | func (this LinkDiscoveryTLV) SetSize(s int) { 9 | this.MergeTypeAndLen(this.Type(), uint16(s-2)) 10 | } 11 | 12 | func (this LinkDiscoveryTLV) MergeTypeAndLen(t uint8, l uint16) { 13 | this.SetTypeAndLen(uint16(uint16(t&0x07F)<<9 | l&0x1FF)) 14 | } 15 | 16 | func (this LinkDiscoveryTLV) Type() uint8 { 17 | return uint8((this.TypeAndLen() & 0xFF00) >> 9) 18 | } 19 | 20 | func (this LinkDiscoveryTLV) SetType(t uint8) { 21 | this.MergeTypeAndLen(t, uint16(this.Size()-2)) 22 | } 23 | -------------------------------------------------------------------------------- /openflow/errormsg.go: -------------------------------------------------------------------------------- 1 | package openflow 2 | 3 | import ( 4 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 5 | "github.com/kandoo/beehive-netctrl/openflow/of10" 6 | "github.com/kandoo/beehive-netctrl/openflow/of12" 7 | ) 8 | 9 | func (of *of10Driver) handleErrorMsg(err of10.ErrorMsg, c *ofConn) error { 10 | glog.Errorf("Error from switch %s: type=%d code=%d", c.node, err.ErrType(), 11 | err.Code()) 12 | return nil 13 | } 14 | 15 | func (of *of12Driver) handleErrorMsg(err of12.ErrorMsg, c *ofConn) error { 16 | glog.Errorf("Error from switch %s: type=%d code=%d", c.node, err.ErrType(), 17 | err.Code()) 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /kandoo/bh.go: -------------------------------------------------------------------------------- 1 | package kandoo 2 | 3 | import ( 4 | "time" 5 | 6 | bh "github.com/kandoo/beehive" 7 | "github.com/kandoo/beehive-netctrl/nom" 8 | ) 9 | 10 | // RegisterApps registers Kandoo applications on the hive, with the given 11 | // elephant flow size threshold. 12 | func RegisterApps(hive bh.Hive, threshold uint64) { 13 | ar := hive.NewApp("Reroute") 14 | ar.Handle(ElephantDetected{}, Rerouter{}) 15 | 16 | ad := hive.NewApp("Detect") 17 | ad.Handle(nom.FlowStatsQueryResult{}, Detector{}) 18 | ad.Handle(nom.NodeJoined{}, Adder{}) 19 | 20 | type poll struct{} 21 | ad.Handle(poll{}, Poller{}) 22 | ad.Detached(bh.NewTimer(1*time.Second, func() { 23 | hive.Emit(poll{}) 24 | })) 25 | } 26 | -------------------------------------------------------------------------------- /kandoo/README.md: -------------------------------------------------------------------------------- 1 | Kandoo 2 | ====== 3 | This package implements 4 | [Kandoo](http://dl.acm.org/citation.cfm?id=2342441.2342446) on top of 5 | [Beehive SDN/OpenFlow Controller](https://github.com/kandoo/beehive-netctrl). 6 | 7 | To boot the first controller, run: 8 | 9 | ``` 10 | # go run main/main.go -addr ADDR1:PORT1 11 | ``` 12 | 13 | where `ADDR1` is the listening address and `PORT1` is the listening port. 14 | 15 | To connect a new controller running on another machine to 16 | your first controller, run: 17 | 18 | ``` 19 | # go run main/main.go -addr ADDRN:PORTN -paddrs ADDR1:PORT1 20 | ``` 21 | 22 | All controllers will be local controllers and one of them automatically 23 | becomes the root controller. 24 | 25 | -------------------------------------------------------------------------------- /openflow/role.go: -------------------------------------------------------------------------------- 1 | package openflow 2 | 3 | import ( 4 | "github.com/kandoo/beehive-netctrl/nom" 5 | "github.com/kandoo/beehive-netctrl/openflow/of12" 6 | ) 7 | 8 | func (d *of12Driver) handleRoleReply(r of12.RoleReply, c *ofConn) error { 9 | var role nom.DriverRole 10 | switch of12.ControllerRole(r.Role()) { 11 | case of12.ROLE_EQUAL: 12 | role = nom.DriverRoleDefault 13 | case of12.ROLE_MASTER: 14 | role = nom.DriverRoleMaster 15 | case of12.ROLE_SLAVE: 16 | role = nom.DriverRoleSlave 17 | } 18 | 19 | c.ctx.Emit(nom.DriverRoleUpdate{ 20 | Node: c.node.UID(), 21 | Driver: nom.Driver{ 22 | BeeID: c.ctx.ID(), 23 | Role: role, 24 | }, 25 | Generation: r.GenerationId(), 26 | }) 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /kandoo/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | 6 | bh "github.com/kandoo/beehive" 7 | "github.com/kandoo/beehive-netctrl/controller" 8 | "github.com/kandoo/beehive-netctrl/discovery" 9 | "github.com/kandoo/beehive-netctrl/kandoo" 10 | "github.com/kandoo/beehive-netctrl/openflow" 11 | "github.com/kandoo/beehive-netctrl/switching" 12 | ) 13 | 14 | var eThreshold = flag.Uint64("kandoo.thresh", 1024, 15 | "the minimum size of an elephent flow ") 16 | 17 | func main() { 18 | h := bh.NewHive() 19 | 20 | openflow.StartOpenFlow(h) 21 | controller.RegisterNOMController(h) 22 | discovery.RegisterDiscovery(h) 23 | switching.RegisterSwitch(h) 24 | kandoo.RegisterApps(h, *eThreshold) 25 | 26 | h.Start() 27 | } 28 | -------------------------------------------------------------------------------- /nom/id.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import ( 4 | "encoding/gob" 5 | "strings" 6 | ) 7 | 8 | // UID is a unique ID of a NOM object. Unlike UUID/GUID, this ID contains 9 | // redundant information about the object. For example a port's UID contains its 10 | // network and node ID along with an ID for the port. 11 | type UID string 12 | 13 | const ( 14 | Nil = "" 15 | ) 16 | 17 | // UIDSeparator is the token added in between the parts of a UID. 18 | const UIDSeparator = "$$" 19 | 20 | // UIDJoin joins an array of IDs into a UID. 21 | func UIDJoin(ids ...string) UID { 22 | return UID(strings.Join(ids, UIDSeparator)) 23 | } 24 | 25 | // UIDSplit splits a UID into its smaller IDs. 26 | func UIDSplit(id UID) []string { 27 | return strings.Split(string(id), UIDSeparator) 28 | } 29 | 30 | func init() { 31 | gob.Register(UID("")) 32 | } 33 | -------------------------------------------------------------------------------- /openflow/conv.go: -------------------------------------------------------------------------------- 1 | package openflow 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/kandoo/beehive-netctrl/nom" 7 | ) 8 | 9 | func datapathIDToNodeID(dpID uint64) nom.NodeID { 10 | return nom.NodeID(strconv.FormatUint(dpID, 16)) 11 | } 12 | 13 | func nodeIDToDatapathID(nodeID nom.NodeID) (uint64, error) { 14 | return strconv.ParseUint(string(nodeID), 16, 64) 15 | } 16 | 17 | func portNoToPortID(portNo uint32) nom.PortID { 18 | return nom.PortID(strconv.FormatUint(uint64(portNo), 10)) 19 | } 20 | 21 | func portIDToPortNo(portID nom.PortID) (uint32, error) { 22 | no, err := strconv.ParseUint(string(portID), 10, 32) 23 | return uint32(no), err 24 | } 25 | 26 | func datapathIDToMACAddr(dpID uint64) nom.MACAddr { 27 | var mac [6]byte 28 | for i := 0; i < len(mac); i++ { 29 | mac[i] = byte((dpID >> uint(len(mac)-1-i)) & 0xFF) 30 | } 31 | return mac 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Beehive Network Controller ![Travis Build Status](https://api.travis-ci.org/kandoo/beehive-netctrl.svg?branch=master) 2 | 3 | This is a distributed SDN controller built on top of 4 | [Beehive](http://github.com/kandoo/beehive). It supports 5 | OpenFlow but can be easily extended for other southbound protocols. 6 | 7 | Beehive network controller is high throughput, fault-tolerant and, 8 | more importantly, can automatically optimize itself after a fault: 9 | 10 | ![Beehive Demo](http://raw.github.com/kandoo/beehive-netctrl/master/Docs/assets/beehive-optimization.gif) 11 | 12 | Beehive network controller supports different forwarding and routing 13 | methods, has automated discovery, end-to-end paths, and isolation. 14 | 15 | Kandoo 16 | ------ 17 | You can find an implementation of Kandoo in 18 | the [kandoo](https://github.com/kandoo/beehive-netctrl/tree/master/kandoo) 19 | package. 20 | 21 | -------------------------------------------------------------------------------- /net/meta.packet: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014, The Beehive project authors. 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 ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # This is a meta packet that includes all network protocols. This is 16 | # required for generating polymorphic headers. 17 | # Author: Soheil Hassas Yeganeh 18 | 19 | include ; 20 | include ; 21 | -------------------------------------------------------------------------------- /switching/hub.go: -------------------------------------------------------------------------------- 1 | package switching 2 | 3 | import ( 4 | bh "github.com/kandoo/beehive" 5 | "github.com/kandoo/beehive-netctrl/nom" 6 | ) 7 | 8 | type Hub struct{} 9 | 10 | func (h Hub) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 11 | in := msg.Data().(nom.PacketIn) 12 | out := nom.PacketOut{ 13 | Node: in.Node, 14 | InPort: in.InPort, 15 | BufferID: in.BufferID, 16 | Packet: in.Packet, 17 | Actions: []nom.Action{nom.ActionFlood{}}, 18 | } 19 | ctx.Reply(msg, out) 20 | return nil 21 | } 22 | 23 | func (h Hub) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 24 | return bh.MappedCells{{"N", string(msg.Data().(nom.PacketIn).Node)}} 25 | } 26 | 27 | // RegisterHub registers the hub application on the given 28 | // hive with the provided options. 29 | func RegisterHub(h bh.Hive, opts ...bh.AppOption) { 30 | app := h.NewApp("Hub", opts...) 31 | app.Handle(nom.PacketIn{}, Hub{}) 32 | } 33 | -------------------------------------------------------------------------------- /net/ethernet.packet: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014, The Beehive project authors. 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 ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | enum EtherType { 16 | ETH_T_LLDP = 0x88CC 17 | } 18 | 19 | # TODO(soheil): This is very hackish and must be fixed later. 20 | @bigendian 21 | packet Ethernet { 22 | @repeated(count = 6) 23 | uint8 dst_mac; 24 | @repeated(count = 6) 25 | uint8 src_mac; 26 | uint16 type; 27 | } 28 | 29 | 30 | -------------------------------------------------------------------------------- /openflow/ofmeta.packet: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013-2014, The Beehive project authors. 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 ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # This is a meta openflow packet that includes all openflow versions. This is 16 | # required for generating polymorphic headers. 17 | # Author: Soheil Hassas Yeganeh 18 | 19 | # Add new openflow versions below. 20 | include ; 21 | include ; 22 | 23 | -------------------------------------------------------------------------------- /nom/net.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import "encoding/json" 4 | 5 | // Network represents a virtual or physical network in NOM. 6 | type Network struct { 7 | ID NetworkID // The id of the network. 8 | Desc string // A human-readable description of the network. 9 | } 10 | 11 | // NetworkID is the ID of the network. 12 | type NetworkID string 13 | 14 | // UID returns the UID of the network. 15 | func (n Network) UID() UID { 16 | return UID(n.ID) 17 | } 18 | 19 | // ParseNetworkUID parses a network UID into the network ID. Note that for 20 | // network these IDs of the same. 21 | func ParseNetworkUID(id UID) NetworkID { 22 | return NetworkID(id) 23 | } 24 | 25 | // JSONDecode decodes the network from a byte slice using JSON. 26 | func (n *Network) JSONDecode(b []byte) error { 27 | return json.Unmarshal(b, n) 28 | } 29 | 30 | // JSONEncode encodes the network into a byte slice using JSON. 31 | func (n *Network) JSONEncode() ([]byte, error) { 32 | return json.Marshal(n) 33 | } 34 | -------------------------------------------------------------------------------- /controller/ctrl.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "time" 5 | 6 | bh "github.com/kandoo/beehive" 7 | "github.com/kandoo/beehive-netctrl/nom" 8 | ) 9 | 10 | func RegisterNOMController(h bh.Hive) { 11 | app := h.NewApp("NOMController", bh.Persistent(3)) 12 | 13 | app.Handle(nom.NodeConnected{}, nodeConnectedHandler{}) 14 | app.Handle(nom.NodeDisconnected{}, nodeDisconnectedHandler{}) 15 | app.Handle(nom.PortStatusChanged{}, portStatusHandler{}) 16 | 17 | app.Handle(nom.AddFlowEntry{}, addFlowHandler{}) 18 | app.Handle(nom.DelFlowEntry{}, delFlowHandler{}) 19 | 20 | app.Handle(nom.FlowStatsQuery{}, queryHandler{}) 21 | 22 | app.Handle(nom.PacketOut{}, pktOutHandler{}) 23 | 24 | app.Handle(nom.AddTrigger{}, addTriggerHandler{}) 25 | 26 | app.Handle(nom.FlowStatsQueryResult{}, Consolidator{}) 27 | app.Handle(nom.Pong{}, HealthChecker{}) 28 | app.Handle(poll{}, Poller{}) 29 | app.Detached(bh.NewTimer(1*time.Second, func() { 30 | h.Emit(poll{}) 31 | })) 32 | } 33 | -------------------------------------------------------------------------------- /openflow/echo.go: -------------------------------------------------------------------------------- 1 | package openflow 2 | 3 | import ( 4 | "github.com/kandoo/beehive-netctrl/openflow/of" 5 | "github.com/kandoo/beehive-netctrl/openflow/of10" 6 | "github.com/kandoo/beehive-netctrl/openflow/of12" 7 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 8 | ) 9 | 10 | func (d *of10Driver) handleEchoRequest(req of10.EchoRequest, c *ofConn) error { 11 | return doHandleEchoRequest(req.Header, of10.NewEchoReply().Header, c) 12 | } 13 | 14 | func (d *of12Driver) handleEchoRequest(req of12.EchoRequest, c *ofConn) error { 15 | return doHandleEchoRequest(req.Header, of12.NewEchoReply().Header, c) 16 | } 17 | 18 | func doHandleEchoRequest(req of.Header, res of.Header, c *ofConn) error { 19 | glog.V(2).Infof("Received echo request from %v", c.node) 20 | res.SetXid(req.Xid()) 21 | err := c.WriteHeaders([]of.Header{res}) 22 | if err != nil { 23 | return err 24 | } 25 | c.Flush() 26 | glog.V(2).Infof("Sent echo reply to %v", c.node) 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /path/topo.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | bh "github.com/kandoo/beehive" 5 | "github.com/kandoo/beehive-netctrl/discovery" 6 | "github.com/kandoo/beehive-netctrl/nom" 7 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 8 | ) 9 | 10 | func inPortsFromOutPorts(outport []nom.UID, ctx bh.RcvContext) ( 11 | inports []nom.UID) { 12 | 13 | nextoutport: 14 | for _, p := range outport { 15 | n := nom.NodeFromPortUID(p) 16 | for _, l := range discovery.LinksCentralized(n, ctx) { 17 | if l.From == p { 18 | inports = append(inports, p) 19 | continue nextoutport 20 | } 21 | } 22 | glog.Errorf("cannot find peer port for %v", p) 23 | } 24 | return inports 25 | } 26 | 27 | func outPortsFromFloodNode(n, inp nom.UID, ctx bh.RcvContext) ( 28 | outports []nom.UID) { 29 | 30 | for _, l := range discovery.LinksCentralized(n, ctx) { 31 | if l.From != inp { 32 | continue 33 | } 34 | outports = append(outports, l.From) 35 | } 36 | return outports 37 | } 38 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "runtime/pprof" 7 | 8 | bh "github.com/kandoo/beehive" 9 | "github.com/kandoo/beehive-netctrl/controller" 10 | "github.com/kandoo/beehive-netctrl/discovery" 11 | "github.com/kandoo/beehive-netctrl/openflow" 12 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 13 | ) 14 | 15 | var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") 16 | 17 | func main() { 18 | flag.Parse() 19 | if *cpuprofile != "" { 20 | f, err := os.Create(*cpuprofile) 21 | if err != nil { 22 | glog.Fatal(err) 23 | } 24 | pprof.StartCPUProfile(f) 25 | defer pprof.StopCPUProfile() 26 | } 27 | 28 | h := bh.NewHive() 29 | openflow.StartOpenFlow(h) 30 | controller.RegisterNOMController(h) 31 | discovery.RegisterDiscovery(h) 32 | 33 | // Register a switch: 34 | // switching.RegisterSwitch(h, bh.Persistent(1)) 35 | // or a hub: 36 | // switching.RegisterHub(h, bh.NonTransactional()) 37 | 38 | h.Start() 39 | } 40 | -------------------------------------------------------------------------------- /nom/driver.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | // Driver represents the Bee that communicates with the actual networking 9 | // element and adapts the southbound protocol for using in NOM. 10 | type Driver struct { 11 | BeeID uint64 12 | Role DriverRole 13 | } 14 | 15 | func (d Driver) String() string { 16 | return fmt.Sprintf("%016X (role=%v)", d.BeeID, d.Role) 17 | } 18 | 19 | // JSONDecode decodes the driver from a byte slice using JSON. 20 | func (d *Driver) JSONDecode(b []byte) error { 21 | return json.Unmarshal(b, d) 22 | } 23 | 24 | // JSONEncode encodes the driver into a byte slice using JSON. 25 | func (d *Driver) JSONEncode() ([]byte, error) { 26 | return json.Marshal(d) 27 | } 28 | 29 | type Drivers []Driver 30 | 31 | // JSONDecode decodes the drivers from a byte slice using JSON. 32 | func (d *Drivers) JSONDecode(b []byte) error { 33 | return json.Unmarshal(b, d) 34 | } 35 | 36 | // JSONEncode encodes the drivers into a byte slice using JSON. 37 | func (d *Drivers) JSONEncode() ([]byte, error) { 38 | return json.Marshal(d) 39 | } 40 | -------------------------------------------------------------------------------- /nom/role.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import "encoding/gob" 4 | 5 | // DriverRole is the role of a driver for a node. The values can be either 6 | // default, slave or master. Only one driver can be the master, but we can 7 | // have multiple slaves and multiple defaults. 8 | type DriverRole uint8 9 | 10 | // Valid values for DriverRole. 11 | const ( 12 | DriverRoleDefault DriverRole = iota 13 | DriverRoleSlave 14 | DriverRoleMaster 15 | ) 16 | 17 | // ChangeDriverRole is emitted to instruct a driver to change its role for a 18 | // node. 19 | type ChangeDriverRole struct { 20 | Node UID // Tho node ID. 21 | Role DriverRole // The requested new role. 22 | Generation uint64 // The generation of role request. 23 | } 24 | 25 | // DriverRoleUpdate is a message emitted when a driver's role is changed or 26 | // an update (no necessarily a change) is recevied for a node. 27 | type DriverRoleUpdate struct { 28 | Node UID 29 | Driver Driver 30 | Generation uint64 31 | } 32 | 33 | func init() { 34 | gob.Register(ChangeDriverRole{}) 35 | gob.Register(DriverRole(0)) 36 | gob.Register(DriverRoleUpdate{}) 37 | } 38 | -------------------------------------------------------------------------------- /controller/flow.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | bh "github.com/kandoo/beehive" 5 | "github.com/kandoo/beehive-netctrl/nom" 6 | ) 7 | 8 | type addFlowHandler struct{} 9 | 10 | func (h addFlowHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 11 | add := msg.Data().(nom.AddFlowEntry) 12 | var nf nodeFlows 13 | if v, err := ctx.Dict(flowsDict).Get(string(add.Flow.Node)); err == nil { 14 | nf = v.(nodeFlows) 15 | } 16 | added := nom.FlowEntryAdded{Flow: add.Flow} 17 | if nf.maybeAddFlow(add) { 18 | ctx.Emit(added) 19 | sendToMaster(add, add.Flow.Node, ctx) 20 | } 21 | if !add.Subscriber.IsNil() { 22 | ctx.SendToCell(added, add.Subscriber.App, add.Subscriber.Cell()) 23 | } 24 | return ctx.Dict(flowsDict).Put(string(add.Flow.Node), nf) 25 | } 26 | 27 | func (h addFlowHandler) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 28 | return nodeDriversMap(msg.Data().(nom.AddFlowEntry).Flow.Node) 29 | } 30 | 31 | type delFlowHandler struct{} 32 | 33 | func (h delFlowHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 34 | return nil 35 | } 36 | 37 | func (h delFlowHandler) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /discovery/lldp_test.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kandoo/beehive-netctrl/nom" 7 | ) 8 | 9 | func TestLLDPEncode(t *testing.T) { 10 | node := nom.Node{ 11 | ID: "n1", 12 | MACAddr: [6]byte{1, 2, 3, 4, 5, 6}, 13 | } 14 | port := nom.Port{ 15 | ID: "1", 16 | Name: "if1", 17 | MACAddr: [6]byte{1, 2, 3, 4, 5, 7}, 18 | Node: node.UID(), 19 | } 20 | b := encodeLLDP(node, port) 21 | //for i := 0; i < len(b); i += 12 { 22 | //j := 0 23 | //for ; j < 12 && i+j < len(b); j++ { 24 | //fmt.Printf("%02X ", b[i+j]) 25 | //} 26 | //for k := j; k < 12; k++ { 27 | //fmt.Print(" ") 28 | //} 29 | //fmt.Printf("\t%s\n", strconv.QuoteToASCII(string(b[i:i+j]))) 30 | //} 31 | 32 | decN, decP, err := decodeLLDP(b) 33 | if err != nil { 34 | t.Errorf("Cannot decode LLDP: %v", err) 35 | } 36 | 37 | if decN.ID != node.ID { 38 | t.Errorf("Invalid node ID decoded: %v != %v", decN.ID, node.ID) 39 | } 40 | 41 | if decN.MACAddr != node.MACAddr { 42 | t.Errorf("Invalid node MAC decoded: %v != %v", decN.MACAddr, node.MACAddr) 43 | } 44 | 45 | if decP.ID != port.ID { 46 | t.Errorf("Invalid port ID decoded: %v != %v", decP.ID, port.ID) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /discovery/graph_test.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "testing" 5 | 6 | bh "github.com/kandoo/beehive" 7 | "github.com/kandoo/beehive-netctrl/nom" 8 | ) 9 | 10 | func TestGraphBuilderCentralizedSinglePath(t *testing.T) { 11 | links := []nom.Link{ 12 | {From: "n1$$1", To: "n2$$1"}, 13 | {From: "n1$$2", To: "n3$$1"}, 14 | {From: "n2$$2", To: "n4$$1"}, 15 | {From: "n3$$2", To: "n5$$1"}, 16 | {From: "n4$$2", To: "n5$$2"}, 17 | {From: "n5$$3", To: "n6$$1"}, 18 | {From: "n4$$3", To: "n6$$2"}, 19 | {From: "n6$$1", To: "n5$$3"}, 20 | {From: "n6$$2", To: "n4$$3"}, 21 | } 22 | b := GraphBuilderCentralized{} 23 | ctx := &bh.MockRcvContext{} 24 | for _, l := range links { 25 | msg := &bh.MockMsg{ 26 | MsgData: nom.LinkAdded(l), 27 | } 28 | b.Rcv(msg, ctx) 29 | } 30 | paths, l := ShortestPathCentralized("n1", "n6", ctx) 31 | if l != 3 { 32 | t.Errorf("invalid shortest path between n1 and n6: actual=%d want=3", l) 33 | } 34 | if len(paths) != 2 { 35 | t.Errorf("invalid number of paths between n1 and n6: actual=%d want=2", 36 | len(paths)) 37 | } 38 | for _, p := range paths { 39 | if p[1] != links[2] && p[1] != links[3] { 40 | t.Errorf("invalid path: %v", p) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /controller/trigger.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | bh "github.com/kandoo/beehive" 5 | "github.com/kandoo/beehive-netctrl/nom" 6 | ) 7 | 8 | type addTriggerHandler struct{} 9 | 10 | func (h addTriggerHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 11 | add := msg.Data().(nom.AddTrigger) 12 | dict := ctx.Dict(triggersDict) 13 | var nt nodeTriggers 14 | if v, err := dict.Get(string(add.Node)); err == nil { 15 | nt = v.(nodeTriggers) 16 | } 17 | nt.maybeAddTrigger(nom.Trigger(add)) 18 | return dict.Put(string(add.Node), nt) 19 | } 20 | 21 | func (h addTriggerHandler) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 22 | return nodeDriversMap(msg.Data().(nom.AddTrigger).Node) 23 | } 24 | 25 | type delTriggerHandler struct{} 26 | 27 | func (h delTriggerHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 28 | del := msg.Data().(nom.DelTrigger) 29 | var nt nodeTriggers 30 | dict := ctx.Dict(triggersDict) 31 | if v, err := dict.Get(string(del.Node)); err == nil { 32 | nt = v.(nodeTriggers) 33 | } 34 | nt.delTrigger(nom.Trigger(del)) 35 | return dict.Put(string(del.Node), nt) 36 | } 37 | 38 | func (h delTriggerHandler) Map(msg bh.Msg, ctx bh.RcvContext) bh.MappedCells { 39 | return nodeDriversMap(msg.Data().(nom.DelTrigger).Node) 40 | } 41 | -------------------------------------------------------------------------------- /openflow/driver_test.go: -------------------------------------------------------------------------------- 1 | package openflow 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kandoo/beehive-netctrl/nom" 7 | ) 8 | 9 | func TestOF10Match(t *testing.T) { 10 | driver := of10Driver{} 11 | matches := []nom.Match{ 12 | { 13 | Fields: []nom.Field{ 14 | nom.EthSrc{ 15 | Addr: nom.MACAddr{1, 2, 3, 4, 5, 6}, 16 | Mask: nom.MaskNoneMAC, 17 | }, 18 | }, 19 | }, 20 | { 21 | Fields: []nom.Field{ 22 | nom.EthDst{ 23 | Addr: nom.MACAddr{1, 2, 3, 4, 5, 6}, 24 | Mask: nom.MaskNoneMAC, 25 | }, 26 | }, 27 | }, 28 | { 29 | Fields: []nom.Field{ 30 | nom.IPv4Src{ 31 | Addr: nom.IPv4Addr{1, 2, 3, 4}, 32 | Mask: nom.IPv4Addr{255, 255, 255, 0}, 33 | }, 34 | }, 35 | }, 36 | { 37 | Fields: []nom.Field{ 38 | nom.IPv4Dst{ 39 | Addr: nom.IPv4Addr{127, 0, 0, 1}, 40 | Mask: nom.IPv4Addr{255, 255, 255, 128}, 41 | }, 42 | }, 43 | }, 44 | } 45 | for _, m := range matches { 46 | ofm, err := driver.ofMatch(m) 47 | if err != nil { 48 | t.Error(err) 49 | } 50 | nm, err := driver.nomMatch(ofm) 51 | if err != nil { 52 | t.Error(err) 53 | } 54 | if !nm.Equals(m) { 55 | t.Errorf("invalid match conversion:\n\tactual=%#v\n\twant=%#v", nm, m) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /controller/health.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 7 | 8 | bh "github.com/kandoo/beehive" 9 | ) 10 | 11 | type HealthChecker struct{} 12 | 13 | func (h HealthChecker) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 14 | db := msg.From() 15 | dict := ctx.Dict(driversDict) 16 | 17 | var nds []nodeDrivers 18 | dict.ForEach(func(k string, v interface{}) bool { 19 | nd := v.(nodeDrivers) 20 | updated := false 21 | for i := range nd.Drivers { 22 | if nd.Drivers[i].BeeID == db { 23 | nd.Drivers[i].LastSeen = time.Now() 24 | // TODO(soheil): Maybe if outpings was more than MaxPings we 25 | // should emit a connected message. 26 | nd.Drivers[i].OutPings-- 27 | updated = true 28 | } 29 | } 30 | 31 | if updated { 32 | nds = append(nds, nd) 33 | } 34 | 35 | return true 36 | 37 | }) 38 | 39 | for _, nd := range nds { 40 | if err := dict.Put(string(nd.Node.ID), nd); err != nil { 41 | glog.Warningf("error in encoding drivers: %v", err) 42 | } 43 | } 44 | return nil 45 | } 46 | 47 | func (h HealthChecker) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 48 | // Pong is always emitted as a reply. As such Map should never be called, 49 | // and if called the message should be dropped. 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /openflow/listener.go: -------------------------------------------------------------------------------- 1 | package openflow 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | 7 | bh "github.com/kandoo/beehive" 8 | "github.com/kandoo/beehive-netctrl/openflow/of" 9 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 10 | ) 11 | 12 | type ofListener struct { 13 | proto string // The driver's listening protocol. 14 | addr string // The driver's listening address. 15 | readBufLen int // Maximum number of packets to read. 16 | } 17 | 18 | func (l *ofListener) Start(ctx bh.RcvContext) { 19 | nl, err := net.Listen(l.proto, l.addr) 20 | if err != nil { 21 | glog.Errorf("Cannot start the OF listener: %v", err) 22 | return 23 | } 24 | 25 | glog.Infof("OF listener started on %s:%s", l.proto, l.addr) 26 | 27 | defer func() { 28 | glog.Infof("OF listener closed") 29 | nl.Close() 30 | }() 31 | 32 | for { 33 | c, err := nl.Accept() 34 | if err != nil { 35 | glog.Errorf("Error in OF accept: %v", err) 36 | return 37 | } 38 | 39 | l.startOFConn(c, ctx) 40 | } 41 | } 42 | 43 | func (l *ofListener) startOFConn(conn net.Conn, ctx bh.RcvContext) { 44 | ofc := &ofConn{ 45 | HeaderConn: of.NewHeaderConn(conn), 46 | readBufLen: l.readBufLen, 47 | } 48 | 49 | ctx.StartDetached(ofc) 50 | } 51 | 52 | func (l *ofListener) Stop(ctx bh.RcvContext) { 53 | } 54 | 55 | func (l *ofListener) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 56 | return errors.New("No message should be sent to the listener") 57 | } 58 | -------------------------------------------------------------------------------- /nom/packet.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import ( 4 | "encoding/gob" 5 | "fmt" 6 | ) 7 | 8 | // Special ports. 9 | const ( 10 | PortFlood UID = "Ports.PortBcast" 11 | PortAll UID = "Ports.PortAll" 12 | ) 13 | 14 | // PacketIn messages are emitted when a packet is forwarded to the controller. 15 | type PacketIn struct { 16 | Node UID 17 | InPort UID 18 | BufferID PacketBufferID 19 | Packet Packet 20 | } 21 | 22 | func (in PacketIn) String() string { 23 | return fmt.Sprintf("packet in on switch %s port %s", in.Node, in.InPort) 24 | } 25 | 26 | // PacketOut messages are emitted to send a packet out of a port. 27 | type PacketOut struct { 28 | Node UID 29 | InPort UID 30 | BufferID PacketBufferID 31 | Packet Packet 32 | Actions []Action 33 | } 34 | 35 | // Packet is simply the packet data. 36 | type Packet []byte 37 | 38 | // DstMAC returns the destination MAC address from the ethernet header. 39 | func (p Packet) DstMAC() MACAddr { 40 | return MACAddr{p[0], p[1], p[2], p[3], p[4], p[5]} 41 | } 42 | 43 | // SrcMAC returns the source MAC address from the ethernet header. 44 | func (p Packet) SrcMAC() MACAddr { 45 | return MACAddr{p[6], p[7], p[8], p[9], p[10], p[11]} 46 | } 47 | 48 | // TODO(soheil): add code to parse ip addresses and tcp ports. 49 | 50 | // PacketBufferID represents a packet buffered in the switch. 51 | type PacketBufferID uint32 52 | 53 | func init() { 54 | gob.Register(Packet{}) 55 | gob.Register(PacketBufferID(0)) 56 | gob.Register(PacketIn{}) 57 | gob.Register(PacketOut{}) 58 | } 59 | -------------------------------------------------------------------------------- /nom/query.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import ( 4 | "encoding/gob" 5 | "time" 6 | ) 7 | 8 | // NodeQuery queries the information of a node. 9 | type NodeQuery struct { 10 | Node UID 11 | } 12 | 13 | // NodeQueryResult is the result for NodeQuery. 14 | type NodeQueryResult struct { 15 | Err error 16 | Node Node 17 | } 18 | 19 | // PortQuery queries the information of a port. 20 | type PortQuery struct { 21 | Port UID 22 | } 23 | 24 | // PortQueryResult is the result for a PortQuery. 25 | type PortQueryResult struct { 26 | Err error 27 | Port Port 28 | } 29 | 30 | // FlowStatsQuery queries the flows that would match the query. If Exact is 31 | // false, it removes all flow entries that are subsumed by the given match. 32 | type FlowStatsQuery struct { 33 | Node UID 34 | Match Match 35 | } 36 | 37 | // FlowStatsQueryResult is the result for a FlowStatQuery 38 | type FlowStatsQueryResult struct { 39 | Node UID 40 | Stats []FlowStats 41 | } 42 | 43 | // FlowStats is the statistics of flow 44 | type FlowStats struct { 45 | Match Match 46 | Duration time.Duration 47 | Packets uint64 48 | Bytes uint64 49 | } 50 | 51 | func (stats FlowStats) BW() Bandwidth { 52 | if stats.Duration == 0 { 53 | return 0 54 | } 55 | return Bandwidth(stats.Bytes / uint64(stats.Duration)) 56 | } 57 | 58 | func init() { 59 | gob.Register(FlowStatsQuery{}) 60 | gob.Register(FlowStatsQueryResult{}) 61 | gob.Register(NodeQuery{}) 62 | gob.Register(NodeQueryResult{}) 63 | gob.Register(PortQuery{}) 64 | gob.Register(PortQueryResult{}) 65 | } 66 | -------------------------------------------------------------------------------- /net/lldp.packet: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2014, The Beehive project authors. 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 ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | include ; 16 | 17 | enum LinkDiscoveryTLVType { 18 | TLV_CHASSIS_ID = 1, 19 | TLV_PORT_ID = 2, 20 | TLV_TTL = 3, 21 | TLV_CUSTOM = 127 22 | } 23 | 24 | enum PortTLVSubtype { 25 | PORT_TLV_IFACE_ALIAS = 1, 26 | PORT_TLV_PORT_COMP = 2, 27 | PORT_TLV_MAC_ADDR = 3, 28 | PORT_TLV_NET_ADDR = 4, 29 | PORT_TLV_IFACE_NAME = 5, 30 | PORT_TLV_AGENT_CIRCUIT_ID = 6, 31 | PORT_TLV_LOCAL = 7 32 | } 33 | 34 | @bigendian 35 | packet LinkDiscoveryTLV { 36 | uint16 type_and_len; 37 | @repeated 38 | uint8 value; 39 | } 40 | 41 | @type_selector(type=ethernet.EtherType.ETH_T_LLDP) 42 | packet LinkDiscoveryProtocol(ethernet.Ethernet) { 43 | } 44 | 45 | @bigendian 46 | packet ChassisTLV { 47 | uint16 type_and_len; 48 | uint8 subtype; 49 | } 50 | 51 | @type_selector(type_and_len=0x0207, subtype=0x04) 52 | packet ChassisMacTLV(ChassisTLV) { 53 | @repeated(count=6) 54 | uint8 mac_addr; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /controller/port.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 7 | 8 | bh "github.com/kandoo/beehive" 9 | "github.com/kandoo/beehive-netctrl/nom" 10 | ) 11 | 12 | type portStatusHandler struct{} 13 | 14 | func (h portStatusHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 15 | // FIXME(soheil): This implementation is very naive and cannot tolerate 16 | // faults. We need to first check if the driver is the mater, and then apply 17 | // the change. Otherwise, we need to enque this message for that driver and 18 | // make sure we apply the log to the port status 19 | data := msg.Data().(nom.PortStatusChanged) 20 | dict := ctx.Dict(driversDict) 21 | k := string(data.Port.Node) 22 | v, err := dict.Get(k) 23 | if err != nil { 24 | return fmt.Errorf("NOMController: node %v not found", data.Port.Node) 25 | } 26 | n := v.(nodeDrivers) 27 | 28 | if !n.isMaster(data.Driver) { 29 | glog.Warningf("NOMController: %v ignored, %v is not master, master is %v", 30 | data.Port, data.Driver, n.master()) 31 | return nil 32 | } 33 | 34 | if p, ok := n.Ports.GetPort(data.Port.UID()); ok { 35 | if p == data.Port { 36 | return fmt.Errorf("NOMController: duplicate port status change for %v", 37 | data.Port) 38 | } 39 | 40 | n.Ports.DelPort(p) 41 | } 42 | 43 | n.Ports.AddPort(data.Port) 44 | ctx.Emit(nom.PortUpdated(data.Port)) 45 | return nil 46 | } 47 | 48 | func (h portStatusHandler) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 49 | return bh.MappedCells{ 50 | {driversDict, string(msg.Data().(nom.PortStatusChanged).Port.Node)}, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /nom/trigger.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import ( 4 | "encoding/gob" 5 | "time" 6 | 7 | bh "github.com/kandoo/beehive" 8 | ) 9 | 10 | // AddTrigger is a message emitted to install a trigger. 11 | type AddTrigger Trigger 12 | 13 | // DelTrigger is a message emitted to remove a trigger for a specific 14 | // subscriber. 15 | type DelTrigger Trigger 16 | 17 | // Trigger represents a flow trigger. When a trigger is installed on a node, the 18 | // node will be queried continuiously. Whenever any matching flow goes beyond 19 | // the given bandwidth consumption or lives beyond the given duration, a 20 | // Triggered message will be emitted. 21 | type Trigger struct { 22 | Subscriber bh.AppCellKey // Triggered messages sent to the Subscriber. 23 | Node UID // The node. 24 | Match Match // The mathing criteria. 25 | Exact bool // Whether Match should exactly match the flow. 26 | Duration time.Duration // Minimum live duration to trigger. 27 | Bandwidth Bandwidth // Minimum bandwidth consumption to trigger. 28 | } 29 | 30 | func (t Trigger) Equals(that Trigger) bool { 31 | return t.Subscriber == that.Subscriber && t.Node == that.Node && 32 | t.Match.Equals(that.Match) && t.Exact == that.Exact && 33 | t.Duration == that.Duration && t.Bandwidth == that.Bandwidth 34 | } 35 | 36 | // Fired returns whether the trigger is fired according to the stats. 37 | func (t Trigger) Fired(stats FlowStats) bool { 38 | return t.Bandwidth <= stats.BW() || t.Duration <= stats.Duration 39 | } 40 | 41 | // Triggered is a message emitted when a trigger is triggered. 42 | type Triggered struct { 43 | Node UID 44 | Match Match 45 | Duration time.Duration 46 | Bandwidth Bandwidth 47 | } 48 | 49 | func init() { 50 | gob.Register(AddTrigger{}) 51 | gob.Register(DelTrigger{}) 52 | gob.Register(Trigger{}) 53 | } 54 | -------------------------------------------------------------------------------- /nom/link.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import ( 4 | "encoding/gob" 5 | "encoding/json" 6 | ) 7 | 8 | // LinkAdded is emitted when a new link is discovered. 9 | type LinkAdded Link 10 | 11 | // LinkDeleted is emitted when a new link is removed. 12 | type LinkDeleted Link 13 | 14 | // Link represents an outgoing link from a port. 15 | type Link struct { 16 | ID LinkID // Link's ID. 17 | From UID // From is the link's port. 18 | To UID // To stores the port(s) connected to From using this link. 19 | State LinkState // The link's state. 20 | } 21 | 22 | // LinkID is a link's ID which is unique among the outgoing links of a port. 23 | type LinkID string 24 | 25 | // UID returns the UID of the link in the form of 26 | // net_id$$node_id$$port_id$$link_id. 27 | func (l Link) UID() UID { 28 | return UIDJoin(string(l.From), string(l.ID)) 29 | } 30 | 31 | // ParseLinkUID parses a link UID into the respetive node, port, and link ids. 32 | func ParseLinkUID(id UID) (NodeID, PortID, LinkID) { 33 | s := UIDSplit(id) 34 | return NodeID(s[0]), PortID(s[1]), LinkID(s[2]) 35 | } 36 | 37 | // JSONDecode decodes the node from a byte array using JSON. 38 | func (l *Link) JSONDecode(b []byte) error { 39 | return json.Unmarshal(b, l) 40 | } 41 | 42 | // JSONEncode encodes the node into a byte array using JSON. 43 | func (l *Link) JSONEncode() ([]byte, error) { 44 | return json.Marshal(l) 45 | } 46 | 47 | // LinkState represents the status of a link. 48 | type LinkState uint8 49 | 50 | // Valid values for LinkState. 51 | const ( 52 | LinkStateUnknown LinkState = iota 53 | LinkStateUp = iota 54 | LinkStateDown = iota 55 | ) 56 | 57 | // Bandwidth represents bandwidth in Bps. 58 | type Bandwidth uint64 59 | 60 | // Bandwidth units. 61 | const ( 62 | KBps Bandwidth = 1000 63 | MBps Bandwidth = 1000000 64 | GBps Bandwidth = 1000000000 65 | ) 66 | 67 | func init() { 68 | gob.Register(LinkAdded{}) 69 | gob.Register(LinkDeleted{}) 70 | gob.Register(Link{}) 71 | gob.Register(LinkID("")) 72 | gob.Register(LinkState(0)) 73 | gob.Register(Bandwidth(0)) 74 | } 75 | -------------------------------------------------------------------------------- /openflow/bh.go: -------------------------------------------------------------------------------- 1 | package openflow 2 | 3 | import ( 4 | "flag" 5 | 6 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 7 | "github.com/kandoo/beehive/bucket" 8 | 9 | bh "github.com/kandoo/beehive" 10 | ) 11 | 12 | var ( 13 | proto = flag.String("of.proto", "tcp", "protocol of the OpenFlow listener") 14 | addr = flag.String("of.addr", "0.0.0.0:6633", 15 | "address of the OpenFlow listener in the form of HOST:PORT") 16 | readBufLen = flag.Int("of.rbuflen", 1<<8, 17 | "maximum number of packets to read per each read call") 18 | maxConnRate = flag.Int("of.maxrate", 1<<18, 19 | "maximum number of messages an openflow connection can generate per second") 20 | ) 21 | 22 | // Option represents an OpenFlow listener option. 23 | type Option func(l *ofListener) 24 | 25 | // ListenOn returns an OpenFlow option that sets the address on which the 26 | // OpenFlow service listens. 27 | func ListenOn(addr string) Option { 28 | return func(l *ofListener) { 29 | l.addr = addr 30 | } 31 | } 32 | 33 | // UseProto returns an Openflow option that sets the protocol that the OpenFlow 34 | // service uses to listen. 35 | func UseProto(proto string) Option { 36 | return func(l *ofListener) { 37 | l.proto = proto 38 | } 39 | } 40 | 41 | // SetReadBufLen returns an OpenFlow option that sets reader buffer length of 42 | // the OpenFlow service. 43 | func SetReadBufLen(rlen int) Option { 44 | return func(l *ofListener) { 45 | l.readBufLen = rlen 46 | } 47 | } 48 | 49 | // StartOpenFlow starts the OpenFlow driver on the given hive using the default 50 | // OpenFlow configuration that can be set through command line arguments. 51 | func StartOpenFlow(hive bh.Hive, options ...Option) error { 52 | app := hive.NewApp("OFDriver", 53 | bh.OutRate(bucket.Rate(*maxConnRate), 10*uint64(*maxConnRate))) 54 | l := &ofListener{ 55 | proto: *proto, 56 | addr: *addr, 57 | readBufLen: *readBufLen, 58 | } 59 | 60 | for _, opt := range options { 61 | opt(l) 62 | } 63 | 64 | app.Detached(l) 65 | glog.V(2).Infof("OpenFlow driver registered on %s:%s", l.proto, l.addr) 66 | 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /openflow/stats.go: -------------------------------------------------------------------------------- 1 | package openflow 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/kandoo/beehive-netctrl/nom" 8 | "github.com/kandoo/beehive-netctrl/openflow/of10" 9 | "github.com/kandoo/beehive-netctrl/openflow/of12" 10 | ) 11 | 12 | func (d *of10Driver) handleStatsReply(reply of10.StatsReply, 13 | c *ofConn) error { 14 | 15 | switch { 16 | case of10.IsFlowStatsReply(reply): 17 | return d.handleFlowStatsReply(of10.NewFlowStatsReplyWithBuf(reply.Buf), c) 18 | default: 19 | return fmt.Errorf("of10Driver: unsupported stats type %v", 20 | reply.StatsType()) 21 | } 22 | } 23 | 24 | func (d *of10Driver) handleFlowStatsReply(reply of10.FlowStatsReply, 25 | c *ofConn) error { 26 | 27 | nomReply := nom.FlowStatsQueryResult{ 28 | Node: c.node.UID(), 29 | } 30 | for _, stat := range reply.FlowStats() { 31 | m, err := d.nomMatch(stat.Match()) 32 | if err != nil { 33 | return err 34 | } 35 | stat := nom.FlowStats{ 36 | Match: m, 37 | Duration: time.Duration(stat.DurationSec())*time.Second + 38 | time.Duration(stat.DurationNsec()), 39 | Packets: stat.PacketCount(), 40 | Bytes: stat.ByteCount(), 41 | } 42 | nomReply.Stats = append(nomReply.Stats, stat) 43 | } 44 | c.ctx.Emit(nomReply) 45 | return nil 46 | } 47 | 48 | func (d *of12Driver) handleStatsReply(reply of12.StatsReply, 49 | c *ofConn) error { 50 | 51 | switch { 52 | case of12.IsFlowStatsReply(reply): 53 | return d.handleFlowStatsReply(of12.NewFlowStatsReplyWithBuf(reply.Buf), c) 54 | default: 55 | return fmt.Errorf("of12Driver: unsupported stats type %v", 56 | reply.StatsType()) 57 | } 58 | } 59 | 60 | func (d *of12Driver) handleFlowStatsReply(reply of12.FlowStatsReply, 61 | c *ofConn) error { 62 | 63 | nomReply := nom.FlowStatsQueryResult{ 64 | Node: c.node.UID(), 65 | } 66 | for _, stat := range reply.FlowStats() { 67 | m, err := d.nomMatch(stat.Match()) 68 | if err != nil { 69 | return err 70 | } 71 | stat := nom.FlowStats{ 72 | Match: m, 73 | Duration: time.Duration(stat.DurationSec())*time.Second + 74 | time.Duration(stat.DurationNsec()), 75 | Packets: stat.PacketCount(), 76 | Bytes: stat.ByteCount(), 77 | } 78 | nomReply.Stats = append(nomReply.Stats, stat) 79 | } 80 | c.ctx.Emit(nomReply) 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /openflow/of.packet: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2013, The Beehive project authors. 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 ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Base openflow packet header. 16 | @bigendian 17 | packet Header { 18 | uint8 version; # P_VERSION. 19 | uint8 type; # One of the PT_ constants. 20 | @size uint16 length; # Length including this ofp_header. 21 | uint32 xid; # Transaction id associated with this packet 22 | # Replies use the same id as was in the request 23 | # to facilitate pairing. 24 | } 25 | 26 | enum Versions { 27 | OPENFLOW_1_0 = 0x01, # OpenFlow 1.0. 28 | OPENFLOW_1_1 = 0x02, # OpenFlow 1.1. 29 | OPENFLOW_1_2 = 0x03, # OpenFlow 1.2. 30 | OPENFLOW_1_3 = 0x04 # OpenFlow 1.3. 31 | } 32 | 33 | enum Constants { 34 | P_ETH_ALEN = 6, # Bytes in an Ethernet address. 35 | P_IPV4_ALEN = 4, 36 | P_IPV6_ALEN = 16, 37 | P_MAX_PORT_NAME_LEN = 16, 38 | P_MAX_TABLE_NAME_LEN = 32 39 | } 40 | 41 | # OpenFlow V1 packet types. 42 | enum Type { 43 | # Immutable messages. 44 | PT_HELLO = 0, # Symmetric message 45 | PT_ERROR = 1, # Symmetric message 46 | PT_ECHO_REQUEST = 2, # Symmetric message 47 | PT_ECHO_REPLY = 3, # Symmetric message 48 | PT_VENDOR = 4, # Symmetric message 49 | 50 | # Switch configuration messages. 51 | PT_FEATURES_REQUEST = 5, # Controller/switch message 52 | PT_FEATURES_REPLY = 6, # Controller/switch message 53 | PT_GET_CONFIG_REQUEST = 7, # Controller/switch message 54 | PT_GET_CONFIG_REPLY = 8, # Controller/switch message 55 | PT_SET_CONFIG = 9 # Controller/switch message 56 | } 57 | 58 | @type_selector(type = Type.PT_HELLO) 59 | packet Hello(Header) { 60 | } 61 | 62 | 63 | -------------------------------------------------------------------------------- /kandoo/detect.go: -------------------------------------------------------------------------------- 1 | package kandoo 2 | 3 | import ( 4 | "fmt" 5 | 6 | bh "github.com/kandoo/beehive" 7 | "github.com/kandoo/beehive-netctrl/nom" 8 | ) 9 | 10 | type flow struct { 11 | Stats nom.FlowStats 12 | Notified bool 13 | } 14 | 15 | // Detector implements the sample rerouting application in kandoo. 16 | type Detector struct { 17 | Local 18 | 19 | ElephantThreshold uint64 // The minimum size of an elephent flow. 20 | } 21 | 22 | func (d Detector) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 23 | res := msg.Data().(nom.FlowStatsQueryResult) 24 | 25 | var flows []flow 26 | if v, err := ctx.Dict("Switches").Get(string(res.Node)); err == nil { 27 | flows = v.([]flow) 28 | } 29 | 30 | for _, s1 := range res.Stats { 31 | // Find the previous stats. 32 | index := -1 33 | for i := range flows { 34 | if !s1.Match.Equals(flows[i].Stats.Match) { 35 | continue 36 | } 37 | 38 | if s1.Duration < flows[i].Stats.Duration { 39 | // The stat is newer than what we've seen so far. 40 | flows[i].Notified = false 41 | } 42 | 43 | flows[i].Stats = s1 44 | index = i 45 | break 46 | } 47 | 48 | if index < 0 { 49 | index = len(flows) 50 | flows = append(flows, flow{Stats: s1}) 51 | } 52 | 53 | // Notify the router if it is an elephant flow, and make sure you do it 54 | // once. 55 | if !flows[index].Notified && 56 | flows[index].Stats.Bytes > d.ElephantThreshold { 57 | 58 | flows[index].Notified = true 59 | ctx.Emit(ElephantDetected{ 60 | Match: flows[index].Stats.Match, 61 | }) 62 | } 63 | } 64 | 65 | return ctx.Dict("Switches").Put(string(res.Node), flows) 66 | } 67 | 68 | // Adder adds a node to the dictionary when it joins the network. 69 | type Adder struct { 70 | Local 71 | } 72 | 73 | func (a Adder) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 74 | joined := msg.Data().(nom.NodeJoined) 75 | 76 | return ctx.Dict("Switches").Put(string(nom.Node(joined).UID()), []flow{}) 77 | } 78 | 79 | // Poller polls the switches. 80 | type Poller struct { 81 | Local 82 | } 83 | 84 | func (p Poller) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 85 | ctx.Dict("Switches").ForEach(func(k string, v interface{}) bool { 86 | fmt.Printf("poller: polling switch %v\n", k) 87 | ctx.Emit(nom.FlowStatsQuery{ 88 | Node: nom.UID(k), 89 | }) 90 | return true 91 | }) 92 | return nil 93 | } 94 | -------------------------------------------------------------------------------- /switching/learning.go: -------------------------------------------------------------------------------- 1 | package switching 2 | 3 | import ( 4 | bh "github.com/kandoo/beehive" 5 | "github.com/kandoo/beehive-netctrl/nom" 6 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 7 | ) 8 | 9 | type LearningSwitch struct { 10 | Hub 11 | } 12 | 13 | func (h LearningSwitch) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 14 | in := msg.Data().(nom.PacketIn) 15 | src := in.Packet.SrcMAC() 16 | dst := in.Packet.DstMAC() 17 | glog.V(2).Infof("received packet in from %v to %v", src, dst) 18 | if dst.IsLLDP() { 19 | // TODO(soheil): just drop LLDP. 20 | glog.Infof("dropped LLDP packet to %v", dst) 21 | return nil 22 | } 23 | 24 | if dst.IsBroadcast() || dst.IsMulticast() { 25 | return h.Hub.Rcv(msg, ctx) 26 | } 27 | 28 | d := ctx.Dict("mac2port") 29 | srck := src.Key() 30 | update := false 31 | if v, err := d.Get(srck); err == nil { 32 | p := v.(nom.UID) 33 | if p != in.InPort { 34 | update = true 35 | // TODO(soheil): maybe add support for multi ports. 36 | glog.Infof("%v is moved from port %v to port %v", src, p, in.InPort) 37 | } 38 | } else { 39 | update = true 40 | } 41 | 42 | if update { 43 | if err := d.Put(srck, in.InPort); err != nil { 44 | glog.Fatalf("cannot serialize port: %v", err) 45 | } 46 | } 47 | 48 | dstk := dst.Key() 49 | v, err := d.Get(dstk) 50 | if err != nil { 51 | return h.Hub.Rcv(msg, ctx) 52 | } 53 | p := v.(nom.UID) 54 | 55 | add := nom.AddFlowEntry{ 56 | Flow: nom.FlowEntry{ 57 | Node: in.Node, 58 | Match: nom.Match{ 59 | Fields: []nom.Field{ 60 | nom.EthDst{ 61 | Addr: dst, 62 | Mask: nom.MaskNoneMAC, 63 | }, 64 | }, 65 | }, 66 | Actions: []nom.Action{ 67 | nom.ActionForward{ 68 | Ports: []nom.UID{p}, 69 | }, 70 | }, 71 | }, 72 | } 73 | ctx.Reply(msg, add) 74 | 75 | out := nom.PacketOut{ 76 | Node: in.Node, 77 | InPort: in.InPort, 78 | BufferID: in.BufferID, 79 | Packet: in.Packet, 80 | Actions: []nom.Action{ 81 | nom.ActionForward{ 82 | Ports: []nom.UID{p}, 83 | }, 84 | }, 85 | } 86 | ctx.Reply(msg, out) 87 | return nil 88 | } 89 | 90 | // RegisterSwitch registers the learning switch application on the given 91 | // hive with the provided options. 92 | func RegisterSwitch(h bh.Hive, opts ...bh.AppOption) { 93 | app := h.NewApp("Switch", opts...) 94 | app.Handle(nom.PacketIn{}, LearningSwitch{}) 95 | } 96 | -------------------------------------------------------------------------------- /discovery/lldp.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/kandoo/beehive-netctrl/net/lldp" 7 | "github.com/kandoo/beehive-netctrl/nom" 8 | bh "github.com/kandoo/beehive" 9 | ) 10 | 11 | func sendLLDPPacket(n nom.Node, p nom.Port, ctx bh.RcvContext) { 12 | pkt := nom.PacketOut{ 13 | Node: n.UID(), 14 | Packet: nom.Packet(encodeLLDP(n, p)), 15 | BufferID: 0xFFFFFFFF, 16 | Actions: []nom.Action{ 17 | nom.ActionForward{ 18 | Ports: []nom.UID{p.UID()}, 19 | }, 20 | }, 21 | } 22 | ctx.Emit(pkt) 23 | } 24 | 25 | func encodeLLDP(n nom.Node, p nom.Port) []byte { 26 | b := make([]byte, 256) 27 | h := lldp.NewLinkDiscoveryProtocolWithBuf(b) 28 | h.Init() 29 | h.SetSrcMac(n.MACAddr) 30 | h.SetDstMac([6]byte{0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E}) 31 | size := h.Size() 32 | 33 | tlvb := b[size:] 34 | chTLV := lldp.NewChassisMacTLVWithBuf(tlvb) 35 | chTLV.Init() 36 | chTLV.SetMacAddr(n.MACAddr) 37 | size += chTLV.Size() 38 | 39 | tlvb = b[size:] 40 | pTLV := lldp.NewLinkDiscoveryTLVWithBuf(tlvb) 41 | pTLV.Init() 42 | pTLV.SetType(uint8(lldp.TLV_PORT_ID)) 43 | 44 | pTLV.AddValue(byte(lldp.PORT_TLV_IFACE_NAME)) 45 | for _, v := range []byte(p.UID()) { 46 | pTLV.AddValue(v) 47 | } 48 | size += pTLV.Size() 49 | 50 | tlvb = b[size:] 51 | ttlTLV := lldp.NewLinkDiscoveryTLVWithBuf(tlvb) 52 | ttlTLV.Init() 53 | ttlTLV.SetType(uint8(lldp.TLV_TTL)) 54 | ttlTLV.AddValue(0) 55 | ttlTLV.AddValue(0xFF) 56 | 57 | size += ttlTLV.Size() 58 | 59 | // TODO(soheil): Maybe add a few custom fields? 60 | return b[:size] 61 | } 62 | 63 | func decodeLLDP(b []byte) (nom.Node, nom.Port, error) { 64 | h := lldp.NewLinkDiscoveryProtocolWithBuf(b) 65 | size := h.Size() 66 | 67 | tlvb := b[size:] 68 | chTLV := lldp.NewChassisMacTLVWithBuf(tlvb) 69 | if chTLV.Size() == 0 { 70 | return nom.Node{}, nom.Port{}, errors.New("decodeLLDP: no chassis id") 71 | } 72 | n := nom.Node{ 73 | MACAddr: chTLV.MacAddr(), 74 | } 75 | size += chTLV.Size() 76 | 77 | tlvb = b[size:] 78 | pTLV := lldp.NewLinkDiscoveryTLVWithBuf(tlvb) 79 | if pTLV.Size() == 0 || pTLV.Type() != uint8(lldp.TLV_PORT_ID) { 80 | return nom.Node{}, nom.Port{}, errors.New("decodeLLDP: no port id") 81 | } 82 | 83 | v := pTLV.Value() 84 | if v[0] != uint8(lldp.PORT_TLV_IFACE_NAME) { 85 | return nom.Node{}, nom.Port{}, errors.New("decodeLLDP: no port iface name") 86 | } 87 | 88 | portUID := nom.UID(v[1:]) 89 | nID, pID := nom.ParsePortUID(portUID) 90 | n.ID = nID 91 | 92 | return n, nom.Port{ID: pID, Node: n.UID()}, nil 93 | } 94 | -------------------------------------------------------------------------------- /nom/node.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import ( 4 | "encoding/gob" 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | // NodeConnected is a message emitted when a node connects to a driver. 10 | type NodeConnected struct { 11 | Node Node 12 | Driver Driver 13 | } 14 | 15 | // NodeDisconnected is a message emitted when a node disconnects from its 16 | // driver. 17 | type NodeDisconnected struct { 18 | Node Node 19 | Driver Driver 20 | } 21 | 22 | // NodeJoined is a message emitted when a node joins the network through the 23 | // controller. It is always emitted after processing NodeConnected in the 24 | // controller. 25 | type NodeJoined Node 26 | 27 | // NodeLeft is a message emitted when a node disconnects from its driver. It is 28 | // always emitted after processing NodeDisconnected in the controller. 29 | type NodeLeft Node 30 | 31 | // Node represents a forwarding element, such as switches and routers. 32 | type Node struct { 33 | ID NodeID 34 | Net UID 35 | Capabilities []NodeCapability 36 | MACAddr MACAddr 37 | } 38 | 39 | func (n Node) String() string { 40 | return fmt.Sprintf("Node %s (mac=%v)", string(n.ID), n.MACAddr) 41 | } 42 | 43 | // NodeID is the ID of a node. This must be unique among all nodes in the 44 | // network. 45 | type NodeID string 46 | 47 | // UID converts id into a UID. 48 | func (id NodeID) UID() UID { 49 | return UID(id) 50 | } 51 | 52 | // UID returns the node's unique ID. This id is in the form of net_id$$node_id. 53 | func (n Node) UID() UID { 54 | return UID(string(n.ID)) 55 | } 56 | 57 | // ParseNodeUID parses a UID of a node and returns the respective node IDs. 58 | func ParseNodeUID(id UID) NodeID { 59 | s := UIDSplit(id) 60 | return NodeID(s[0]) 61 | } 62 | 63 | // JSONDecode decodes the node from a byte array using JSON. 64 | func (n *Node) JSONDecode(b []byte) error { 65 | return json.Unmarshal(b, n) 66 | } 67 | 68 | // JSONEncode encodes the node into a byte array using JSON. 69 | func (n *Node) JSONEncode() ([]byte, error) { 70 | return json.Marshal(n) 71 | } 72 | 73 | func (n Node) HasCapability(c NodeCapability) bool { 74 | for _, nc := range n.Capabilities { 75 | if c == nc { 76 | return true 77 | } 78 | } 79 | 80 | return false 81 | } 82 | 83 | // NodeCapability is a capability of a NOM node. 84 | type NodeCapability uint32 85 | 86 | // Valid values for NodeCapability. 87 | const ( 88 | CapDriverRole NodeCapability = 1 << iota // Node can set the driver's role. 89 | ) 90 | 91 | func init() { 92 | gob.Register(NodeConnected{}) 93 | gob.Register(NodeDisconnected{}) 94 | gob.Register(NodeLeft{}) 95 | gob.Register(NodeJoined{}) 96 | } 97 | -------------------------------------------------------------------------------- /path/flow.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "time" 7 | 8 | bh "github.com/kandoo/beehive" 9 | "github.com/kandoo/beehive-netctrl/nom" 10 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 11 | ) 12 | 13 | type pathAndFlows struct { 14 | Subscriber bh.AppCellKey 15 | Path nom.Path 16 | Flows []flowAndStatus 17 | Installed int 18 | Timestamp time.Time 19 | } 20 | 21 | type flowAndStatus struct { 22 | Flow nom.FlowEntry 23 | Installed bool 24 | } 25 | 26 | func addFlowEntriesForPath(sub bh.AppCellKey, path nom.Path, 27 | flows []nom.FlowEntry, ctx bh.RcvContext) { 28 | 29 | fs := make([]flowAndStatus, 0, len(flows)) 30 | path.ID = strconv.FormatUint(reservePathID(ctx), 16) 31 | for i := range flows { 32 | flows[i].ID = path.ID 33 | fs = append(fs, flowAndStatus{Flow: flows[i]}) 34 | } 35 | 36 | pf := pathAndFlows{ 37 | Subscriber: sub, 38 | Path: path, 39 | Flows: fs, 40 | Timestamp: time.Now(), 41 | } 42 | d := ctx.Dict(dictPath) 43 | if err := d.Put(path.ID, pf); err != nil { 44 | glog.Fatalf("error in storing path entry: %v", err) 45 | } 46 | 47 | ack := centralizedAppCellKey(ctx.App()) 48 | for _, f := range flows { 49 | addf := nom.AddFlowEntry{ 50 | Flow: f, 51 | Subscriber: ack, 52 | } 53 | ctx.Emit(addf) 54 | } 55 | } 56 | 57 | func confirmFlowEntryForPath(flow nom.FlowEntry, ctx bh.RcvContext) error { 58 | d := ctx.Dict(dictPath) 59 | 60 | v, err := d.Get(flow.ID) 61 | if err != nil { 62 | return fmt.Errorf("path: flow not found: %v", err) 63 | } 64 | 65 | pf := v.(pathAndFlows) 66 | 67 | for i := range pf.Flows { 68 | if pf.Flows[i].Flow.Equals(flow) { 69 | if pf.Flows[i].Installed { 70 | return fmt.Errorf("%v is already installed", flow) 71 | } 72 | pf.Flows[i].Installed = true 73 | pf.Installed++ 74 | break 75 | } 76 | } 77 | 78 | if pf.Installed == len(pf.Flows) { 79 | ctx.SendToCell(nom.PathAdded{Path: pf.Path}, pf.Subscriber.App, 80 | pf.Subscriber.Cell()) 81 | } 82 | return d.Put(flow.ID, pf) 83 | } 84 | 85 | func delFlowEntryFromPath(flow nom.FlowEntry, ctx bh.RcvContext) error { 86 | // TODO(soheil): Add deleted flow entries. 87 | panic("TODO not implemented yet") 88 | } 89 | 90 | type flowHandler struct{} 91 | 92 | func (h flowHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 93 | switch data := msg.Data().(type) { 94 | case nom.FlowEntryAdded: 95 | return confirmFlowEntryForPath(nom.FlowEntry(data.Flow), ctx) 96 | case nom.FlowEntryDeleted: 97 | return delFlowEntryFromPath(nom.FlowEntry(data.Flow), ctx) 98 | } 99 | return fmt.Errorf("flowHandler: unsupported message %v", msg.Type()) 100 | } 101 | 102 | func (h flowHandler) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 103 | return nil 104 | } 105 | -------------------------------------------------------------------------------- /openflow/packetin.go: -------------------------------------------------------------------------------- 1 | package openflow 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kandoo/beehive-netctrl/nom" 7 | "github.com/kandoo/beehive-netctrl/openflow/of10" 8 | "github.com/kandoo/beehive-netctrl/openflow/of12" 9 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 10 | ) 11 | 12 | func (of *of10Driver) handlePacketIn(in of10.PacketIn, c *ofConn) error { 13 | inPort := in.InPort() 14 | // Ignore packet-ins on switch specific ports. 15 | if inPort > uint16(of10.PP_MAX) { 16 | glog.V(2).Infof("ignoring packet-in on %v", inPort) 17 | return nil 18 | } 19 | 20 | port, ok := of.ofPorts[inPort] 21 | if !ok { 22 | return fmt.Errorf("of10Driver: port not found %v", inPort) 23 | } 24 | 25 | if glog.V(2) { 26 | glog.Infof("packet received: %v", in) 27 | } 28 | 29 | nomIn := nom.PacketIn{ 30 | Node: c.node.UID(), 31 | InPort: port.UID(), 32 | BufferID: nom.PacketBufferID(in.BufferId()), 33 | } 34 | nomIn.Packet = nom.Packet(in.Data()) 35 | c.ctx.Emit(nomIn) 36 | 37 | //c.ctx.Emit(in) 38 | 39 | //buf := make([]byte, 32) 40 | //out := of10.NewPacketOutWithBuf(buf) 41 | //out.Init() 42 | //out.SetBufferId(in.BufferId()) 43 | //out.SetInPort(in.InPort()) 44 | 45 | //bcast := of10.NewActionOutput() 46 | //bcast.SetPort(uint16(of10.PP_FLOOD)) 47 | 48 | //out.AddActions(bcast.ActionHeader) 49 | 50 | //if in.BufferId() == 0xFFFFFFFF { 51 | //for _, d := range in.Data() { 52 | //out.AddData(d) 53 | //} 54 | //} else { 55 | //out.SetBufferId(in.BufferId()) 56 | //} 57 | 58 | //c.wCh <- out.Header 59 | //if err := c.WriteHeader(out.Header); err != nil { 60 | //return fmt.Errorf("Error in writing a packet out: %v", err) 61 | //} 62 | return nil 63 | } 64 | 65 | func (of *of12Driver) handlePacketIn(in of12.PacketIn, c *ofConn) error { 66 | m := in.Match() 67 | if m.Type() == uint16(of12.PMT_STANDARD) { 68 | glog.Warningf("standard matches are not supported") 69 | return nil 70 | } 71 | 72 | var inPort uint32 73 | hasInPort := false 74 | 75 | xm, _ := of12.ToOXMatch(in.Match()) 76 | for _, f := range xm.Fields() { 77 | if of12.IsOxmInPort(f) { 78 | xp, _ := of12.ToOxmInPort(f) 79 | inPort = xp.InPort() 80 | hasInPort = true 81 | } 82 | } 83 | 84 | if !hasInPort { 85 | glog.V(2).Infof("packet in does not have an input port") 86 | return nil 87 | } 88 | 89 | // Ignore packet-ins on switch specific ports. 90 | if inPort > uint32(of10.PP_MAX) { 91 | glog.V(2).Infof("ignoring packet-in on %v", inPort) 92 | return nil 93 | } 94 | 95 | port, ok := of.ofPorts[inPort] 96 | if !ok { 97 | return fmt.Errorf("of12Driver: port not found %v", inPort) 98 | } 99 | 100 | if glog.V(2) { 101 | glog.Infof("packet received: %v", in) 102 | } 103 | 104 | nomIn := nom.PacketIn{ 105 | Node: c.node.UID(), 106 | InPort: port.UID(), 107 | BufferID: nom.PacketBufferID(in.BufferId()), 108 | } 109 | nomIn.Packet = nom.Packet(in.Data()) 110 | c.ctx.Emit(nomIn) 111 | 112 | return nil 113 | } 114 | -------------------------------------------------------------------------------- /nom/addr_test.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import "testing" 4 | 5 | func TestIPv4String(t *testing.T) { 6 | addrs := map[IPv4Addr]string{ 7 | IPv4Addr{0, 0, 0, 0}: "0.0.0.0", 8 | IPv4Addr{127, 0, 0, 1}: "127.0.0.1", 9 | IPv4Addr{255, 255, 255, 255}: "255.255.255.255", 10 | } 11 | for a, s := range addrs { 12 | if a.String() != s { 13 | t.Errorf("invalid string for ipv4 address: actual=%v want=%v", 14 | a.String(), s) 15 | } 16 | } 17 | } 18 | 19 | func TestIPv4CIDR(t *testing.T) { 20 | addrs := map[IPv4Addr]int{ 21 | IPv4Addr{0, 0, 0, 0}: 0, 22 | IPv4Addr{255, 128, 0, 0}: 9, 23 | IPv4Addr{255, 255, 0, 0}: 16, 24 | IPv4Addr{255, 255, 255, 255}: 32, 25 | } 26 | for a, m := range addrs { 27 | if a.AsCIDRMask() != m { 28 | t.Errorf("invalid CIDR for ipv4 mask: actual=%v want=%v", 29 | a.AsCIDRMask(), m) 30 | } 31 | } 32 | } 33 | 34 | func TestMaskedIPv4String(t *testing.T) { 35 | addrs := map[MaskedIPv4Addr]string{ 36 | MaskedIPv4Addr{ 37 | Addr: IPv4Addr{0, 0, 0, 0}, 38 | Mask: IPv4Addr{}, 39 | }: "0.0.0.0/0", 40 | MaskedIPv4Addr{ 41 | Addr: IPv4Addr{127, 0, 0, 1}, 42 | Mask: IPv4Addr{255, 255, 255, 255}, 43 | }: "127.0.0.1/32", 44 | MaskedIPv4Addr{ 45 | Addr: IPv4Addr{255, 255, 255, 255}, 46 | Mask: IPv4Addr{255, 255, 128, 0}, 47 | }: "255.255.255.255/17", 48 | } 49 | for a, s := range addrs { 50 | if a.String() != s { 51 | t.Errorf("invalid string for ipv4 address: actual=%v want=%v", 52 | a.String(), s) 53 | } 54 | } 55 | } 56 | 57 | func TestIPv6String(t *testing.T) { 58 | addrs := map[IPv6Addr]string{ 59 | IPv6Addr{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}: "::1", 60 | IPv6Addr{0x20, 0x01, 0x0d, 0xb8, 0x12, 0x34}: "2001:db8:1234::", 61 | IPv6Addr{0, 1, 0, 0, 0, 1}: "1:0:1::", 62 | } 63 | for a, s := range addrs { 64 | if a.String() != s { 65 | t.Errorf("invalid string for ipv6 address: actual=%v want=%v", 66 | a.String(), s) 67 | } 68 | } 69 | } 70 | 71 | func TestIPv6CIDR(t *testing.T) { 72 | addrs := map[IPv6Addr]int{ 73 | IPv6Addr{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}: 0, 74 | IPv6Addr{255, 128}: 9, 75 | IPv6Addr{255, 255, 255, 255, 128}: 33, 76 | } 77 | for a, m := range addrs { 78 | if a.AsCIDRMask() != m { 79 | t.Errorf("invalid CIDR for ipv6 mask: actual=%v want=%v", 80 | a.AsCIDRMask(), m) 81 | } 82 | } 83 | } 84 | 85 | func TestMaskedIPv6String(t *testing.T) { 86 | addrs := map[MaskedIPv6Addr]string{ 87 | MaskedIPv6Addr{ 88 | Addr: IPv6Addr{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, 89 | Mask: IPv6Addr{}, 90 | }: "::1/0", 91 | MaskedIPv6Addr{ 92 | Addr: IPv6Addr{0x02, 0x01, 0x0d, 0xb8, 0x12, 0x34}, 93 | Mask: IPv6Addr{255, 255, 255, 255}, 94 | }: "201:db8:1234::/32", 95 | MaskedIPv6Addr{ 96 | Addr: IPv6Addr{0, 0, 255, 255, 255, 255}, 97 | Mask: IPv6Addr{255, 255, 128, 0}, 98 | }: "0:ffff:ffff::/17", 99 | } 100 | for a, s := range addrs { 101 | if a.String() != s { 102 | t.Errorf("invalid string for ipv6 address: actual=%v want=%v", 103 | a.String(), s) 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /nom/path.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import ( 4 | "encoding/gob" 5 | 6 | bh "github.com/kandoo/beehive" 7 | ) 8 | 9 | // Path is a logical sequence of pathlets, where pathlet[i+1] will match the 10 | // output of pathlet[i]. If pathlet[i+1] matches on an incoming port p1, 11 | // pathlet[i] should have a forward action that forwards to a port 2 directly 12 | // connected to p1. Clearly, this rule does not apply to the first and the last 13 | // pathlets in the path. 14 | type Path struct { 15 | ID string // ID needs to be unique only to the subscriber. 16 | Pathlets []Pathlet // Pathlets in the path. 17 | Priority uint16 // Priority of this path. 18 | } 19 | 20 | func (p Path) Equals(thatp Path) bool { 21 | if len(p.Pathlets) != len(thatp.Pathlets) { 22 | return false 23 | } 24 | 25 | for i := range p.Pathlets { 26 | if !p.Pathlets[i].Equals(thatp.Pathlets[i]) { 27 | return false 28 | } 29 | } 30 | return p.ID == thatp.ID 31 | } 32 | 33 | // TODO(soheil): add multi-path if there was a real need. 34 | 35 | // Pathlet represents a logical connection pathlet in a path, where incoming 36 | // packets matching Match are processing using Actions. 37 | type Pathlet struct { 38 | Match Match // Pathlet's match. 39 | Exclude []InPort // Exclude packets from these ports in the pathlet. 40 | Actions []Action // Action that are applied. 41 | } 42 | 43 | func (pt Pathlet) Equals(thatpt Pathlet) bool { 44 | if len(pt.Actions) != len(thatpt.Actions) || 45 | len(pt.Exclude) != len(thatpt.Exclude) { 46 | 47 | return false 48 | } 49 | if !pt.Match.Equals(thatpt.Match) { 50 | return false 51 | } 52 | for i := range pt.Actions { 53 | if !pt.Actions[i].Equals(thatpt.Actions[i]) { 54 | return false 55 | } 56 | } 57 | ports := make(map[InPort]struct{}) 58 | for _, ex := range pt.Exclude { 59 | ports[ex] = struct{}{} 60 | } 61 | for _, ex := range thatpt.Exclude { 62 | if _, ok := ports[ex]; !ok { 63 | return false 64 | } 65 | } 66 | return true 67 | } 68 | 69 | // AddPath is emitted to install a path in the network. 70 | type AddPath struct { 71 | Subscriber bh.AppCellKey 72 | Path Path 73 | } 74 | 75 | // DelPath is emitted to delete a path from the network. 76 | type DelPath struct { 77 | Path Path 78 | } 79 | 80 | // PathAdded is emitted to the subscriber when the path is successfully added. 81 | type PathAdded struct { 82 | Path Path 83 | } 84 | 85 | // PathDeleted is emitted to the subscriber when the path is deleted (because it 86 | // cannot be installed in the network, or because it is explicitly removed). 87 | type PathDeleted struct { 88 | Path Path 89 | Reason PathDelReason 90 | } 91 | 92 | // PathDelReason is the reason that a path is deleted. 93 | type PathDelReason int 94 | 95 | const ( 96 | // PathDelExplicit means that the path is explicitly deleted using a DelPath. 97 | PathDelExplicit PathDelReason = iota 98 | // PathDelInvalid means that the path has contradicting pathlets. 99 | PathDelInvalid 100 | // PathDelInfeasible means that the path is valid but cannot be formed due to 101 | // the current state of the network. 102 | PathDelInfeasible 103 | ) 104 | 105 | func init() { 106 | gob.Register(AddPath{}) 107 | gob.Register(DelPath{}) 108 | gob.Register(Path{}) 109 | gob.Register(PathAdded{}) 110 | gob.Register(PathDeleted{}) 111 | gob.Register(PathDelReason(0)) 112 | gob.Register(Pathlet{}) 113 | } 114 | -------------------------------------------------------------------------------- /controller/consolidator.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 5 | 6 | bh "github.com/kandoo/beehive" 7 | "github.com/kandoo/beehive-netctrl/nom" 8 | ) 9 | 10 | const ( 11 | MaxPings = 3 12 | ) 13 | 14 | type Consolidator struct{} 15 | 16 | func (c Consolidator) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 17 | res := msg.Data().(nom.FlowStatsQueryResult) 18 | var nf nodeFlows 19 | if v, err := ctx.Dict(flowsDict).Get(string(res.Node)); err == nil { 20 | nf = v.(nodeFlows) 21 | } 22 | var nt nodeTriggers 23 | if v, err := ctx.Dict(triggersDict).Get(string(res.Node)); err == nil { 24 | nt = v.(nodeTriggers) 25 | } 26 | found := false 27 | matchedFlows := make(map[int]struct{}) 28 | for _, stat := range res.Stats { 29 | for i := range nf.Flows { 30 | if nf.Flows[i].FlowEntry.Match.Equals(stat.Match) { 31 | found = true 32 | matchedFlows[i] = struct{}{} 33 | nf.Flows[i].updateStats(stat) 34 | } 35 | } 36 | if !found { 37 | nf.Flows = append(nf.Flows, flow{ 38 | FlowEntry: nom.FlowEntry{ 39 | Match: stat.Match, 40 | }, 41 | Duration: stat.Duration, 42 | Packets: stat.Packets, 43 | Bytes: stat.Bytes, 44 | }) 45 | // TODO(soheil): emit flow entry here. 46 | } 47 | 48 | for _, t := range nt.Triggers { 49 | if t.Fired(stat) { 50 | triggered := newTriggered(t, stat.Duration, stat.BW()) 51 | sub := t.Subscriber 52 | if !sub.IsNil() { 53 | ctx.SendToCell(triggered, sub.App, sub.Cell()) 54 | } 55 | } 56 | } 57 | } 58 | 59 | count := 0 60 | for i, f := range nf.Flows { 61 | if _, ok := matchedFlows[i]; ok { 62 | continue 63 | } 64 | 65 | i -= count 66 | nf.Flows = append(nf.Flows[:i], nf.Flows[i+1:]...) 67 | count++ 68 | del := nom.FlowEntryDeleted{ 69 | Flow: f.FlowEntry, 70 | } 71 | ctx.Emit(del) 72 | for _, sub := range f.FlowSubscribers { 73 | if !sub.IsNil() { 74 | ctx.SendToCell(del, sub.App, sub.Cell()) 75 | } 76 | } 77 | } 78 | 79 | return ctx.Dict(flowsDict).Put(string(res.Node), nf) 80 | } 81 | 82 | func (c Consolidator) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 83 | return nodeDriversMap(msg.Data().(nom.FlowStatsQueryResult).Node) 84 | } 85 | 86 | type poll struct{} 87 | 88 | type Poller struct{} 89 | 90 | func (p Poller) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 91 | dict := ctx.Dict(driversDict) 92 | 93 | var nds []nodeDrivers 94 | dict.ForEach(func(k string, v interface{}) bool { 95 | node := nom.UID(k) 96 | query := nom.FlowStatsQuery{ 97 | Node: node, 98 | } 99 | sendToMaster(query, node, ctx) 100 | 101 | nd := v.(nodeDrivers) 102 | updated := false 103 | for i := range nd.Drivers { 104 | // TODO(soheil): remove the hardcoded value. 105 | if nd.Drivers[i].OutPings > MaxPings { 106 | ctx.SendToBee(nom.NodeDisconnected{ 107 | Node: nom.Node{ID: nom.NodeID(node)}, 108 | Driver: nd.Drivers[i].Driver, 109 | }, ctx.ID()) 110 | continue 111 | } 112 | 113 | ctx.SendToBee(nom.Ping{}, nd.Drivers[i].BeeID) 114 | nd.Drivers[i].OutPings++ 115 | updated = true 116 | } 117 | 118 | if updated { 119 | nds = append(nds, nd) 120 | } 121 | 122 | return true 123 | }) 124 | 125 | for _, nd := range nds { 126 | if err := dict.Put(string(nd.Node.ID), nd); err != nil { 127 | glog.Warningf("error in encoding drivers: %v", err) 128 | } 129 | } 130 | return nil 131 | } 132 | 133 | func (p Poller) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 134 | return bh.MappedCells{} 135 | } 136 | -------------------------------------------------------------------------------- /openflow/conn.go: -------------------------------------------------------------------------------- 1 | package openflow 2 | 3 | import ( 4 | "io" 5 | 6 | bh "github.com/kandoo/beehive" 7 | "github.com/kandoo/beehive-netctrl/nom" 8 | "github.com/kandoo/beehive-netctrl/openflow/of" 9 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 10 | ) 11 | 12 | type ofConnConfig struct { 13 | readBufLen int 14 | } 15 | 16 | type ofConn struct { 17 | of.HeaderConn 18 | 19 | ctx bh.RcvContext // RcvContext of the detached bee running ofConn. 20 | 21 | readBufLen int // Maximum number of packets to read. 22 | wCh chan bh.Msg // Messages to be written. 23 | wErr error // Last error in write. 24 | 25 | node nom.Node // Node that this connection represents. 26 | driver ofDriver // OpenFlow driver of this connection. 27 | } 28 | 29 | func (c *ofConn) drainWCh() { 30 | for { 31 | if _, ok := <-c.wCh; !ok { 32 | return 33 | } 34 | } 35 | } 36 | 37 | func (c *ofConn) Start(ctx bh.RcvContext) { 38 | defer func() { 39 | if c.driver != nil { 40 | c.driver.handleConnClose(c) 41 | } 42 | c.Close() 43 | // TODO(soheil): is there any better way to prevent deadlocks? 44 | glog.Infof("%v drains write queue for %v", ctx, c.RemoteAddr()) 45 | go c.drainWCh() 46 | }() 47 | 48 | c.ctx = ctx 49 | c.wCh = make(chan bh.Msg, ctx.Hive().Config().DataChBufSize) 50 | 51 | var err error 52 | if c.driver, err = c.handshake(); err != nil { 53 | glog.Errorf("Error in OpenFlow handshake: %v", err) 54 | return 55 | } 56 | 57 | stop := make(chan struct{}) 58 | 59 | wdone := make(chan struct{}) 60 | go c.doWrite(wdone, stop) 61 | 62 | rdone := make(chan struct{}) 63 | go c.doRead(rdone, stop) 64 | 65 | select { 66 | case <-rdone: 67 | close(stop) 68 | case <-wdone: 69 | close(stop) 70 | } 71 | 72 | <-rdone 73 | <-wdone 74 | } 75 | 76 | func (c *ofConn) doWrite(done chan struct{}, stop chan struct{}) { 77 | defer close(done) 78 | 79 | written := false 80 | var msg bh.Msg 81 | for { 82 | msg = nil 83 | if !written { 84 | select { 85 | case msg = <-c.wCh: 86 | case <-stop: 87 | return 88 | } 89 | } else { 90 | select { 91 | case msg = <-c.wCh: 92 | case <-stop: 93 | return 94 | default: 95 | if c.wErr = c.HeaderConn.Flush(); c.wErr != nil { 96 | return 97 | } 98 | written = false 99 | continue 100 | } 101 | } 102 | 103 | // Write the message. 104 | err := c.driver.handleMsg(msg, c) 105 | if c.wErr != nil { 106 | return 107 | } 108 | if err != nil { 109 | glog.Errorf("ofconn: Cannot convert NOM message to OpenFlow: %v", 110 | err) 111 | } 112 | written = true 113 | } 114 | } 115 | 116 | func (c *ofConn) doRead(done chan struct{}, stop chan struct{}) { 117 | defer close(done) 118 | 119 | pkts := make([]of.Header, c.readBufLen) 120 | for { 121 | select { 122 | case <-stop: 123 | return 124 | default: 125 | } 126 | 127 | n, err := c.ReadHeaders(pkts) 128 | if err != nil { 129 | if err == io.EOF { 130 | glog.Infof("connection %v closed", c.RemoteAddr()) 131 | } else { 132 | glog.Errorf("cannot read from the connection %v: %v", c.RemoteAddr(), 133 | err) 134 | } 135 | return 136 | } 137 | 138 | for _, pkt := range pkts[:n] { 139 | if err := c.driver.handlePkt(pkt, c); err != nil { 140 | glog.Errorf("%s", err) 141 | return 142 | } 143 | } 144 | 145 | pkts = pkts[n:] 146 | if len(pkts) == 0 { 147 | pkts = make([]of.Header, c.readBufLen) 148 | } 149 | } 150 | } 151 | 152 | func (c *ofConn) Stop(ctx bh.RcvContext) { 153 | c.Close() 154 | } 155 | 156 | func (c *ofConn) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 157 | c.wCh <- msg 158 | return nil 159 | } 160 | 161 | func (c *ofConn) NodeUID() nom.UID { 162 | return c.node.UID() 163 | } 164 | 165 | func (c *ofConn) WriteHeader(pkt of.Header) error { 166 | c.wErr = c.HeaderConn.WriteHeader(pkt) 167 | return c.wErr 168 | } 169 | -------------------------------------------------------------------------------- /path/path_test.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "testing" 5 | 6 | bh "github.com/kandoo/beehive" 7 | "github.com/kandoo/beehive-netctrl/discovery" 8 | "github.com/kandoo/beehive-netctrl/nom" 9 | ) 10 | 11 | func buildTopologyForTest() *bh.MockRcvContext { 12 | links := []nom.Link{ 13 | {From: "n1$$1", To: "n2$$1"}, 14 | {From: "n2$$1", To: "n1$$1"}, 15 | {From: "n2$$2", To: "n4$$1"}, 16 | {From: "n4$$1", To: "n2$$2"}, 17 | {From: "n3$$2", To: "n5$$1"}, 18 | {From: "n5$$1", To: "n3$$2"}, 19 | {From: "n4$$2", To: "n6$$1"}, 20 | {From: "n6$$1", To: "n4$$2"}, 21 | {From: "n5$$2", To: "n6$$2"}, 22 | {From: "n6$$2", To: "n5$$2"}, 23 | } 24 | b := discovery.GraphBuilderCentralized{} 25 | ctx := &bh.MockRcvContext{} 26 | for _, l := range links { 27 | msg := &bh.MockMsg{ 28 | MsgData: nom.LinkAdded(l), 29 | } 30 | b.Rcv(msg, ctx) 31 | } 32 | return ctx 33 | } 34 | 35 | func TestAddP2PPath(t *testing.T) { 36 | ctx := buildTopologyForTest() 37 | p := addHandler{} 38 | msg := &bh.MockMsg{ 39 | MsgData: nom.AddPath{}, 40 | } 41 | err := p.Rcv(msg, ctx) 42 | if err == nil { 43 | t.Error("no error on invalid path") 44 | } 45 | msg.MsgData = nom.AddPath{ 46 | Path: nom.Path{ 47 | Pathlets: []nom.Pathlet{ 48 | { 49 | Match: nom.Match{ 50 | Fields: []nom.Field{ 51 | nom.InPort("n1$$0"), 52 | }, 53 | }, 54 | Actions: []nom.Action{ 55 | nom.ActionForward{ 56 | Ports: []nom.UID{"n6$$3"}, 57 | }, 58 | }, 59 | }, 60 | }, 61 | Priority: 1, 62 | }, 63 | } 64 | if err := p.Rcv(msg, ctx); err != nil { 65 | t.Errorf("cannot install flows for path: %v", err) 66 | } 67 | if len(ctx.CtxMsgs) == 0 { 68 | t.Error("no flows installed") 69 | } 70 | 71 | iports := []nom.UID{"n1$$0", "n2$$1", "n4$$1", "n6$$1"} 72 | oports := []nom.UID{"n1$$1", "n2$$2", "n4$$2", "n6$$3"} 73 | for i, msg := range ctx.CtxMsgs { 74 | add := msg.Data().(nom.AddFlowEntry) 75 | if add.Flow.Priority != 1 { 76 | t.Errorf("invalid flow priority: actual=%v want=1", add.Flow.Priority) 77 | } 78 | iport, ok := add.Flow.Match.InPort() 79 | if !ok { 80 | t.Errorf("flow #%v has no in ports", i) 81 | } else if nom.UID(iport) != iports[i] { 82 | t.Errorf("invalid input port on flow #%v: actual=%v want=%v", i, iport, 83 | iports[i]) 84 | } 85 | 86 | oport := add.Flow.Actions[0].(nom.ActionForward).Ports[0] 87 | if oport != oports[i] { 88 | t.Errorf("invalid output port on flow #%v: actual=%v want=%v", i, oport, 89 | oports[i]) 90 | } 91 | } 92 | } 93 | 94 | func TestAddL2Path(t *testing.T) { 95 | ctx := buildTopologyForTest() 96 | p := addHandler{} 97 | msg := &bh.MockMsg{ 98 | MsgData: nom.AddPath{}, 99 | } 100 | err := p.Rcv(msg, ctx) 101 | if err == nil { 102 | t.Error("no error on invalid path") 103 | } 104 | ethDst := nom.EthDst{ 105 | Addr: nom.MACAddr{1, 2, 3, 4, 5, 6}, 106 | Mask: nom.MaskNoneMAC, 107 | } 108 | msg.MsgData = nom.AddPath{ 109 | Path: nom.Path{ 110 | Pathlets: []nom.Pathlet{ 111 | { 112 | Match: nom.Match{ 113 | Fields: []nom.Field{ 114 | ethDst, 115 | }, 116 | }, 117 | Actions: []nom.Action{ 118 | nom.ActionForward{ 119 | Ports: []nom.UID{"n6$$3"}, 120 | }, 121 | }, 122 | }, 123 | }, 124 | Priority: 1, 125 | }, 126 | } 127 | if err := p.Rcv(msg, ctx); err != nil { 128 | t.Errorf("cannot install flows for path: %v", err) 129 | } 130 | if len(ctx.CtxMsgs) == 0 { 131 | t.Error("no flows installed") 132 | } 133 | 134 | out := map[nom.UID]nom.UID{ 135 | "n1": "n1$$1", 136 | "n2": "n2$$2", 137 | "n3": "n3$$2", 138 | "n4": "n4$$2", 139 | "n5": "n5$$2", 140 | "n6": "n6$$3", 141 | } 142 | for i, msg := range ctx.CtxMsgs { 143 | add := msg.Data().(nom.AddFlowEntry) 144 | if add.Flow.Priority != 1 { 145 | t.Errorf("invalid flow priority: actual=%v want=1", add.Flow.Priority) 146 | } 147 | fethDst, ok := add.Flow.Match.EthDst() 148 | if !ok { 149 | t.Errorf("flow #%v has no destination eth addr", i) 150 | } else if !fethDst.Equals(ethDst) { 151 | t.Errorf("invalid eth address for flow #%v: actual=%v want=%v", i, 152 | fethDst, ethDst) 153 | } 154 | 155 | delete(out, add.Flow.Node) 156 | } 157 | 158 | for _, p := range out { 159 | t.Errorf("no flow installed on port %v", p) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /controller/node.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 8 | 9 | bh "github.com/kandoo/beehive" 10 | "github.com/kandoo/beehive-netctrl/nom" 11 | ) 12 | 13 | type nodeConnectedHandler struct{} 14 | 15 | func (h nodeConnectedHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 16 | nc := msg.Data().(nom.NodeConnected) 17 | 18 | ddict := ctx.Dict(driversDict) 19 | k := string(nc.Node.ID) 20 | n := nodeDrivers{ 21 | Node: nc.Node, 22 | } 23 | if v, err := ddict.Get(k); err == nil { 24 | n = v.(nodeDrivers) 25 | } 26 | 27 | if _, ok := n.driver(nc.Driver); ok { 28 | return fmt.Errorf("driver %v reconnects to %v", nc.Driver, n.Node) 29 | } 30 | 31 | gdict := ctx.Dict(genDict) 32 | gen := uint64(0) 33 | if v, err := gdict.Get("gen"); err == nil { 34 | gen = v.(uint64) 35 | } 36 | gen++ 37 | 38 | db := nc.Driver.BeeID 39 | if len(n.Drivers) == 0 { 40 | nc.Driver.Role = nom.DriverRoleMaster 41 | ctx.Emit(nom.NodeJoined(nc.Node)) 42 | glog.V(2).Infof("%v connected to master controller", nc.Node) 43 | } else { 44 | nc.Driver.Role = nom.DriverRoleSlave 45 | glog.V(2).Infof("%v connected to slave controller", nc.Node) 46 | } 47 | n.Drivers = append(n.Drivers, driverInfo{ 48 | Driver: nc.Driver, 49 | LastSeen: time.Now(), 50 | }) 51 | 52 | ctx.SendToBee(nom.ChangeDriverRole{ 53 | Node: nc.Node.UID(), 54 | Role: nc.Driver.Role, 55 | Generation: gen, 56 | }, db) 57 | 58 | gdict.Put("gen", gen) 59 | return ddict.Put(k, n) 60 | } 61 | 62 | func (h nodeConnectedHandler) Map(msg bh.Msg, 63 | ctx bh.MapContext) bh.MappedCells { 64 | 65 | nc := msg.Data().(nom.NodeConnected) 66 | return bh.MappedCells{{driversDict, string(nc.Node.ID)}} 67 | } 68 | 69 | type nodeDisconnectedHandler struct{} 70 | 71 | func (h nodeDisconnectedHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 72 | dc := msg.Data().(nom.NodeDisconnected) 73 | d := ctx.Dict(driversDict) 74 | k := string(dc.Node.ID) 75 | v, err := d.Get(k) 76 | if err != nil { 77 | return fmt.Errorf("driver %v disconnects from %v before connecting", 78 | dc.Driver, dc.Node) 79 | } 80 | nd := v.(nodeDrivers) 81 | 82 | ndd, ok := nd.driver(dc.Driver) 83 | if !ok { 84 | return fmt.Errorf("driver %v disconnects from %v before connecting", 85 | dc.Driver, dc.Node) 86 | } 87 | nd.removeDriver(dc.Driver) 88 | 89 | if len(nd.Drivers) == 0 { 90 | ctx.Emit(nom.NodeLeft(nd.Node)) 91 | return d.Del(k) 92 | } 93 | 94 | if ndd.Role == nom.DriverRoleMaster { 95 | // TODO(soheil): Maybe a smarter load balacning technique. 96 | gdict := ctx.Dict(genDict) 97 | gen := uint64(0) 98 | if v, err := gdict.Get("gen"); err == nil { 99 | gen = v.(uint64) 100 | } 101 | gen++ 102 | 103 | nd.Drivers[0].Role = nom.DriverRoleMaster 104 | ctx.SendToBee(nom.ChangeDriverRole{ 105 | Node: nd.Node.UID(), 106 | Role: nom.DriverRoleMaster, 107 | Generation: gen, 108 | }, nd.master().BeeID) 109 | } 110 | 111 | return d.Put(k, nd) 112 | } 113 | 114 | func (h nodeDisconnectedHandler) Map(msg bh.Msg, 115 | ctx bh.MapContext) bh.MappedCells { 116 | 117 | nd := msg.Data().(nom.NodeDisconnected) 118 | return bh.MappedCells{{driversDict, string(nd.Node.ID)}} 119 | } 120 | 121 | type roleUpdateHandler struct{} 122 | 123 | func (h roleUpdateHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 124 | ru := msg.Data().(nom.DriverRoleUpdate) 125 | 126 | ddict := ctx.Dict(driversDict) 127 | k := string(ru.Node) 128 | v, err := ddict.Get(k) 129 | if err != nil { 130 | return fmt.Errorf("role update received before node %v connects", ru.Node) 131 | } 132 | n := v.(nodeDrivers) 133 | 134 | found := false 135 | for i := range n.Drivers { 136 | if n.Drivers[i].BeeID == ru.Driver.BeeID { 137 | found = true 138 | n.Drivers[i].Role = ru.Driver.Role 139 | break 140 | } 141 | } 142 | 143 | if !found { 144 | return fmt.Errorf("role update received before driver %v connects", 145 | ru.Driver) 146 | } 147 | 148 | gdict := ctx.Dict(genDict) 149 | gen := uint64(0) 150 | if v, err := gdict.Get("gen"); err == nil { 151 | gen = v.(uint64) 152 | } 153 | if ru.Generation > gen { 154 | gdict.Put("gen", ru.Generation) 155 | } 156 | 157 | return nil 158 | } 159 | 160 | func (h roleUpdateHandler) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 161 | ru := msg.Data().(nom.DriverRoleUpdate) 162 | return bh.MappedCells{{driversDict, string(ru.Node)}} 163 | } 164 | -------------------------------------------------------------------------------- /controller/dicts.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "encoding/gob" 5 | "encoding/json" 6 | "fmt" 7 | "time" 8 | 9 | bh "github.com/kandoo/beehive" 10 | "github.com/kandoo/beehive-netctrl/nom" 11 | "github.com/kandoo/beehive/state" 12 | ) 13 | 14 | const ( 15 | driversDict = "ND" 16 | genDict = "GD" 17 | triggersDict = "TD" 18 | flowsDict = "FD" 19 | ) 20 | 21 | type driverInfo struct { 22 | nom.Driver // The driver. 23 | LastSeen time.Time // The timestamp of the last pong. 24 | OutPings int // Number of pings on the fly. 25 | } 26 | 27 | type nodeDrivers struct { 28 | Node nom.Node 29 | Drivers []driverInfo 30 | Ports nom.Ports 31 | } 32 | 33 | func (nd *nodeDrivers) UID() nom.UID { 34 | return nd.Node.UID() 35 | } 36 | 37 | func (nd *nodeDrivers) JSONDecode(b []byte) error { 38 | return json.Unmarshal(b, nd) 39 | } 40 | 41 | func (nd *nodeDrivers) JSONEncode() ([]byte, error) { 42 | return json.Marshal(nd) 43 | } 44 | 45 | func (nd nodeDrivers) driver(d nom.Driver) (driverInfo, bool) { 46 | for _, e := range nd.Drivers { 47 | if e.BeeID == d.BeeID { 48 | return e, true 49 | } 50 | } 51 | 52 | return driverInfo{}, false 53 | } 54 | 55 | func (nd *nodeDrivers) removeDriver(d nom.Driver) bool { 56 | for i, e := range nd.Drivers { 57 | if e.BeeID == d.BeeID { 58 | nd.Drivers = append(nd.Drivers[:i], nd.Drivers[i+1:]...) 59 | return true 60 | } 61 | } 62 | return false 63 | } 64 | 65 | func (nd *nodeDrivers) master() driverInfo { 66 | if len(nd.Drivers) == 0 { 67 | return driverInfo{} 68 | } 69 | 70 | for _, d := range nd.Drivers { 71 | if d.Role == nom.DriverRoleMaster { 72 | return d 73 | } 74 | } 75 | return nd.Drivers[0] 76 | } 77 | 78 | func (nd *nodeDrivers) isMaster(d nom.Driver) bool { 79 | return nd.master().BeeID == d.BeeID 80 | } 81 | 82 | func nodeDriversMap(node nom.UID) bh.MappedCells { 83 | return bh.MappedCells{{driversDict, string(node)}} 84 | } 85 | 86 | func sendToMaster(msg interface{}, node nom.UID, ctx bh.RcvContext) error { 87 | d := ctx.Dict(driversDict) 88 | v, err := d.Get(string(node)) 89 | if err != nil { 90 | if err == state.ErrNoSuchKey { 91 | return fmt.Errorf("nom: cannot find node %s", node) 92 | } 93 | return err 94 | } 95 | nd := v.(nodeDrivers) 96 | ctx.SendToBee(msg, nd.master().BeeID) 97 | return nil 98 | } 99 | 100 | type nodeTriggers struct { 101 | Node nom.Node 102 | Triggers []nom.Trigger 103 | } 104 | 105 | func (nt nodeTriggers) hasTrigger(trigger nom.Trigger) bool { 106 | for _, t := range nt.Triggers { 107 | if t.Equals(trigger) { 108 | return true 109 | } 110 | } 111 | return false 112 | } 113 | 114 | func (nt *nodeTriggers) maybeAddTrigger(t nom.Trigger) bool { 115 | if nt.hasTrigger(t) { 116 | return false 117 | } 118 | nt.Triggers = append(nt.Triggers, t) 119 | return true 120 | } 121 | 122 | func (nt *nodeTriggers) delTrigger(t nom.Trigger) { 123 | panic("todo: implement delTrigger") 124 | } 125 | 126 | func newTriggered(t nom.Trigger, d time.Duration, 127 | bw nom.Bandwidth) nom.Triggered { 128 | 129 | return nom.Triggered{ 130 | Node: t.Node, 131 | Match: t.Match, 132 | Duration: d, 133 | Bandwidth: bw, 134 | } 135 | } 136 | 137 | type flow struct { 138 | FlowEntry nom.FlowEntry 139 | FlowSubscribers []bh.AppCellKey 140 | Duration time.Duration 141 | Packets uint64 142 | Bytes uint64 143 | } 144 | 145 | func (f flow) bw() nom.Bandwidth { 146 | return nom.Bandwidth(f.Bytes / uint64(f.Duration)) 147 | } 148 | 149 | func (f *flow) updateStats(stats nom.FlowStats) { 150 | f.Duration = stats.Duration 151 | f.Bytes = stats.Bytes 152 | f.Packets = stats.Packets 153 | } 154 | 155 | func (f flow) hasFlowSubscriber(sub bh.AppCellKey) bool { 156 | for _, s := range f.FlowSubscribers { 157 | if s == sub { 158 | return true 159 | } 160 | } 161 | return false 162 | } 163 | 164 | func (f *flow) maybeAddFlowSubscriber(sub bh.AppCellKey) bool { 165 | if f.hasFlowSubscriber(sub) { 166 | return false 167 | } 168 | f.FlowSubscribers = append(f.FlowSubscribers, sub) 169 | return true 170 | } 171 | 172 | type nodeFlows struct { 173 | Node nom.Node 174 | Flows []flow 175 | } 176 | 177 | func (nf *nodeFlows) flowIndex(flow nom.FlowEntry) int { 178 | for i := range nf.Flows { 179 | if flow.Equals(nf.Flows[i].FlowEntry) { 180 | return i 181 | } 182 | } 183 | return -1 184 | } 185 | 186 | func (nf *nodeFlows) maybeAddFlow(add nom.AddFlowEntry) bool { 187 | i := nf.flowIndex(add.Flow) 188 | if i < 0 { 189 | f := flow{ 190 | FlowEntry: add.Flow, 191 | FlowSubscribers: []bh.AppCellKey{add.Subscriber}, 192 | } 193 | nf.Flows = append(nf.Flows, f) 194 | return true 195 | } 196 | return nf.Flows[i].maybeAddFlowSubscriber(add.Subscriber) 197 | } 198 | 199 | func init() { 200 | gob.Register(driverInfo{}) 201 | gob.Register(flow{}) 202 | gob.Register(nodeDrivers{}) 203 | gob.Register(nodeFlows{}) 204 | gob.Register(nodeTriggers{}) 205 | } 206 | -------------------------------------------------------------------------------- /openflow/handshake.go: -------------------------------------------------------------------------------- 1 | package openflow 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/kandoo/beehive-netctrl/nom" 7 | "github.com/kandoo/beehive-netctrl/openflow/of" 8 | "github.com/kandoo/beehive-netctrl/openflow/of10" 9 | "github.com/kandoo/beehive-netctrl/openflow/of12" 10 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 11 | ) 12 | 13 | func (c *ofConn) handshake() (ofDriver, error) { 14 | hdr, err := c.ReadHeader() 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | h, err := of.ToHello(hdr) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | glog.V(2).Info("%v received hello from a switch with OFv%v", c.ctx, 25 | h.Version()) 26 | 27 | version := of.OPENFLOW_1_0 28 | if h.Version() >= uint8(of.OPENFLOW_1_2) { 29 | version = of.OPENFLOW_1_2 30 | } 31 | h.SetVersion(uint8(version)) 32 | 33 | if err = c.WriteHeader(h.Header); err != nil { 34 | return nil, err 35 | } 36 | c.Flush() 37 | 38 | glog.V(2).Info("%v sent hello to the switch", c.ctx) 39 | 40 | var driver ofDriver 41 | switch version { 42 | case of.OPENFLOW_1_0: 43 | driver = &of10Driver{} 44 | case of.OPENFLOW_1_2: 45 | driver = &of12Driver{} 46 | } 47 | 48 | if err = driver.handshake(c); err != nil { 49 | return nil, err 50 | } 51 | 52 | if c.node.ID == nom.NodeID(0) { 53 | return nil, errors.New("ofConn: invalid node after handshake") 54 | } 55 | 56 | return driver, nil 57 | } 58 | 59 | func (d *of10Driver) handshake(c *ofConn) error { 60 | freq := of10.NewFeaturesRequest() 61 | if err := c.WriteHeader(freq.Header); err != nil { 62 | return err 63 | } 64 | 65 | c.Flush() 66 | 67 | glog.V(2).Info("%v sent features request to the switch", c.ctx) 68 | 69 | hdr, err := c.ReadHeader() 70 | if err != nil { 71 | return err 72 | } 73 | 74 | v10, err := of10.ToHeader10(hdr) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | frep, err := of10.ToFeaturesReply(v10) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | glog.Infof("%v completes handshaking with switch %016x", c.ctx, 85 | frep.DatapathId()) 86 | glog.Infof("%v disables packet buffers in switch %016x", c.ctx, 87 | frep.DatapathId()) 88 | cfg := of10.NewSwitchSetConfig() 89 | cfg.SetMissSendLen(0xFFFF) 90 | c.WriteHeader(cfg.Header) 91 | 92 | nodeID := datapathIDToNodeID(frep.DatapathId()) 93 | c.node = nom.Node{ 94 | ID: nodeID, 95 | MACAddr: datapathIDToMACAddr(frep.DatapathId()), 96 | Capabilities: nil, 97 | } 98 | glog.Infof("%v is connected to %v", c.ctx, c.node) 99 | 100 | nomDriver := nom.Driver{ 101 | BeeID: c.ctx.ID(), 102 | Role: nom.DriverRoleDefault, 103 | } 104 | 105 | c.ctx.Emit(nom.NodeConnected{ 106 | Node: c.node, 107 | Driver: nomDriver, 108 | }) 109 | 110 | d.ofPorts = make(map[uint16]*nom.Port) 111 | d.nomPorts = make(map[nom.UID]uint16) 112 | for _, p := range frep.Ports() { 113 | name := p.Name() 114 | port := nom.Port{ 115 | ID: portNoToPortID(uint32(p.PortNo())), 116 | Name: string(name[:]), 117 | MACAddr: p.HwAddr(), 118 | Node: c.NodeUID(), 119 | } 120 | d.ofPorts[p.PortNo()] = &port 121 | d.nomPorts[port.UID()] = p.PortNo() 122 | glog.Infof("%v added", port) 123 | if p.PortNo() <= uint16(of10.PP_MAX) { 124 | c.ctx.Emit(nom.PortStatusChanged{ 125 | Port: port, 126 | Driver: nomDriver, 127 | }) 128 | } 129 | } 130 | return nil 131 | } 132 | 133 | func (d *of12Driver) handshake(c *ofConn) error { 134 | freq := of12.NewFeaturesRequest() 135 | if err := c.WriteHeader(freq.Header); err != nil { 136 | return err 137 | } 138 | c.Flush() 139 | 140 | glog.V(2).Info("Sent features request to the switch") 141 | 142 | hdr, err := c.ReadHeader() 143 | if err != nil { 144 | return err 145 | } 146 | 147 | v12, err := of12.ToHeader12(hdr) 148 | if err != nil { 149 | return err 150 | } 151 | 152 | frep, err := of12.ToFeaturesReply(v12) 153 | if err != nil { 154 | return err 155 | } 156 | 157 | glog.Infof("Handshake completed for switch %016x", frep.DatapathId()) 158 | 159 | glog.Infof("Disabling packet buffers in the switch.") 160 | cfg := of12.NewSwitchSetConfig() 161 | cfg.SetMissSendLen(0xFFFF) 162 | c.WriteHeader(cfg.Header) 163 | 164 | nodeID := datapathIDToNodeID(frep.DatapathId()) 165 | c.node = nom.Node{ 166 | ID: nodeID, 167 | MACAddr: datapathIDToMACAddr(frep.DatapathId()), 168 | Capabilities: []nom.NodeCapability{ 169 | nom.CapDriverRole, 170 | }, 171 | } 172 | 173 | nomDriver := nom.Driver{ 174 | BeeID: c.ctx.ID(), 175 | Role: nom.DriverRoleDefault, 176 | } 177 | c.ctx.Emit(nom.NodeConnected{ 178 | Node: c.node, 179 | Driver: nomDriver, 180 | }) 181 | 182 | d.ofPorts = make(map[uint32]*nom.Port) 183 | d.nomPorts = make(map[nom.UID]uint32) 184 | for _, p := range frep.Ports() { 185 | if p.PortNo() > uint32(of12.PP_MAX) { 186 | continue 187 | } 188 | name := p.Name() 189 | port := nom.Port{ 190 | ID: portNoToPortID(p.PortNo()), 191 | Name: string(name[:]), 192 | MACAddr: p.HwAddr(), 193 | Node: c.NodeUID(), 194 | } 195 | d.ofPorts[p.PortNo()] = &port 196 | d.nomPorts[port.UID()] = p.PortNo() 197 | glog.Infof("%v added", port) 198 | c.ctx.Emit(nom.PortStatusChanged{ 199 | Port: port, 200 | Driver: nomDriver, 201 | }) 202 | } 203 | 204 | return nil 205 | } 206 | -------------------------------------------------------------------------------- /nom/port.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import ( 4 | "encoding/gob" 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | // PortUpdated is a high-level event emitted when a port is added, removed, or 10 | // its state/configuration is changed. 11 | type PortUpdated Port 12 | 13 | // PortStatusChanged is emitted when a driver receives a port status 14 | type PortStatusChanged struct { 15 | Port Port 16 | Driver Driver 17 | } 18 | 19 | // Port is either a physical or a virtual port of a node. 20 | type Port struct { 21 | ID PortID // ID is unique among the ports of this node. 22 | Name string // Human-readable name of the port. 23 | MACAddr MACAddr // Hardware address of the port. 24 | Node UID // The node. 25 | Link UID // The outgoing link. 26 | State PortState // Is the state of the port. 27 | Config PortConfig // Is the configuration of the port. 28 | Feature PortFeature // Features of this port. 29 | } 30 | 31 | func (p Port) String() string { 32 | return fmt.Sprintf("Port (node=%v, id=%v, addr=%v)", p.Node, p.ID, p.MACAddr) 33 | } 34 | 35 | // PortID is the ID of a port and is unique among the ports of a node. 36 | type PortID string 37 | 38 | // UID returns the unique ID of the port in the form of 39 | // net_id$$node_id$$port_id. 40 | func (p Port) UID() UID { 41 | return UIDJoin(string(p.Node), string(p.ID)) 42 | } 43 | 44 | // ParsePortUID parses a UID of a port and returns the respective node and port 45 | // IDs. 46 | func ParsePortUID(id UID) (NodeID, PortID) { 47 | s := UIDSplit(id) 48 | return NodeID(s[0]), PortID(s[1]) 49 | } 50 | 51 | // NodeFromPortUID returns the node UID from the port's UID. 52 | func NodeFromPortUID(port UID) (node UID) { 53 | n, _ := ParsePortUID(port) 54 | return n.UID() 55 | } 56 | 57 | // JSONDecode decodes the port from a byte array using JSON. 58 | func (p *Port) JSONDecode(b []byte) error { 59 | return json.Unmarshal(b, p) 60 | } 61 | 62 | // JSONEncode encodes the port into a byte array using JSON. 63 | func (p *Port) JSONEncode() ([]byte, error) { 64 | return json.Marshal(p) 65 | } 66 | 67 | // Ports is a slice of ports with useful auxilaries. 68 | type Ports []Port 69 | 70 | // GetPort retrieves a port by its ID, and returns false if no port is found. 71 | func (ports Ports) GetPort(id UID) (Port, bool) { 72 | for _, p := range ports { 73 | if p.UID() == id { 74 | return p, true 75 | } 76 | } 77 | return Port{}, false 78 | } 79 | 80 | // HasPort returns whether port is in ports. 81 | func (ports Ports) HasPort(port Port) bool { 82 | _, ok := ports.GetPort(port.UID()) 83 | return ok 84 | } 85 | 86 | // AddPort adds p to ports. 87 | func (ports *Ports) AddPort(p Port) { 88 | *ports = append(*ports, p) 89 | } 90 | 91 | // DelPort deletes port from ports. If there is no such port, it returns false. 92 | func (ports *Ports) DelPort(port Port) bool { 93 | id := port.UID() 94 | for i, p := range *ports { 95 | if p.UID() == id { 96 | *ports = append((*ports)[:i], (*ports)[i+1:]...) 97 | return true 98 | } 99 | } 100 | return false 101 | } 102 | 103 | // PortState is the current state of a port. 104 | type PortState uint8 105 | 106 | // Valid values for PortState. 107 | const ( 108 | PortStateUnknown PortState = iota // Port's state is unknown. 109 | PortStateDown = iota // Port is not connected to any link. 110 | PortStateUp = iota // Port is up and forwarding packets. 111 | PortStateBlocked = iota // Port is blocked. 112 | ) 113 | 114 | // PortConfig is the NOM specific configuration of the port. 115 | type PortConfig uint8 116 | 117 | // Valid values for PortConfig. 118 | const ( 119 | PortConfigDown PortConfig = 1 << iota // Down. 120 | PortConfigDropPackets = 1 << iota // Drop incoming packets. 121 | PortConfigNoForward = 1 << iota // Do not forward packets. 122 | PortConfigNoFlood = 1 << iota // Do not include in flood. 123 | PortConfigNoPacketIn = 1 << iota // Do not send packet ins. 124 | PortConfigDisableStp = 1 << iota // Disable STP. 125 | PortConfigDropStp = 1 << iota // Drop STP packets. 126 | ) 127 | 128 | // PortFeature represents port features. 129 | type PortFeature uint16 130 | 131 | // Valid values for PortFeature 132 | const ( 133 | PortFeature10MBHD PortFeature = 1 << iota // 10MB half-duplex. 134 | PortFeature10MBFD = 1 << iota // 10MB full-duplex. 135 | PortFeature100MBHD = 1 << iota // 100MB half-duplex. 136 | PortFeature100MBFD = 1 << iota // 100MB half-duplex. 137 | PortFeature1GBHD = 1 << iota // 1GB half-duplex. 138 | PortFeature1GBFD = 1 << iota // 1GB half-duplex. 139 | PortFeature10GBHD = 1 << iota // 10GB half-duplex. 140 | PortFeature10GBFD = 1 << iota // 10GB half-duplex. 141 | PortFeatureCopper = 1 << iota // Copper. 142 | PortFeatureFiber = 1 << iota // Fiber. 143 | PortFeatureAutoneg = 1 << iota // Auto negotiation. 144 | PortPause = 1 << iota // Pause. 145 | PortPauseAsym = 1 << iota // Asymmetric pause. 146 | ) 147 | 148 | func init() { 149 | gob.Register(Port{}) 150 | gob.Register(PortID("")) 151 | gob.Register(PortStatusChanged{}) 152 | gob.Register(PortUpdated{}) 153 | } 154 | -------------------------------------------------------------------------------- /net/ethernet/ethernet.go: -------------------------------------------------------------------------------- 1 | // Automatically generated by Packet Go code generator. 2 | package ethernet 3 | 4 | import ( 5 | "bufio" 6 | "bytes" 7 | "encoding/binary" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "net" 12 | 13 | "github.com/packet/packet/src/go/packet" 14 | ) 15 | 16 | type EtherType int 17 | 18 | const ( 19 | ETH_T_LLDP EtherType = 35020 20 | ) 21 | 22 | func NewEthernetWithBuf(b []byte) Ethernet { 23 | return Ethernet{packet.Packet{Buf: b}} 24 | } 25 | 26 | func NewEthernet() Ethernet { 27 | s := 14 28 | b := make([]byte, s) 29 | p := Ethernet{packet.Packet{Buf: b}} 30 | p.Init() 31 | return p 32 | } 33 | 34 | type Ethernet struct { 35 | packet.Packet 36 | } 37 | 38 | func (this Ethernet) minSize() int { 39 | return 14 40 | } 41 | 42 | func (this Ethernet) Clone() (Ethernet, error) { 43 | var newBuf bytes.Buffer 44 | _, err := io.CopyN(&newBuf, bytes.NewBuffer(this.Buf), int64(this.Size())) 45 | if err != nil { 46 | return NewEthernet(), err 47 | } 48 | 49 | return NewEthernetWithBuf(newBuf.Bytes()), nil 50 | } 51 | 52 | type EthernetConn struct { 53 | net.Conn 54 | w *bufio.Writer 55 | buf []byte 56 | offset int 57 | } 58 | 59 | func NewEthernetConn(c net.Conn) EthernetConn { 60 | return EthernetConn{ 61 | Conn: c, 62 | w: bufio.NewWriter(c), 63 | buf: make([]byte, packet.DefaultBufSize), 64 | } 65 | } 66 | 67 | func (c *EthernetConn) WriteEthernet(pkt Ethernet) error { 68 | s := pkt.Size() 69 | b := pkt.Buffer()[:s] 70 | n := 0 71 | for s > 0 { 72 | var err error 73 | if n, err = c.w.Write(b); err != nil { 74 | return fmt.Errorf("Error in write: %v", err) 75 | } 76 | s -= n 77 | } 78 | 79 | return nil 80 | } 81 | 82 | func (c *EthernetConn) WriteEthernets(pkts []Ethernet) error { 83 | for _, p := range pkts { 84 | if err := c.WriteEthernet(p); err != nil { 85 | return err 86 | } 87 | } 88 | return nil 89 | } 90 | 91 | func (c *EthernetConn) Flush() error { 92 | return c.w.Flush() 93 | } 94 | 95 | func (c *EthernetConn) ReadEthernet() (Ethernet, error) { 96 | pkts := make([]Ethernet, 1) 97 | _, err := c.ReadEthernets(pkts) 98 | if err != nil { 99 | return NewEthernet(), err 100 | } 101 | 102 | return pkts[0], nil 103 | } 104 | 105 | func (c *EthernetConn) ReadEthernets(pkts []Ethernet) (int, error) { 106 | if len(c.buf) == c.offset { 107 | newSize := packet.DefaultBufSize 108 | if newSize < len(c.buf) { 109 | newSize = 2 * len(c.buf) 110 | } 111 | 112 | buf := make([]byte, newSize) 113 | copy(buf, c.buf[:c.offset]) 114 | c.buf = buf 115 | } 116 | 117 | r, err := c.Conn.Read(c.buf[c.offset:]) 118 | if err != nil { 119 | return 0, err 120 | } 121 | 122 | r += c.offset 123 | 124 | s := 0 125 | n := 0 126 | for i := range pkts { 127 | p := NewEthernetWithBuf(c.buf[s:]) 128 | 129 | pSize := p.Size() 130 | if pSize == 0 || r < s+pSize { 131 | break 132 | } 133 | 134 | pkts[i] = p 135 | s += pSize 136 | n++ 137 | } 138 | 139 | c.offset = r - s 140 | if c.offset < 0 { 141 | panic("Invalid value for offset") 142 | } 143 | 144 | c.buf = c.buf[s:] 145 | return n, nil 146 | } 147 | 148 | func (this *Ethernet) Init() { 149 | // Invariants. 150 | } 151 | 152 | func (this Ethernet) Size() int { 153 | return 14 154 | } 155 | 156 | func ToEthernet(p packet.Packet) (Ethernet, error) { 157 | if !IsEthernet(p) { 158 | return NewEthernetWithBuf(nil), errors.New("Cannot convert to ethernet.Ethernet") 159 | } 160 | 161 | return NewEthernetWithBuf(p.Buf), nil 162 | } 163 | 164 | func IsEthernet(p packet.Packet) bool { 165 | return true 166 | } 167 | 168 | func (this Ethernet) DstMac() [6]uint8 { 169 | offset := this.DstMacOffset() 170 | packet_size := this.Size() 171 | size := packet_size - offset 172 | count := 6 173 | i := 0 174 | var res [6]uint8 175 | for size > 0 && count > 0 && packet_size > offset { 176 | elem := uint8(this.Buf[offset]) 177 | if size < 1 { 178 | break 179 | } 180 | size -= 1 181 | offset += 1 182 | count-- 183 | res[i] = elem 184 | i++ 185 | } 186 | return res 187 | } 188 | 189 | func (this *Ethernet) SetDstMac(d [6]uint8) { 190 | offset := this.DstMacOffset() 191 | for _, e := range d { 192 | this.Buf[offset] = byte(e) 193 | offset++ 194 | } 195 | } 196 | 197 | func (this Ethernet) DstMacOffset() int { 198 | offset := 0 199 | return offset 200 | } 201 | 202 | func (this Ethernet) SrcMac() [6]uint8 { 203 | offset := this.SrcMacOffset() 204 | packet_size := this.Size() 205 | size := packet_size - offset 206 | count := 6 207 | i := 0 208 | var res [6]uint8 209 | for size > 0 && count > 0 && packet_size > offset { 210 | elem := uint8(this.Buf[offset]) 211 | if size < 1 { 212 | break 213 | } 214 | size -= 1 215 | offset += 1 216 | count-- 217 | res[i] = elem 218 | i++ 219 | } 220 | return res 221 | } 222 | 223 | func (this *Ethernet) SetSrcMac(s [6]uint8) { 224 | offset := this.SrcMacOffset() 225 | for _, e := range s { 226 | this.Buf[offset] = byte(e) 227 | offset++ 228 | } 229 | } 230 | 231 | func (this Ethernet) SrcMacOffset() int { 232 | offset := 6 233 | return offset 234 | } 235 | 236 | func (this Ethernet) Type() uint16 { 237 | offset := this.TypeOffset() 238 | res := binary.BigEndian.Uint16(this.Buf[offset:]) 239 | return res 240 | } 241 | 242 | func (this *Ethernet) SetType(t uint16) { 243 | offset := this.TypeOffset() 244 | binary.BigEndian.PutUint16(this.Buf[offset:], t) 245 | offset += 2 246 | } 247 | 248 | func (this Ethernet) TypeOffset() int { 249 | offset := 12 250 | return offset 251 | } 252 | -------------------------------------------------------------------------------- /path/path.go: -------------------------------------------------------------------------------- 1 | package path 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | bh "github.com/kandoo/beehive" 8 | "github.com/kandoo/beehive-netctrl/discovery" 9 | "github.com/kandoo/beehive-netctrl/nom" 10 | ) 11 | 12 | type addHandler struct{} 13 | 14 | func (h addHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 15 | add := msg.Data().(nom.AddPath) 16 | 17 | if len(add.Path.Pathlets) == 0 { 18 | return errors.New("path: path has no pathlets") 19 | } 20 | 21 | flows := make([]nom.FlowEntry, 0, len(add.Path.Pathlets)) 22 | // TODO(soheil): maybe detect loops in pathlets? 23 | var outports []nom.UID 24 | var newflows []nom.FlowEntry 25 | var err error 26 | for _, p := range add.Path.Pathlets { 27 | if len(outports) == 0 { 28 | newflows, outports, err = genFlowsForPathlet(p, nom.Nil, 29 | add.Path.Priority, ctx) 30 | if err != nil { 31 | return err 32 | } 33 | flows = append(flows, newflows...) 34 | continue 35 | } 36 | 37 | inps := inPortsFromOutPorts(outports, ctx) 38 | outports = outports[0:0] 39 | for _, inp := range inps { 40 | newflows, newoutports, err := genFlowsForPathlet(p, inp, 41 | add.Path.Priority, ctx) 42 | if err != nil { 43 | return err 44 | } 45 | outports = append(outports, newoutports...) 46 | flows = append(flows, newflows...) 47 | } 48 | } 49 | 50 | uniqueFlows := make([]nom.FlowEntry, 0, len(flows)) 51 | nextFlow: 52 | for i, fi := range flows { 53 | for j, fj := range flows { 54 | if i == j { 55 | continue 56 | } 57 | 58 | if fj.Subsumes(fi) { 59 | continue nextFlow 60 | } 61 | 62 | // TODO(soheil): check for subsumption and merge flows if possible. 63 | if j < i && fj.Equals(fi) { 64 | continue nextFlow 65 | } 66 | } 67 | uniqueFlows = append(uniqueFlows, fi) 68 | } 69 | addFlowEntriesForPath(add.Subscriber, add.Path, uniqueFlows, ctx) 70 | return nil 71 | } 72 | 73 | func (h addHandler) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 74 | return centralizedMap 75 | } 76 | 77 | func genFlowsForPathlet(p nom.Pathlet, inport nom.UID, priority uint16, 78 | ctx bh.RcvContext) (flows []nom.FlowEntry, outports []nom.UID, err error) { 79 | 80 | fwdnps := forwardNodes(p.Actions) 81 | for _, ports := range fwdnps { 82 | outports = append(outports, ports...) 83 | } 84 | floodns := floodNodes(p.Actions) 85 | for n, p := range floodns { 86 | outports = append(outports, outPortsFromFloodNode(n, p, ctx)...) 87 | } 88 | 89 | port, matchHasPort := p.Match.InPort() 90 | if matchHasPort { 91 | if inport != nom.Nil && inport != nom.UID(port) { 92 | return nil, nil, fmt.Errorf("path: two different inports %v and %v", 93 | inport, port) 94 | } 95 | inport = nom.UID(port) 96 | } 97 | 98 | m := p.Match 99 | if inport != nom.Nil && !matchHasPort { 100 | m = p.Match.Clone() 101 | m.Fields = append(m.Fields, nom.InPort(inport)) 102 | } 103 | 104 | noinMatch := p.Match.Clone() 105 | for f := range m.Fields { 106 | if _, ok := m.Fields[f].(nom.InPort); ok { 107 | noinMatch.Fields = append(noinMatch.Fields[:f], m.Fields[f+1:]...) 108 | break 109 | } 110 | } 111 | 112 | nofwdActions := make([]nom.Action, 0, len(p.Actions)) 113 | for _, a := range p.Actions { 114 | switch a.(type) { 115 | case nom.ActionForward, nom.ActionFlood: 116 | continue 117 | default: 118 | nofwdActions = append(nofwdActions, a) 119 | } 120 | } 121 | 122 | var innodes []nom.UID 123 | if inport != nom.Nil { 124 | innodes = []nom.UID{nom.NodeFromPortUID(inport)} 125 | } else { 126 | innodes = discovery.NodesCentralized(ctx) 127 | } 128 | 129 | for _, inn := range innodes { 130 | for _, outp := range outports { 131 | outn := nom.NodeFromPortUID(outp) 132 | sps, l := discovery.ShortestPathCentralized(inn, outn, ctx) 133 | if l < 0 { 134 | // TODO(soheil): maybe just log this and continue installing other 135 | // flows. 136 | return nil, nil, fmt.Errorf("path: no path found from %v to %v", inport, 137 | outp) 138 | } 139 | 140 | if l == 0 { 141 | m := noinMatch.Clone() 142 | if inport != nom.Nil { 143 | m.Fields = append(m.Fields, nom.InPort(inport)) 144 | } 145 | flow := nom.FlowEntry{ 146 | Node: outn, 147 | Match: m, 148 | Actions: p.Actions, 149 | Priority: priority, 150 | } 151 | flows = append(flows, flow) 152 | continue 153 | } 154 | 155 | // TODO(soheil): maybe install multiple paths. 156 | lastInPort := inport 157 | for _, link := range sps[0] { 158 | m := noinMatch.Clone() 159 | if lastInPort != nom.Nil { 160 | m.Fields = append(m.Fields, nom.InPort(lastInPort)) 161 | } 162 | 163 | var a []nom.Action 164 | a = append(a, nofwdActions...) 165 | a = append(a, nom.ActionForward{Ports: []nom.UID{link.From}}) 166 | 167 | flow := nom.FlowEntry{ 168 | Node: nom.NodeFromPortUID(link.From), 169 | Match: m, 170 | Actions: a, 171 | Priority: priority, 172 | } 173 | flows = append(flows, flow) 174 | 175 | lastInPort = link.To 176 | } 177 | 178 | m := noinMatch.Clone() 179 | if lastInPort != nom.Nil { 180 | m.Fields = append(m.Fields, nom.InPort(lastInPort)) 181 | } 182 | flow := nom.FlowEntry{ 183 | Node: outn, 184 | Match: m, 185 | Actions: p.Actions, 186 | Priority: priority, 187 | } 188 | flows = append(flows, flow) 189 | } 190 | } 191 | return flows, outports, nil 192 | } 193 | 194 | func forwardNodes(actions []nom.Action) (nodeToPorts map[nom.UID][]nom.UID) { 195 | nodeToPorts = make(map[nom.UID][]nom.UID) 196 | for _, a := range actions { 197 | switch f := a.(type) { 198 | case nom.ActionForward: 199 | for _, p := range f.Ports { 200 | fnid, _ := nom.ParsePortUID(p) 201 | ports := nodeToPorts[fnid.UID()] 202 | ports = append(ports, p) 203 | nodeToPorts[fnid.UID()] = ports 204 | } 205 | } 206 | } 207 | return nodeToPorts 208 | } 209 | 210 | func floodNodes(actions []nom.Action) (nodes map[nom.UID]nom.UID) { 211 | fn := make(map[nom.UID]nom.UID) 212 | for _, a := range actions { 213 | switch f := a.(type) { 214 | case nom.ActionFlood: 215 | nid := nom.NodeFromPortUID(f.InPort) 216 | fn[nid] = f.InPort 217 | } 218 | } 219 | return fn 220 | } 221 | -------------------------------------------------------------------------------- /discovery/graph.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "container/heap" 5 | "encoding/gob" 6 | "fmt" 7 | 8 | bh "github.com/kandoo/beehive" 9 | "github.com/kandoo/beehive-netctrl/nom" 10 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 11 | ) 12 | 13 | const ( 14 | GraphDict = "NetGraph" 15 | ) 16 | 17 | // GraphBuilderCentralized is a handler that builds a centralized graph of the 18 | // network topology. This handler is only useful for centralized applications. 19 | type GraphBuilderCentralized struct{} 20 | 21 | func (b GraphBuilderCentralized) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 22 | dict := ctx.Dict(GraphDict) 23 | var link nom.Link 24 | switch dm := msg.Data().(type) { 25 | case nom.LinkAdded: 26 | link = nom.Link(dm) 27 | case nom.LinkDeleted: 28 | link = nom.Link(dm) 29 | default: 30 | return fmt.Errorf("GraphBuilderCentralized: unsupported message type %v", 31 | msg.Type()) 32 | } 33 | 34 | nf, _ := nom.ParsePortUID(link.From) 35 | nt, _ := nom.ParsePortUID(link.To) 36 | 37 | if nf == nt { 38 | return fmt.Errorf("%v is a loop", link) 39 | } 40 | 41 | k := string(nf) 42 | links := make(map[nom.UID][]nom.Link) 43 | if v, err := dict.Get(k); err == nil { 44 | links = v.(map[nom.UID][]nom.Link) 45 | } 46 | links[nt.UID()] = append(links[nt.UID()], link) 47 | return dict.Put(k, links) 48 | } 49 | 50 | func (b GraphBuilderCentralized) Map(msg bh.Msg, 51 | ctx bh.MapContext) bh.MappedCells { 52 | 53 | var from nom.UID 54 | switch dm := msg.Data().(type) { 55 | case nom.LinkAdded: 56 | from = dm.From 57 | case nom.LinkDeleted: 58 | from = dm.From 59 | default: 60 | return nil 61 | } 62 | // TODO(soheil): maybe store and update the matrix directly here. 63 | n, _ := nom.ParsePortUID(from) 64 | return bh.MappedCells{{GraphDict, string(n)}} 65 | } 66 | 67 | // ShortestPathCentralized calculates the shortest path from node "from" to node 68 | // "to" according to the state stored in GraphDict by the 69 | // GraphBuilderCentralized. 70 | // 71 | // This method is not go-routine safe and must be called within a handler of the 72 | // application that uses the GraphBuilderCentralized as a handler. Otherwise, 73 | // the user needs to synchronize the two. 74 | func ShortestPathCentralized(from, to nom.UID, ctx bh.RcvContext) ( 75 | paths [][]nom.Link, length int) { 76 | 77 | if from == to { 78 | return nil, 0 79 | } 80 | 81 | visited := make(map[nom.UID]distAndLinks) 82 | visited[from] = distAndLinks{Dist: 0} 83 | 84 | pq := nodeAndDistSlice{{Dist: 0, Node: from}} 85 | heap.Init(&pq) 86 | 87 | dict := ctx.Dict(GraphDict) 88 | for len(pq) != 0 { 89 | nd := heap.Pop(&pq).(nodeAndDist) 90 | if nd.Node == to { 91 | continue 92 | } 93 | nodeLinks := make(map[nom.UID][]nom.Link) 94 | if v, err := dict.Get(string(nd.Node)); err == nil { 95 | nodeLinks = v.(map[nom.UID][]nom.Link) 96 | } 97 | nd.Dist = visited[nd.Node].Dist 98 | for _, links := range nodeLinks { 99 | for _, l := range links { 100 | nid, _ := nom.ParsePortUID(l.To) 101 | ton := nom.UID(nid) 102 | if dl, ok := visited[ton]; ok { 103 | switch { 104 | case nd.Dist+1 < dl.Dist: 105 | glog.Fatalf("invalid distance in BFS") 106 | case nd.Dist+1 == dl.Dist: 107 | dl.BackLinks = append(dl.BackLinks, l) 108 | visited[ton] = dl 109 | } 110 | continue 111 | } 112 | 113 | visited[ton] = distAndLinks{ 114 | Dist: nd.Dist + 1, 115 | BackLinks: []nom.Link{l}, 116 | } 117 | ndto := nodeAndDist{ 118 | Dist: nd.Dist + 1, 119 | Node: ton, 120 | } 121 | heap.Push(&pq, ndto) 122 | } 123 | } 124 | } 125 | return allPaths(from, to, visited) 126 | } 127 | 128 | // LinksCentralized returns links of node. 129 | // 130 | // Note that this method should be used only when the GraphBuilderCentralized is 131 | // in use. 132 | func LinksCentralized(node nom.UID, ctx bh.RcvContext) (links []nom.Link) { 133 | dict := ctx.Dict(GraphDict) 134 | v, err := dict.Get(string(node)) 135 | if err != nil { 136 | return nil 137 | } 138 | nodeLinks := v.(map[nom.UID][]nom.Link) 139 | 140 | for _, nl := range nodeLinks { 141 | links = append(links, nl...) 142 | } 143 | return links 144 | } 145 | 146 | // NodesCentralized returns the nodes with outgoing links so far. 147 | // 148 | // Note that this methods should be used only when the GraphBuilderCentralized 149 | // is in use. 150 | func NodesCentralized(ctx bh.RcvContext) (nodes []nom.UID) { 151 | ctx.Dict(GraphDict).ForEach(func(k string, v interface{}) bool { 152 | nodes = append(nodes, nom.UID(k)) 153 | return true 154 | }) 155 | return nodes 156 | } 157 | 158 | func allPaths(from, to nom.UID, visited map[nom.UID]distAndLinks) ( 159 | [][]nom.Link, int) { 160 | 161 | if from == to { 162 | return nil, 0 163 | } 164 | 165 | todl, ok := visited[to] 166 | if !ok { 167 | return nil, -1 168 | } 169 | 170 | var paths [][]nom.Link 171 | for _, l := range todl.BackLinks { 172 | lfn, _ := nom.ParsePortUID(l.From) 173 | prevpaths, _ := allPaths(from, nom.UID(lfn), visited) 174 | if len(prevpaths) == 0 { 175 | paths = append(paths, []nom.Link{l}) 176 | continue 177 | } 178 | for _, p := range prevpaths { 179 | paths = append(paths, append(p, l)) 180 | } 181 | } 182 | return paths, todl.Dist 183 | } 184 | 185 | type nodeAndDist struct { 186 | Dist int 187 | Node nom.UID 188 | } 189 | 190 | type nodeAndDistSlice []nodeAndDist 191 | 192 | func (s nodeAndDistSlice) Len() int { return len(s) } 193 | func (s nodeAndDistSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 194 | func (s nodeAndDistSlice) Less(i, j int) bool { return s[i].Dist < s[j].Dist } 195 | 196 | func (s *nodeAndDistSlice) Push(x interface{}) { 197 | *s = append(*s, x.(nodeAndDist)) 198 | } 199 | 200 | func (s *nodeAndDistSlice) Pop() interface{} { 201 | l := len(*s) - 1 202 | nd := (*s)[l] 203 | *s = (*s)[0:l] 204 | return nd 205 | } 206 | 207 | type distMatrix map[mentry]distAndLinks 208 | 209 | type mentry struct { 210 | From nom.UID 211 | To nom.UID 212 | } 213 | 214 | type distAndLinks struct { 215 | Dist int 216 | BackLinks []nom.Link 217 | } 218 | 219 | func init() { 220 | gob.Register(distAndLinks{}) 221 | gob.Register(distMatrix{}) 222 | gob.Register(GraphBuilderCentralized{}) 223 | gob.Register(mentry{}) 224 | gob.Register(nodeAndDist{}) 225 | gob.Register(nodeAndDistSlice{}) 226 | } 227 | -------------------------------------------------------------------------------- /discovery/discovery.go: -------------------------------------------------------------------------------- 1 | package discovery 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | bh "github.com/kandoo/beehive" 8 | "github.com/kandoo/beehive-netctrl/net/ethernet" 9 | "github.com/kandoo/beehive-netctrl/nom" 10 | "github.com/kandoo/beehive/Godeps/_workspace/src/github.com/golang/glog" 11 | ) 12 | 13 | const ( 14 | nodeDict = "N" 15 | ) 16 | 17 | type nodePortsAndLinks struct { 18 | N nom.Node 19 | P []nom.Port 20 | L []nom.Link 21 | } 22 | 23 | func (np *nodePortsAndLinks) hasPort(port nom.Port) bool { 24 | for _, p := range np.P { 25 | if p.ID == port.ID { 26 | return true 27 | } 28 | } 29 | return false 30 | } 31 | 32 | func (np *nodePortsAndLinks) removePort(port nom.Port) bool { 33 | for i, p := range np.P { 34 | if p.ID == port.ID { 35 | np.P = append(np.P[:i], np.P[i+1:]...) 36 | return true 37 | } 38 | } 39 | return false 40 | } 41 | 42 | func (np *nodePortsAndLinks) linkFrom(from nom.UID) (nom.Link, bool) { 43 | for _, l := range np.L { 44 | if l.From == from { 45 | return l, true 46 | } 47 | } 48 | return nom.Link{}, false 49 | } 50 | 51 | func (np *nodePortsAndLinks) hasLinkFrom(from nom.UID) bool { 52 | _, ok := np.linkFrom(from) 53 | return ok 54 | } 55 | 56 | func (np *nodePortsAndLinks) hasLink(link nom.Link) bool { 57 | id := link.UID() 58 | for _, l := range np.L { 59 | if l.UID() == id { 60 | return true 61 | } 62 | } 63 | return false 64 | } 65 | 66 | func (np *nodePortsAndLinks) removeLink(link nom.Link) bool { 67 | for i, l := range np.L { 68 | if l.From == link.From { 69 | np.L = append(np.L[:i], np.L[i+1:]...) 70 | return true 71 | } 72 | } 73 | return false 74 | } 75 | 76 | type nodeJoinedHandler struct{} 77 | 78 | func (h *nodeJoinedHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 79 | joined := msg.Data().(nom.NodeJoined) 80 | d := ctx.Dict(nodeDict) 81 | n := nom.Node(joined) 82 | k := string(n.UID()) 83 | var np nodePortsAndLinks 84 | if v, err := d.Get(k); err != nil { 85 | glog.Warningf("%v rejoins", n) 86 | } else { 87 | np = v.(nodePortsAndLinks) 88 | } 89 | np.N = n 90 | // TODO(soheil): Add a flow entry to forward lldp packets to the controller. 91 | return d.Put(k, np) 92 | } 93 | 94 | func (h *nodeJoinedHandler) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 95 | return bh.MappedCells{ 96 | {nodeDict, string(nom.Node(msg.Data().(nom.NodeJoined)).UID())}, 97 | } 98 | } 99 | 100 | type nodeLeftHandler struct{} 101 | 102 | func (h *nodeLeftHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 103 | n := nom.Node(msg.Data().(nom.NodeLeft)) 104 | d := ctx.Dict(nodeDict) 105 | k := string(n.UID()) 106 | if _, err := d.Get(k); err != nil { 107 | return fmt.Errorf("%v is not joined", n) 108 | } 109 | d.Del(k) 110 | return nil 111 | } 112 | 113 | func (h *nodeLeftHandler) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 114 | return bh.MappedCells{ 115 | {nodeDict, string(nom.Node(msg.Data().(nom.NodeLeft)).UID())}, 116 | } 117 | } 118 | 119 | type portUpdateHandler struct{} 120 | 121 | func (h *portUpdateHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 122 | p := nom.Port(msg.Data().(nom.PortUpdated)) 123 | d := ctx.Dict(nodeDict) 124 | k := string(p.Node) 125 | v, err := d.Get(k) 126 | if err != nil { 127 | glog.Warningf("%v added before its node", p) 128 | ctx.Snooze(1 * time.Second) 129 | return nil 130 | } 131 | 132 | np := v.(nodePortsAndLinks) 133 | if np.hasPort(p) { 134 | glog.Warningf("%v readded") 135 | np.removePort(p) 136 | } 137 | 138 | sendLLDPPacket(np.N, p, ctx) 139 | 140 | np.P = append(np.P, p) 141 | return d.Put(k, np) 142 | } 143 | 144 | func (h *portUpdateHandler) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 145 | return bh.MappedCells{ 146 | {nodeDict, string(msg.Data().(nom.PortUpdated).Node)}, 147 | } 148 | } 149 | 150 | type lldpTimeout struct{} 151 | 152 | type timeoutHandler struct{} 153 | 154 | func (h *timeoutHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 155 | d := ctx.Dict(nodeDict) 156 | d.ForEach(func(k string, v interface{}) bool { 157 | np := v.(nodePortsAndLinks) 158 | for _, p := range np.P { 159 | sendLLDPPacket(np.N, p, ctx) 160 | } 161 | return true 162 | }) 163 | return nil 164 | } 165 | 166 | func (h *timeoutHandler) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 167 | return bh.MappedCells{} 168 | } 169 | 170 | type pktInHandler struct{} 171 | 172 | func (h *pktInHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 173 | pin := msg.Data().(nom.PacketIn) 174 | e := ethernet.NewEthernetWithBuf([]byte(pin.Packet)) 175 | if e.Type() != uint16(ethernet.ETH_T_LLDP) { 176 | return nil 177 | } 178 | 179 | _, port, err := decodeLLDP([]byte(pin.Packet)) 180 | if err != nil { 181 | return err 182 | } 183 | 184 | d := ctx.Dict(nodeDict) 185 | k := string(pin.Node) 186 | if _, err := d.Get(k); err != nil { 187 | return fmt.Errorf("Node %v not found", pin.Node) 188 | } 189 | 190 | l := nom.Link{ 191 | ID: nom.LinkID(port.UID()), 192 | From: pin.InPort, 193 | To: port.UID(), 194 | State: nom.LinkStateUp, 195 | } 196 | ctx.Emit(NewLink(l)) 197 | 198 | l = nom.Link{ 199 | ID: nom.LinkID(pin.InPort), 200 | From: port.UID(), 201 | To: pin.InPort, 202 | State: nom.LinkStateUp, 203 | } 204 | ctx.Emit(NewLink(l)) 205 | 206 | return nil 207 | } 208 | 209 | func (h *pktInHandler) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 210 | return bh.MappedCells{ 211 | {nodeDict, string(msg.Data().(nom.PacketIn).Node)}, 212 | } 213 | } 214 | 215 | type NewLink nom.Link 216 | 217 | type newLinkHandler struct{} 218 | 219 | func (h *newLinkHandler) Rcv(msg bh.Msg, ctx bh.RcvContext) error { 220 | l := nom.Link(msg.Data().(NewLink)) 221 | n, _ := nom.ParsePortUID(l.From) 222 | d := ctx.Dict(nodeDict) 223 | k := string(n) 224 | v, err := d.Get(k) 225 | if err != nil { 226 | return err 227 | } 228 | np := v.(nodePortsAndLinks) 229 | 230 | if oldl, ok := np.linkFrom(l.From); ok { 231 | if oldl.UID() == l.UID() { 232 | return nil 233 | } 234 | np.removeLink(oldl) 235 | ctx.Emit(nom.LinkDeleted(oldl)) 236 | } 237 | 238 | glog.V(2).Infof("Link detected %v", l) 239 | ctx.Emit(nom.LinkAdded(l)) 240 | np.L = append(np.L, l) 241 | return d.Put(k, np) 242 | } 243 | 244 | func (h *newLinkHandler) Map(msg bh.Msg, ctx bh.MapContext) bh.MappedCells { 245 | n, _ := nom.ParsePortUID(msg.Data().(NewLink).From) 246 | return bh.MappedCells{{nodeDict, string(n)}} 247 | } 248 | 249 | // RegisterDiscovery registers the handlers for topology discovery on the hive. 250 | func RegisterDiscovery(h bh.Hive) { 251 | a := h.NewApp("discovery") 252 | a.Handle(nom.NodeJoined{}, &nodeJoinedHandler{}) 253 | a.Handle(nom.NodeLeft{}, &nodeLeftHandler{}) 254 | a.Handle(nom.PortUpdated{}, &portUpdateHandler{}) 255 | // TODO(soheil): Handle PortRemoved. 256 | a.Handle(nom.PacketIn{}, &pktInHandler{}) 257 | a.Handle(NewLink{}, &newLinkHandler{}) 258 | a.Handle(lldpTimeout{}, &timeoutHandler{}) 259 | go func() { 260 | for { 261 | h.Emit(lldpTimeout{}) 262 | time.Sleep(60 * time.Second) 263 | } 264 | }() 265 | } 266 | -------------------------------------------------------------------------------- /nom/flow_test.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import "testing" 4 | 5 | func testMatch(t *testing.T, m1, m2 Match, subsumption, equality [2]bool) { 6 | if !m1.Equals(m1) { 7 | t.Errorf("%#v should be equal to itself", m1) 8 | } 9 | if !m2.Equals(m2) { 10 | t.Errorf("%#v should be equal to itself", m2) 11 | } 12 | if m1.Subsumes(m2) != subsumption[0] { 13 | t.Errorf("unexpected subsumption for %#v and %#v: actual=%v want=%v", 14 | m1, m2, !subsumption[0], subsumption[0]) 15 | } 16 | if m2.Subsumes(m1) != subsumption[1] { 17 | t.Errorf("unexpected subsumption for %#v and %#v: actual=%v want=%v", 18 | m2, m1, !subsumption[1], subsumption[1]) 19 | } 20 | if m1.Equals(m2) != equality[0] { 21 | t.Errorf("unexpected subsumption for %#v and %#v: actual=%v want=%v", 22 | m1, m2, !equality[0], equality[0]) 23 | } 24 | if m2.Equals(m1) != equality[1] { 25 | t.Errorf("unexpected subsumption for %#v and %#v: actual=%v want=%v", 26 | m2, m1, !equality[1], equality[1]) 27 | } 28 | } 29 | 30 | func TestInPort(t *testing.T) { 31 | m1 := Match{ 32 | Fields: []Field{InPort(1)}, 33 | } 34 | m2 := Match{ 35 | Fields: []Field{InPort(2)}, 36 | } 37 | testMatch(t, m1, m2, [2]bool{false, false}, [2]bool{false, false}) 38 | testMatch(t, m1, m1, [2]bool{true, true}, [2]bool{true, true}) 39 | } 40 | 41 | func TestEthSrc(t *testing.T) { 42 | m1 := Match{ 43 | Fields: []Field{ 44 | EthSrc{ 45 | Addr: [6]byte{0x01, 0x02, 0x03, 0x00, 0x00, 0x00}, 46 | Mask: [6]byte{0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}, 47 | }, 48 | }, 49 | } 50 | m2 := Match{ 51 | Fields: []Field{ 52 | EthSrc{ 53 | Addr: [6]byte{0x01, 0x02, 0x03, 0x04, 0x00, 0x00}, 54 | Mask: [6]byte{0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00}, 55 | }, 56 | }, 57 | } 58 | testMatch(t, m1, m2, [2]bool{true, false}, [2]bool{false, false}) 59 | } 60 | 61 | func TestEthDst(t *testing.T) { 62 | m1 := Match{ 63 | Fields: []Field{ 64 | EthDst{ 65 | Addr: [6]byte{0x01, 0x02, 0x03, 0x00, 0x00, 0x00}, 66 | Mask: [6]byte{0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}, 67 | }, 68 | }, 69 | } 70 | m2 := Match{ 71 | Fields: []Field{ 72 | EthDst{ 73 | Addr: [6]byte{0x01, 0x02, 0x03, 0x04, 0x00, 0x00}, 74 | Mask: [6]byte{0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00}, 75 | }, 76 | }, 77 | } 78 | testMatch(t, m1, m2, [2]bool{true, false}, [2]bool{false, false}) 79 | } 80 | 81 | func TestEth(t *testing.T) { 82 | m1 := Match{ 83 | Fields: []Field{ 84 | EthSrc{ 85 | Addr: [6]byte{0x01, 0x02, 0x03, 0x00, 0x00, 0x00}, 86 | Mask: [6]byte{0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}, 87 | }, 88 | EthDst{ 89 | Addr: [6]byte{0x01, 0x02, 0x03, 0x00, 0x00, 0x00}, 90 | Mask: [6]byte{0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}, 91 | }, 92 | }, 93 | } 94 | m2 := Match{ 95 | Fields: []Field{ 96 | EthSrc{ 97 | Addr: [6]byte{0x01, 0x02, 0x03, 0x00, 0x00, 0x00}, 98 | Mask: [6]byte{0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00}, 99 | }, 100 | EthDst{ 101 | Addr: [6]byte{0x01, 0x02, 0x03, 0x04, 0x00, 0x00}, 102 | Mask: [6]byte{0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00}, 103 | }, 104 | }, 105 | } 106 | testMatch(t, m1, m2, [2]bool{true, false}, [2]bool{false, false}) 107 | } 108 | 109 | func TestIPv4Src(t *testing.T) { 110 | m1 := Match{ 111 | Fields: []Field{ 112 | IPv4Src{ 113 | Addr: [4]byte{0x01, 0x02, 0x03, 0x00}, 114 | Mask: [4]byte{0xFF, 0xFF, 0xFF, 0x00}, 115 | }, 116 | }, 117 | } 118 | m2 := Match{ 119 | Fields: []Field{ 120 | IPv4Src{ 121 | Addr: [4]byte{0x01, 0x02, 0x03, 0x04}, 122 | Mask: [4]byte{0xFF, 0xFF, 0xFF, 0xFF}, 123 | }, 124 | }, 125 | } 126 | testMatch(t, m1, m2, [2]bool{true, false}, [2]bool{false, false}) 127 | } 128 | 129 | func TestIPv4Dst(t *testing.T) { 130 | m1 := Match{ 131 | Fields: []Field{ 132 | IPv4Dst{ 133 | Addr: [4]byte{0x01, 0x02, 0x03, 0x00}, 134 | Mask: [4]byte{0xFF, 0xFF, 0xFF, 0x00}, 135 | }, 136 | }, 137 | } 138 | m2 := Match{ 139 | Fields: []Field{ 140 | IPv4Dst{ 141 | Addr: [4]byte{0x01, 0x02, 0x03, 0x04}, 142 | Mask: [4]byte{0xFF, 0xFF, 0xFF, 0xFF}, 143 | }, 144 | }, 145 | } 146 | testMatch(t, m1, m2, [2]bool{true, false}, [2]bool{false, false}) 147 | } 148 | 149 | func TestIPv4(t *testing.T) { 150 | m1 := Match{ 151 | Fields: []Field{ 152 | IPv4Src{ 153 | Addr: [4]byte{0x01, 0x02, 0x03, 0x00}, 154 | Mask: [4]byte{0xFF, 0xFF, 0xFF, 0x00}, 155 | }, 156 | IPv4Dst{ 157 | Addr: [4]byte{0x01, 0x02, 0x03, 0x00}, 158 | Mask: [4]byte{0xFF, 0xFF, 0xFF, 0x00}, 159 | }, 160 | }, 161 | } 162 | m2 := Match{ 163 | Fields: []Field{ 164 | IPv4Src{ 165 | Addr: [4]byte{0x01, 0x02, 0x03, 0x04}, 166 | Mask: [4]byte{0xFF, 0xFF, 0xFF, 0xFF}, 167 | }, 168 | IPv4Dst{ 169 | Addr: [4]byte{0x01, 0x02, 0x03, 0x00}, 170 | Mask: [4]byte{0xFF, 0xFF, 0xFF, 0x00}, 171 | }, 172 | }, 173 | } 174 | testMatch(t, m1, m2, [2]bool{true, false}, [2]bool{false, false}) 175 | } 176 | 177 | func TestIPv6(t *testing.T) { 178 | m1 := Match{ 179 | Fields: []Field{ 180 | IPv6Src{ 181 | Addr: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, 182 | Mask: [16]byte{0xFF, 0xFF, 0xFF}, 183 | }, 184 | IPv6Dst{ 185 | Addr: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, 186 | Mask: [16]byte{0xFF, 0xFF, 0xFF, 0xFF}, 187 | }, 188 | }, 189 | } 190 | m2 := Match{ 191 | Fields: []Field{ 192 | IPv6Src{ 193 | Addr: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, 194 | Mask: [16]byte{0xFF, 0xFF, 0xFF, 0xFF}, 195 | }, 196 | IPv6Dst{ 197 | Addr: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, 198 | Mask: [16]byte{0xFF, 0xFF, 0xFF, 0xFF}, 199 | }, 200 | }, 201 | } 202 | testMatch(t, m1, m2, [2]bool{true, false}, [2]bool{false, false}) 203 | } 204 | 205 | func TestTransportPortSrc(t *testing.T) { 206 | m1 := Match{ 207 | Fields: []Field{TransportPortSrc(88)}, 208 | } 209 | m2 := Match{ 210 | Fields: []Field{TransportPortSrc(80)}, 211 | } 212 | testMatch(t, m1, m2, [2]bool{false, false}, [2]bool{false, false}) 213 | testMatch(t, m1, m1, [2]bool{true, true}, [2]bool{true, true}) 214 | } 215 | 216 | func TestTransportPortDst(t *testing.T) { 217 | m1 := Match{ 218 | Fields: []Field{TransportPortDst(88)}, 219 | } 220 | m2 := Match{ 221 | Fields: []Field{TransportPortDst(80)}, 222 | } 223 | testMatch(t, m1, m2, [2]bool{false, false}, [2]bool{false, false}) 224 | testMatch(t, m1, m1, [2]bool{true, true}, [2]bool{true, true}) 225 | } 226 | 227 | func testFlowEntry(t *testing.T, f1, f2 FlowEntry, 228 | equality, subsumption [2]bool) { 229 | 230 | if f1.Equals(f2) != equality[0] { 231 | t.Errorf("invalid flow equality for %v and %v: actual=%v want=%v", 232 | f1, f2, !equality[0], equality[0]) 233 | } 234 | if f2.Equals(f1) != equality[1] { 235 | t.Errorf("invalid flow equality for %v and %v: actual=%v want=%v", 236 | f2, f1, !equality[1], equality[1]) 237 | } 238 | if f1.Subsumes(f2) != subsumption[0] { 239 | t.Errorf("invalid flow subsumption for %v and %v: actual=%v want=%v", 240 | f1, f2, !subsumption[0], subsumption[0]) 241 | } 242 | if f2.Subsumes(f1) != subsumption[1] { 243 | t.Errorf("invalid flow subsumption for %v and %v: actual=%v want=%v", 244 | f2, f1, !subsumption[1], subsumption[1]) 245 | } 246 | } 247 | 248 | func TestFlowEquality(t *testing.T) { 249 | f1 := FlowEntry{ 250 | ID: "0", 251 | Node: "n6", 252 | Match: Match{ 253 | Fields: []Field{ 254 | EthDst{ 255 | Addr: MACAddr{0x1, 0x2, 0x3, 0x4, 0x5, 0x6}, 256 | Mask: MACAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 257 | }, 258 | InPort("n6$$2"), 259 | }, 260 | }, 261 | Actions: []Action{ 262 | ActionForward{Ports: []UID{"n6$$3"}}, 263 | }, 264 | Priority: 0x1, 265 | IdleTimeout: 0, 266 | HardTimeout: 0, 267 | } 268 | f2 := FlowEntry{ 269 | ID: "0", 270 | Node: "n6", 271 | Match: Match{ 272 | Fields: []Field{ 273 | EthDst{ 274 | Addr: MACAddr{0x1, 0x2, 0x3, 0x4, 0x5, 0x6}, 275 | Mask: MACAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, 276 | }, 277 | InPort("n6$$2"), 278 | }, 279 | }, 280 | Actions: []Action{ 281 | ActionForward{Ports: []UID{"n6$$3"}}, 282 | }, 283 | Priority: 0x1, 284 | IdleTimeout: 0, 285 | HardTimeout: 0, 286 | } 287 | testFlowEntry(t, f1, f1, [2]bool{true, true}, [2]bool{true, true}) 288 | testFlowEntry(t, f1, f2, [2]bool{true, true}, [2]bool{true, true}) 289 | 290 | f1.Match.Fields = f1.Match.Fields[1:] 291 | testFlowEntry(t, f1, f2, [2]bool{false, false}, [2]bool{true, false}) 292 | 293 | f1.Match = f2.Match 294 | f2.Actions = append(f2.Actions, ActionFlood{}) 295 | testFlowEntry(t, f1, f2, [2]bool{false, false}, [2]bool{false, false}) 296 | } 297 | -------------------------------------------------------------------------------- /nom/addr.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "fmt" 7 | ) 8 | 9 | // MACAddr represents a MAC address. 10 | type MACAddr [6]byte 11 | 12 | var ( 13 | MaskNoneMAC MACAddr = [6]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} 14 | BroadcastMAC MACAddr = [6]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} 15 | CDPMulticastMAC MACAddr = [6]byte{0x01, 0x00, 0x0C, 0xCC, 0xCC, 0xCC} 16 | CiscoSTPMulticastMAC MACAddr = [6]byte{0x01, 0x00, 0x0C, 0xCC, 0xCC, 0xCD} 17 | IEEE802MulticastPrefix MACAddr = [6]byte{0x01, 0x80, 0xC2, 0x00, 0x00, 0x00} 18 | IPv4MulticastPrefix MACAddr = [6]byte{0x01, 0x00, 0x5E, 0x00, 0x00, 0x00} 19 | IPv6MulticastPrefix MACAddr = [6]byte{0x33, 0x33, 0x00, 0x00, 0x00, 0x00} 20 | LLDPMulticastMACs []MACAddr = []MACAddr{ 21 | [6]byte{0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E}, 22 | [6]byte{0x01, 0x80, 0xC2, 0x00, 0x00, 0x0C}, 23 | [6]byte{0x01, 0x80, 0xC2, 0x00, 0x00, 0x00}, 24 | } 25 | MaskNoneIPV4 IPv4Addr = [4]byte{0xFF, 0xFF, 0xFF, 0xFF} 26 | MaskNoneIPV6 IPv6Addr = [16]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 27 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} 28 | ) 29 | 30 | func (m MACAddr) String() string { 31 | return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", 32 | m[0], m[1], m[2], m[3], m[4], m[5]) 33 | } 34 | 35 | // Key returns an string represtation of the MAC address suitable to store in 36 | // dictionaries. It is more efficient compared to MACAddr.String(). 37 | func (m MACAddr) Key() string { 38 | return string(m[:]) 39 | } 40 | 41 | // IsBroadcast returns whether the MAC address is a broadcast address. 42 | func (m MACAddr) IsBroadcast() bool { 43 | return m == BroadcastMAC 44 | } 45 | 46 | // IsMulticast returns whether the MAC address is a multicast address. 47 | func (m MACAddr) IsMulticast() bool { 48 | return m == CDPMulticastMAC || m == CiscoSTPMulticastMAC || 49 | m.hasPrefix(IEEE802MulticastPrefix, 3) || 50 | m.hasPrefix(IPv4MulticastPrefix, 3) || 51 | m.hasPrefix(IPv6MulticastPrefix, 2) 52 | } 53 | 54 | // IsLLDP returns whether the mac address is a multicast address used for LLDP. 55 | func (m MACAddr) IsLLDP() bool { 56 | for _, lm := range LLDPMulticastMACs { 57 | if m == lm { 58 | return true 59 | } 60 | } 61 | return false 62 | } 63 | 64 | func (m MACAddr) hasPrefix(p MACAddr, l int) bool { 65 | for i := 0; i < l; i++ { 66 | if p[i] != m[i] { 67 | return false 68 | } 69 | } 70 | return true 71 | } 72 | 73 | func (m MACAddr) Mask(mask MACAddr) MACAddr { 74 | masked := m 75 | for i := range mask { 76 | masked[i] &= mask[i] 77 | } 78 | return masked 79 | } 80 | 81 | func (m MACAddr) Less(thatm MACAddr) bool { 82 | for i := range thatm { 83 | switch { 84 | case m[i] < thatm[i]: 85 | return true 86 | case m[i] > thatm[i]: 87 | return false 88 | } 89 | } 90 | return false 91 | } 92 | 93 | // MaskedMACAddr is a MAC address that is wildcarded with a mask. 94 | type MaskedMACAddr struct { 95 | Addr MACAddr // The MAC address. 96 | Mask MACAddr // The mask of the MAC address. 97 | } 98 | 99 | // Match returns whether the masked mac address matches mac. 100 | func (mm MaskedMACAddr) Match(mac MACAddr) bool { 101 | return mm.Mask.Mask(mm.Addr) == mm.Mask.Mask(mac) 102 | } 103 | 104 | // Subsumes returns whether this mask address includes all the addresses matched 105 | // by thatmm. 106 | func (mm MaskedMACAddr) Subsumes(thatmm MaskedMACAddr) bool { 107 | if thatmm.Mask.Less(mm.Mask) { 108 | return false 109 | } 110 | return mm.Match(thatmm.Addr.Mask(thatmm.Mask)) 111 | } 112 | 113 | // IPv4Addr represents an IP version 4 address in big endian byte order. 114 | // For example, 127.0.0.1 is represented as IPv4Addr{127, 0, 0, 1}. 115 | type IPv4Addr [4]byte 116 | 117 | // Uint converts the IP version 4 address into a 32-bit integer in little 118 | // endian byte order. 119 | func (ip IPv4Addr) Uint() uint32 { 120 | return uint32(ip[0])<<24 | uint32(ip[1])<<16 | uint32(ip[2])<<8 | 121 | uint32(ip[3]) 122 | } 123 | 124 | // FromUint loads the ip address from addr. 125 | func (ip *IPv4Addr) FromUint(addr uint32) { 126 | for i := 0; i < 4; i++ { 127 | ip[i] = byte((addr >> uint(8*(3-i))) & 0xFF) 128 | } 129 | } 130 | 131 | // PopCount returns the number of ones in the IP address. For example, it 132 | // returns 16 for IPv4Addr{255, 255, 0, 0}. 133 | func (ip IPv4Addr) PopCount() uint32 { 134 | v := ip.Uint() 135 | v -= (v >> 1) & 0x55555555 136 | v = ((v >> 2) & 0x33333333) + v&0x33333333 137 | v = ((v >> 4) + v) & 0x0F0F0F0F 138 | v = ((v >> 8) + v) & 0x00FF00FF 139 | return ((v >> 16) + v) & 0x0000FFFF 140 | } 141 | 142 | // Mask masked the IP address with mask. 143 | func (ip IPv4Addr) Mask(mask IPv4Addr) IPv4Addr { 144 | masked := ip 145 | for i := range masked { 146 | masked[i] &= mask[i] 147 | } 148 | return masked 149 | } 150 | 151 | // Less returns whether ip is less than thatip. 152 | func (ip IPv4Addr) Less(thatip IPv4Addr) bool { 153 | for i := range ip { 154 | switch { 155 | case ip[i] < thatip[i]: 156 | return true 157 | case ip[i] > thatip[i]: 158 | return false 159 | } 160 | } 161 | return false 162 | } 163 | 164 | // AsCIDRMask returns the CIDR prefix number based on this address. 165 | // For example, it returns 24 for 255.255.255.0. 166 | func (ip IPv4Addr) AsCIDRMask() int { 167 | m := 0 168 | for i := len(ip) - 1; i >= 0; i-- { 169 | for j := uint(0); j < 8; j++ { 170 | if (ip[i]>>j)&0x1 != 0 { 171 | return 32 - m 172 | } 173 | m++ 174 | } 175 | } 176 | return 0 177 | } 178 | 179 | func (ip IPv4Addr) String() string { 180 | return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) 181 | } 182 | 183 | // CIDRToMaskedIPv4 converts a CIDR-style IP address into a NOM masked IP 184 | // address. For example, if addr is 0x7F000001 and mask is 24, this function 185 | // returns {IPv4Addr{127, 0, 0, 1}, IPv4Addr{255, 255, 255, 0}}. 186 | func CIDRToMaskedIPv4(addr uint32, mask uint) MaskedIPv4Addr { 187 | var maskedip MaskedIPv4Addr 188 | maskedip.Addr.FromUint(addr) 189 | maskedip.Mask.FromUint(uint32(((1 << mask) - 1) << (32 - mask))) 190 | return maskedip 191 | } 192 | 193 | // MaskedIPv4Addr represents a masked IP address (ie, an IPv4 prefix) 194 | type MaskedIPv4Addr struct { 195 | Addr IPv4Addr 196 | Mask IPv4Addr 197 | } 198 | 199 | // Match returns whether the masked IP address matches ip. 200 | func (mi MaskedIPv4Addr) Match(ip IPv4Addr) bool { 201 | return mi.Addr.Mask(mi.Mask) == ip.Mask(mi.Mask) 202 | } 203 | 204 | func (mi MaskedIPv4Addr) Subsumes(thatmi MaskedIPv4Addr) bool { 205 | if thatmi.Mask.Less(mi.Mask) { 206 | return false 207 | } 208 | return mi.Addr.Mask(mi.Mask) == thatmi.Addr.Mask(mi.Mask) 209 | } 210 | 211 | func (mi MaskedIPv4Addr) String() string { 212 | return fmt.Sprintf("%v/%d", mi.Addr, mi.Mask.AsCIDRMask()) 213 | } 214 | 215 | // IPv6Addr represents an IP version 6 address in big-endian byte order. 216 | type IPv6Addr [16]byte 217 | 218 | // Mask masked the IP address with mask. 219 | func (ip IPv6Addr) Mask(mask IPv6Addr) IPv6Addr { 220 | masked := ip 221 | for i := range masked { 222 | masked[i] &= mask[i] 223 | } 224 | return masked 225 | } 226 | 227 | // Less returns whether ip is less than thatip. 228 | func (ip IPv6Addr) Less(thatip IPv6Addr) bool { 229 | for i := range ip { 230 | switch { 231 | case ip[i] < thatip[i]: 232 | return true 233 | case ip[i] > thatip[i]: 234 | return false 235 | } 236 | } 237 | return false 238 | } 239 | 240 | func (ip IPv6Addr) String() string { 241 | var buf bytes.Buffer 242 | zeros := 0 243 | for i := 0; i < 16; i += 2 { 244 | if ip[i] == 0 && ip[i+1] == 0 { 245 | zeros++ 246 | if zeros == 2 { 247 | if zeros == i { 248 | buf.WriteString("::") 249 | } else { 250 | buf.WriteString(":") 251 | } 252 | } 253 | continue 254 | } 255 | if zeros == 1 { 256 | buf.WriteString("0:") 257 | } 258 | buf.WriteString(fmt.Sprintf("%x", int(ip[i])<<8|int(ip[i+1]))) 259 | if i != 14 { 260 | buf.WriteString(":") 261 | } 262 | zeros = 0 263 | } 264 | return buf.String() 265 | } 266 | 267 | // AsCIDRMask returns the CIDR prefix number based on this address. 268 | func (ip IPv6Addr) AsCIDRMask() int { 269 | m := 0 270 | for i := len(ip) - 1; i >= 0; i-- { 271 | for j := uint(0); j < 8; j++ { 272 | if (ip[i]>>j)&0x1 != 0 { 273 | return 128 - m 274 | } 275 | m++ 276 | } 277 | } 278 | return 0 279 | } 280 | 281 | // MaskedIPv6Addr represents a masked IPv6 address. 282 | type MaskedIPv6Addr struct { 283 | Addr IPv6Addr 284 | Mask IPv6Addr 285 | } 286 | 287 | // Match returns whether the masked IP address matches ip. 288 | func (mi MaskedIPv6Addr) Match(ip IPv6Addr) bool { 289 | return mi.Addr.Mask(mi.Mask) == ip.Mask(mi.Mask) 290 | } 291 | 292 | func (mi MaskedIPv6Addr) Subsumes(thatmi MaskedIPv6Addr) bool { 293 | if thatmi.Mask.Less(mi.Mask) { 294 | return false 295 | } 296 | return mi.Addr.Mask(mi.Mask) == thatmi.Addr.Mask(mi.Mask) 297 | } 298 | 299 | func (mi MaskedIPv6Addr) String() string { 300 | return fmt.Sprintf("%v/%d", mi.Addr, mi.Mask.AsCIDRMask()) 301 | } 302 | 303 | func init() { 304 | gob.Register(IPv4Addr{}) 305 | gob.Register(IPv6Addr{}) 306 | gob.Register(MACAddr{}) 307 | gob.Register(MaskedIPv4Addr{}) 308 | gob.Register(MaskedIPv6Addr{}) 309 | gob.Register(MaskedMACAddr{}) 310 | } 311 | -------------------------------------------------------------------------------- /openflow/of/of.go: -------------------------------------------------------------------------------- 1 | // Automatically generated by Packet Go code generator. 2 | package of 3 | 4 | import ( 5 | "bufio" 6 | "bytes" 7 | "encoding/binary" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "net" 12 | 13 | "github.com/packet/packet/src/go/packet" 14 | ) 15 | 16 | type Versions int 17 | 18 | const ( 19 | OPENFLOW_1_0 Versions = 1 20 | OPENFLOW_1_1 Versions = 2 21 | OPENFLOW_1_2 Versions = 3 22 | OPENFLOW_1_3 Versions = 4 23 | ) 24 | 25 | type Constants int 26 | 27 | const ( 28 | P_ETH_ALEN Constants = 6 29 | P_IPV4_ALEN Constants = 4 30 | P_IPV6_ALEN Constants = 16 31 | P_MAX_PORT_NAME_LEN Constants = 16 32 | P_MAX_TABLE_NAME_LEN Constants = 32 33 | ) 34 | 35 | type Type int 36 | 37 | const ( 38 | PT_HELLO Type = 0 39 | PT_ERROR Type = 1 40 | PT_ECHO_REQUEST Type = 2 41 | PT_ECHO_REPLY Type = 3 42 | PT_VENDOR Type = 4 43 | PT_FEATURES_REQUEST Type = 5 44 | PT_FEATURES_REPLY Type = 6 45 | PT_GET_CONFIG_REQUEST Type = 7 46 | PT_GET_CONFIG_REPLY Type = 8 47 | PT_SET_CONFIG Type = 9 48 | ) 49 | 50 | func NewHeaderWithBuf(b []byte) Header { 51 | return Header{packet.Packet{Buf: b}} 52 | } 53 | 54 | func NewHeader() Header { 55 | s := 8 56 | b := make([]byte, s) 57 | p := Header{packet.Packet{Buf: b}} 58 | p.Init() 59 | return p 60 | } 61 | 62 | type Header struct { 63 | packet.Packet 64 | } 65 | 66 | func (this Header) minSize() int { 67 | return 8 68 | } 69 | 70 | func (this Header) Clone() (Header, error) { 71 | var newBuf bytes.Buffer 72 | _, err := io.CopyN(&newBuf, bytes.NewBuffer(this.Buf), int64(this.Size())) 73 | if err != nil { 74 | return NewHeader(), err 75 | } 76 | 77 | return NewHeaderWithBuf(newBuf.Bytes()), nil 78 | } 79 | 80 | type HeaderConn struct { 81 | net.Conn 82 | w *bufio.Writer 83 | buf []byte 84 | offset int 85 | } 86 | 87 | func NewHeaderConn(c net.Conn) HeaderConn { 88 | return HeaderConn{ 89 | Conn: c, 90 | w: bufio.NewWriter(c), 91 | buf: make([]byte, packet.DefaultBufSize), 92 | } 93 | } 94 | 95 | func (c *HeaderConn) WriteHeader(pkt Header) error { 96 | s := pkt.Size() 97 | b := pkt.Buffer()[:s] 98 | n := 0 99 | for s > 0 { 100 | var err error 101 | if n, err = c.w.Write(b); err != nil { 102 | return fmt.Errorf("Error in write: %v", err) 103 | } 104 | s -= n 105 | } 106 | 107 | return nil 108 | } 109 | 110 | func (c *HeaderConn) WriteHeaders(pkts []Header) error { 111 | for _, p := range pkts { 112 | if err := c.WriteHeader(p); err != nil { 113 | return err 114 | } 115 | } 116 | return nil 117 | } 118 | 119 | func (c *HeaderConn) Flush() error { 120 | return c.w.Flush() 121 | } 122 | 123 | func (c *HeaderConn) ReadHeader() (Header, error) { 124 | pkts := make([]Header, 1) 125 | _, err := c.ReadHeaders(pkts) 126 | if err != nil { 127 | return NewHeader(), err 128 | } 129 | 130 | return pkts[0], nil 131 | } 132 | 133 | func (c *HeaderConn) ReadHeaders(pkts []Header) (int, error) { 134 | if len(c.buf) == c.offset { 135 | newSize := packet.DefaultBufSize 136 | if newSize < len(c.buf) { 137 | newSize = 2 * len(c.buf) 138 | } 139 | 140 | buf := make([]byte, newSize) 141 | copy(buf, c.buf[:c.offset]) 142 | c.buf = buf 143 | } 144 | 145 | r, err := c.Conn.Read(c.buf[c.offset:]) 146 | if err != nil { 147 | return 0, err 148 | } 149 | 150 | r += c.offset 151 | 152 | s := 0 153 | n := 0 154 | for i := range pkts { 155 | p := NewHeaderWithBuf(c.buf[s:]) 156 | 157 | pSize := p.Size() 158 | if pSize == 0 || r < s+pSize { 159 | break 160 | } 161 | 162 | pkts[i] = p 163 | s += pSize 164 | n++ 165 | } 166 | 167 | c.offset = r - s 168 | if c.offset < 0 { 169 | panic("Invalid value for offset") 170 | } 171 | 172 | c.buf = c.buf[s:] 173 | return n, nil 174 | } 175 | 176 | func (this *Header) Init() { 177 | this.SetLength(uint16(this.minSize())) 178 | // Invariants. 179 | } 180 | 181 | func (this Header) Size() int { 182 | if len(this.Buf) < this.minSize() { 183 | return 0 184 | } 185 | 186 | size := int(this.Length()) 187 | return size 188 | } 189 | 190 | func ToHeader(p packet.Packet) (Header, error) { 191 | if !IsHeader(p) { 192 | return NewHeaderWithBuf(nil), errors.New("Cannot convert to of.Header") 193 | } 194 | 195 | return NewHeaderWithBuf(p.Buf), nil 196 | } 197 | 198 | func IsHeader(p packet.Packet) bool { 199 | return true 200 | } 201 | 202 | func (this Header) Version() uint8 { 203 | offset := this.VersionOffset() 204 | res := uint8(this.Buf[offset]) 205 | return res 206 | } 207 | 208 | func (this *Header) SetVersion(v uint8) { 209 | offset := this.VersionOffset() 210 | this.Buf[offset] = byte(v) 211 | offset++ 212 | } 213 | 214 | func (this Header) VersionOffset() int { 215 | offset := 0 216 | return offset 217 | } 218 | 219 | func (this Header) Type() uint8 { 220 | offset := this.TypeOffset() 221 | res := uint8(this.Buf[offset]) 222 | return res 223 | } 224 | 225 | func (this *Header) SetType(t uint8) { 226 | offset := this.TypeOffset() 227 | this.Buf[offset] = byte(t) 228 | offset++ 229 | } 230 | 231 | func (this Header) TypeOffset() int { 232 | offset := 1 233 | return offset 234 | } 235 | 236 | func (this Header) Length() uint16 { 237 | offset := this.LengthOffset() 238 | res := binary.BigEndian.Uint16(this.Buf[offset:]) 239 | return res 240 | } 241 | 242 | func (this *Header) SetLength(l uint16) { 243 | offset := this.LengthOffset() 244 | binary.BigEndian.PutUint16(this.Buf[offset:], l) 245 | offset += 2 246 | } 247 | 248 | func (this Header) LengthOffset() int { 249 | offset := 2 250 | return offset 251 | } 252 | 253 | func (this Header) Xid() uint32 { 254 | offset := this.XidOffset() 255 | res := binary.BigEndian.Uint32(this.Buf[offset:]) 256 | return res 257 | } 258 | 259 | func (this *Header) SetXid(x uint32) { 260 | offset := this.XidOffset() 261 | binary.BigEndian.PutUint32(this.Buf[offset:], x) 262 | offset += 4 263 | } 264 | 265 | func (this Header) XidOffset() int { 266 | offset := 4 267 | return offset 268 | } 269 | 270 | func NewHelloWithBuf(b []byte) Hello { 271 | return Hello{Header{packet.Packet{Buf: b}}} 272 | } 273 | 274 | func NewHello() Hello { 275 | s := 8 276 | b := make([]byte, s) 277 | p := Hello{Header{packet.Packet{Buf: b}}} 278 | p.Init() 279 | return p 280 | } 281 | 282 | type Hello struct { 283 | Header 284 | } 285 | 286 | func (this Hello) minSize() int { 287 | return 8 288 | } 289 | 290 | func (this Hello) Clone() (Hello, error) { 291 | var newBuf bytes.Buffer 292 | _, err := io.CopyN(&newBuf, bytes.NewBuffer(this.Buf), int64(this.Size())) 293 | if err != nil { 294 | return NewHello(), err 295 | } 296 | 297 | return NewHelloWithBuf(newBuf.Bytes()), nil 298 | } 299 | 300 | type HelloConn struct { 301 | net.Conn 302 | w *bufio.Writer 303 | buf []byte 304 | offset int 305 | } 306 | 307 | func NewHelloConn(c net.Conn) HelloConn { 308 | return HelloConn{ 309 | Conn: c, 310 | w: bufio.NewWriter(c), 311 | buf: make([]byte, packet.DefaultBufSize), 312 | } 313 | } 314 | 315 | func (c *HelloConn) WriteHello(pkt Hello) error { 316 | s := pkt.Size() 317 | b := pkt.Buffer()[:s] 318 | n := 0 319 | for s > 0 { 320 | var err error 321 | if n, err = c.w.Write(b); err != nil { 322 | return fmt.Errorf("Error in write: %v", err) 323 | } 324 | s -= n 325 | } 326 | 327 | return nil 328 | } 329 | 330 | func (c *HelloConn) WriteHellos(pkts []Hello) error { 331 | for _, p := range pkts { 332 | if err := c.WriteHello(p); err != nil { 333 | return err 334 | } 335 | } 336 | return nil 337 | } 338 | 339 | func (c *HelloConn) Flush() error { 340 | return c.w.Flush() 341 | } 342 | 343 | func (c *HelloConn) ReadHello() (Hello, error) { 344 | pkts := make([]Hello, 1) 345 | _, err := c.ReadHellos(pkts) 346 | if err != nil { 347 | return NewHello(), err 348 | } 349 | 350 | return pkts[0], nil 351 | } 352 | 353 | func (c *HelloConn) ReadHellos(pkts []Hello) (int, error) { 354 | if len(c.buf) == c.offset { 355 | newSize := packet.DefaultBufSize 356 | if newSize < len(c.buf) { 357 | newSize = 2 * len(c.buf) 358 | } 359 | 360 | buf := make([]byte, newSize) 361 | copy(buf, c.buf[:c.offset]) 362 | c.buf = buf 363 | } 364 | 365 | r, err := c.Conn.Read(c.buf[c.offset:]) 366 | if err != nil { 367 | return 0, err 368 | } 369 | 370 | r += c.offset 371 | 372 | s := 0 373 | n := 0 374 | for i := range pkts { 375 | p := NewHelloWithBuf(c.buf[s:]) 376 | 377 | pSize := p.Size() 378 | if pSize == 0 || r < s+pSize { 379 | break 380 | } 381 | 382 | pkts[i] = p 383 | s += pSize 384 | n++ 385 | } 386 | 387 | c.offset = r - s 388 | if c.offset < 0 { 389 | panic("Invalid value for offset") 390 | } 391 | 392 | c.buf = c.buf[s:] 393 | return n, nil 394 | } 395 | 396 | func (this *Hello) Init() { 397 | this.Header.Init() 398 | this.SetLength(uint16(this.minSize())) 399 | // Invariants. 400 | this.SetType(uint8(0)) // type 401 | } 402 | 403 | func (this Hello) Size() int { 404 | if len(this.Buf) < this.minSize() { 405 | return 0 406 | } 407 | 408 | size := int(this.Length()) 409 | return size 410 | } 411 | 412 | func ToHello(p Header) (Hello, error) { 413 | if !IsHello(p) { 414 | return NewHelloWithBuf(nil), errors.New("Cannot convert to of.Hello") 415 | } 416 | 417 | return NewHelloWithBuf(p.Buf), nil 418 | } 419 | 420 | func IsHello(p Header) bool { 421 | return p.Type() == 0 && true 422 | } 423 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /net/lldp/lldp.go: -------------------------------------------------------------------------------- 1 | // Automatically generated by Packet Go code generator. 2 | package lldp 3 | 4 | import ( 5 | "bufio" 6 | "bytes" 7 | "encoding/binary" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "net" 12 | 13 | "github.com/packet/packet/src/go/packet" 14 | 15 | "github.com/kandoo/beehive-netctrl/net/ethernet" 16 | ) 17 | 18 | type LinkDiscoveryTLVType int 19 | 20 | const ( 21 | TLV_CHASSIS_ID LinkDiscoveryTLVType = 1 22 | TLV_PORT_ID LinkDiscoveryTLVType = 2 23 | TLV_TTL LinkDiscoveryTLVType = 3 24 | TLV_CUSTOM LinkDiscoveryTLVType = 127 25 | ) 26 | 27 | type PortTLVSubtype int 28 | 29 | const ( 30 | PORT_TLV_IFACE_ALIAS PortTLVSubtype = 1 31 | PORT_TLV_PORT_COMP PortTLVSubtype = 2 32 | PORT_TLV_MAC_ADDR PortTLVSubtype = 3 33 | PORT_TLV_NET_ADDR PortTLVSubtype = 4 34 | PORT_TLV_IFACE_NAME PortTLVSubtype = 5 35 | PORT_TLV_AGENT_CIRCUIT_ID PortTLVSubtype = 6 36 | PORT_TLV_LOCAL PortTLVSubtype = 7 37 | ) 38 | 39 | func NewLinkDiscoveryTLVWithBuf(b []byte) LinkDiscoveryTLV { 40 | return LinkDiscoveryTLV{packet.Packet{Buf: b}} 41 | } 42 | 43 | func NewLinkDiscoveryTLV() LinkDiscoveryTLV { 44 | s := 2 45 | b := make([]byte, s) 46 | p := LinkDiscoveryTLV{packet.Packet{Buf: b}} 47 | p.Init() 48 | return p 49 | } 50 | 51 | type LinkDiscoveryTLV struct { 52 | packet.Packet 53 | } 54 | 55 | func (this LinkDiscoveryTLV) minSize() int { 56 | return 2 57 | } 58 | 59 | func (this LinkDiscoveryTLV) Clone() (LinkDiscoveryTLV, error) { 60 | var newBuf bytes.Buffer 61 | _, err := io.CopyN(&newBuf, bytes.NewBuffer(this.Buf), int64(this.Size())) 62 | if err != nil { 63 | return NewLinkDiscoveryTLV(), err 64 | } 65 | 66 | return NewLinkDiscoveryTLVWithBuf(newBuf.Bytes()), nil 67 | } 68 | 69 | type LinkDiscoveryTLVConn struct { 70 | net.Conn 71 | w *bufio.Writer 72 | buf []byte 73 | offset int 74 | } 75 | 76 | func NewLinkDiscoveryTLVConn(c net.Conn) LinkDiscoveryTLVConn { 77 | return LinkDiscoveryTLVConn{ 78 | Conn: c, 79 | w: bufio.NewWriter(c), 80 | buf: make([]byte, packet.DefaultBufSize), 81 | } 82 | } 83 | 84 | func (c *LinkDiscoveryTLVConn) WriteLinkDiscoveryTLV(pkt LinkDiscoveryTLV) error { 85 | s := pkt.Size() 86 | b := pkt.Buffer()[:s] 87 | n := 0 88 | for s > 0 { 89 | var err error 90 | if n, err = c.w.Write(b); err != nil { 91 | return fmt.Errorf("Error in write: %v", err) 92 | } 93 | s -= n 94 | } 95 | 96 | return nil 97 | } 98 | 99 | func (c *LinkDiscoveryTLVConn) WriteLinkDiscoveryTLVs(pkts []LinkDiscoveryTLV) error { 100 | for _, p := range pkts { 101 | if err := c.WriteLinkDiscoveryTLV(p); err != nil { 102 | return err 103 | } 104 | } 105 | return nil 106 | } 107 | 108 | func (c *LinkDiscoveryTLVConn) Flush() error { 109 | return c.w.Flush() 110 | } 111 | 112 | func (c *LinkDiscoveryTLVConn) ReadLinkDiscoveryTLV() (LinkDiscoveryTLV, error) { 113 | pkts := make([]LinkDiscoveryTLV, 1) 114 | _, err := c.ReadLinkDiscoveryTLVs(pkts) 115 | if err != nil { 116 | return NewLinkDiscoveryTLV(), err 117 | } 118 | 119 | return pkts[0], nil 120 | } 121 | 122 | func (c *LinkDiscoveryTLVConn) ReadLinkDiscoveryTLVs(pkts []LinkDiscoveryTLV) (int, error) { 123 | if len(c.buf) == c.offset { 124 | newSize := packet.DefaultBufSize 125 | if newSize < len(c.buf) { 126 | newSize = 2 * len(c.buf) 127 | } 128 | 129 | buf := make([]byte, newSize) 130 | copy(buf, c.buf[:c.offset]) 131 | c.buf = buf 132 | } 133 | 134 | r, err := c.Conn.Read(c.buf[c.offset:]) 135 | if err != nil { 136 | return 0, err 137 | } 138 | 139 | r += c.offset 140 | 141 | s := 0 142 | n := 0 143 | for i := range pkts { 144 | p := NewLinkDiscoveryTLVWithBuf(c.buf[s:]) 145 | 146 | pSize := p.Size() 147 | if pSize == 0 || r < s+pSize { 148 | break 149 | } 150 | 151 | pkts[i] = p 152 | s += pSize 153 | n++ 154 | } 155 | 156 | c.offset = r - s 157 | if c.offset < 0 { 158 | panic("Invalid value for offset") 159 | } 160 | 161 | c.buf = c.buf[s:] 162 | return n, nil 163 | } 164 | 165 | func (this *LinkDiscoveryTLV) Init() { 166 | this.SetSize(this.minSize()) 167 | // Invariants. 168 | } 169 | 170 | func ToLinkDiscoveryTLV(p packet.Packet) (LinkDiscoveryTLV, error) { 171 | if !IsLinkDiscoveryTLV(p) { 172 | return NewLinkDiscoveryTLVWithBuf(nil), errors.New("Cannot convert to lldp.LinkDiscoveryTLV") 173 | } 174 | 175 | return NewLinkDiscoveryTLVWithBuf(p.Buf), nil 176 | } 177 | 178 | func IsLinkDiscoveryTLV(p packet.Packet) bool { 179 | return true 180 | } 181 | 182 | func (this LinkDiscoveryTLV) TypeAndLen() uint16 { 183 | offset := this.TypeAndLenOffset() 184 | res := binary.BigEndian.Uint16(this.Buf[offset:]) 185 | return res 186 | } 187 | 188 | func (this *LinkDiscoveryTLV) SetTypeAndLen(t uint16) { 189 | offset := this.TypeAndLenOffset() 190 | binary.BigEndian.PutUint16(this.Buf[offset:], t) 191 | offset += 2 192 | } 193 | 194 | func (this LinkDiscoveryTLV) TypeAndLenOffset() int { 195 | offset := 0 196 | return offset 197 | } 198 | 199 | func (this LinkDiscoveryTLV) Value() []uint8 { 200 | offset := this.ValueOffset() 201 | packet_size := this.Size() 202 | size := packet_size - offset 203 | return []uint8(this.Buf[offset : offset+size]) 204 | } 205 | 206 | func (this *LinkDiscoveryTLV) AddValue(v uint8) { 207 | offset := this.ValueOffset() 208 | offset += this.ValueSize() 209 | size := 1 210 | pSize := this.Size() 211 | this.OpenGap(offset, size, pSize) 212 | this.SetSize(pSize + size) 213 | this.Buf[offset] = byte(v) 214 | offset++ 215 | } 216 | 217 | func (this LinkDiscoveryTLV) ValueOffset() int { 218 | offset := 2 219 | return offset 220 | } 221 | 222 | func (this LinkDiscoveryTLV) ValueSize() int { 223 | offset := this.ValueOffset() 224 | return this.Size() - offset 225 | } 226 | 227 | func NewLinkDiscoveryProtocolWithBuf(b []byte) LinkDiscoveryProtocol { 228 | return LinkDiscoveryProtocol{ethernet.Ethernet{packet.Packet{Buf: b}}} 229 | } 230 | 231 | func NewLinkDiscoveryProtocol() LinkDiscoveryProtocol { 232 | s := 14 233 | b := make([]byte, s) 234 | p := LinkDiscoveryProtocol{ethernet.Ethernet{packet.Packet{Buf: b}}} 235 | p.Init() 236 | return p 237 | } 238 | 239 | type LinkDiscoveryProtocol struct { 240 | ethernet.Ethernet 241 | } 242 | 243 | func (this LinkDiscoveryProtocol) minSize() int { 244 | return 14 245 | } 246 | 247 | func (this LinkDiscoveryProtocol) Clone() (LinkDiscoveryProtocol, error) { 248 | var newBuf bytes.Buffer 249 | _, err := io.CopyN(&newBuf, bytes.NewBuffer(this.Buf), int64(this.Size())) 250 | if err != nil { 251 | return NewLinkDiscoveryProtocol(), err 252 | } 253 | 254 | return NewLinkDiscoveryProtocolWithBuf(newBuf.Bytes()), nil 255 | } 256 | 257 | type LinkDiscoveryProtocolConn struct { 258 | net.Conn 259 | w *bufio.Writer 260 | buf []byte 261 | offset int 262 | } 263 | 264 | func NewLinkDiscoveryProtocolConn(c net.Conn) LinkDiscoveryProtocolConn { 265 | return LinkDiscoveryProtocolConn{ 266 | Conn: c, 267 | w: bufio.NewWriter(c), 268 | buf: make([]byte, packet.DefaultBufSize), 269 | } 270 | } 271 | 272 | func (c *LinkDiscoveryProtocolConn) WriteLinkDiscoveryProtocol(pkt LinkDiscoveryProtocol) error { 273 | s := pkt.Size() 274 | b := pkt.Buffer()[:s] 275 | n := 0 276 | for s > 0 { 277 | var err error 278 | if n, err = c.w.Write(b); err != nil { 279 | return fmt.Errorf("Error in write: %v", err) 280 | } 281 | s -= n 282 | } 283 | 284 | return nil 285 | } 286 | 287 | func (c *LinkDiscoveryProtocolConn) WriteLinkDiscoveryProtocols(pkts []LinkDiscoveryProtocol) error { 288 | for _, p := range pkts { 289 | if err := c.WriteLinkDiscoveryProtocol(p); err != nil { 290 | return err 291 | } 292 | } 293 | return nil 294 | } 295 | 296 | func (c *LinkDiscoveryProtocolConn) Flush() error { 297 | return c.w.Flush() 298 | } 299 | 300 | func (c *LinkDiscoveryProtocolConn) ReadLinkDiscoveryProtocol() (LinkDiscoveryProtocol, error) { 301 | pkts := make([]LinkDiscoveryProtocol, 1) 302 | _, err := c.ReadLinkDiscoveryProtocols(pkts) 303 | if err != nil { 304 | return NewLinkDiscoveryProtocol(), err 305 | } 306 | 307 | return pkts[0], nil 308 | } 309 | 310 | func (c *LinkDiscoveryProtocolConn) ReadLinkDiscoveryProtocols(pkts []LinkDiscoveryProtocol) (int, error) { 311 | if len(c.buf) == c.offset { 312 | newSize := packet.DefaultBufSize 313 | if newSize < len(c.buf) { 314 | newSize = 2 * len(c.buf) 315 | } 316 | 317 | buf := make([]byte, newSize) 318 | copy(buf, c.buf[:c.offset]) 319 | c.buf = buf 320 | } 321 | 322 | r, err := c.Conn.Read(c.buf[c.offset:]) 323 | if err != nil { 324 | return 0, err 325 | } 326 | 327 | r += c.offset 328 | 329 | s := 0 330 | n := 0 331 | for i := range pkts { 332 | p := NewLinkDiscoveryProtocolWithBuf(c.buf[s:]) 333 | 334 | pSize := p.Size() 335 | if pSize == 0 || r < s+pSize { 336 | break 337 | } 338 | 339 | pkts[i] = p 340 | s += pSize 341 | n++ 342 | } 343 | 344 | c.offset = r - s 345 | if c.offset < 0 { 346 | panic("Invalid value for offset") 347 | } 348 | 349 | c.buf = c.buf[s:] 350 | return n, nil 351 | } 352 | 353 | func (this *LinkDiscoveryProtocol) Init() { 354 | this.Ethernet.Init() 355 | // Invariants. 356 | this.SetType(uint16(35020)) // type 357 | } 358 | 359 | func (this LinkDiscoveryProtocol) Size() int { 360 | return 14 361 | } 362 | 363 | func ToLinkDiscoveryProtocol(p ethernet.Ethernet) (LinkDiscoveryProtocol, error) { 364 | if !IsLinkDiscoveryProtocol(p) { 365 | return NewLinkDiscoveryProtocolWithBuf(nil), errors.New("Cannot convert to lldp.LinkDiscoveryProtocol") 366 | } 367 | 368 | return NewLinkDiscoveryProtocolWithBuf(p.Buf), nil 369 | } 370 | 371 | func IsLinkDiscoveryProtocol(p ethernet.Ethernet) bool { 372 | return p.Type() == 35020 && true 373 | } 374 | 375 | func NewChassisTLVWithBuf(b []byte) ChassisTLV { 376 | return ChassisTLV{packet.Packet{Buf: b}} 377 | } 378 | 379 | func NewChassisTLV() ChassisTLV { 380 | s := 3 381 | b := make([]byte, s) 382 | p := ChassisTLV{packet.Packet{Buf: b}} 383 | p.Init() 384 | return p 385 | } 386 | 387 | type ChassisTLV struct { 388 | packet.Packet 389 | } 390 | 391 | func (this ChassisTLV) minSize() int { 392 | return 3 393 | } 394 | 395 | func (this ChassisTLV) Clone() (ChassisTLV, error) { 396 | var newBuf bytes.Buffer 397 | _, err := io.CopyN(&newBuf, bytes.NewBuffer(this.Buf), int64(this.Size())) 398 | if err != nil { 399 | return NewChassisTLV(), err 400 | } 401 | 402 | return NewChassisTLVWithBuf(newBuf.Bytes()), nil 403 | } 404 | 405 | type ChassisTLVConn struct { 406 | net.Conn 407 | w *bufio.Writer 408 | buf []byte 409 | offset int 410 | } 411 | 412 | func NewChassisTLVConn(c net.Conn) ChassisTLVConn { 413 | return ChassisTLVConn{ 414 | Conn: c, 415 | w: bufio.NewWriter(c), 416 | buf: make([]byte, packet.DefaultBufSize), 417 | } 418 | } 419 | 420 | func (c *ChassisTLVConn) WriteChassisTLV(pkt ChassisTLV) error { 421 | s := pkt.Size() 422 | b := pkt.Buffer()[:s] 423 | n := 0 424 | for s > 0 { 425 | var err error 426 | if n, err = c.w.Write(b); err != nil { 427 | return fmt.Errorf("Error in write: %v", err) 428 | } 429 | s -= n 430 | } 431 | 432 | return nil 433 | } 434 | 435 | func (c *ChassisTLVConn) WriteChassisTLVs(pkts []ChassisTLV) error { 436 | for _, p := range pkts { 437 | if err := c.WriteChassisTLV(p); err != nil { 438 | return err 439 | } 440 | } 441 | return nil 442 | } 443 | 444 | func (c *ChassisTLVConn) Flush() error { 445 | return c.w.Flush() 446 | } 447 | 448 | func (c *ChassisTLVConn) ReadChassisTLV() (ChassisTLV, error) { 449 | pkts := make([]ChassisTLV, 1) 450 | _, err := c.ReadChassisTLVs(pkts) 451 | if err != nil { 452 | return NewChassisTLV(), err 453 | } 454 | 455 | return pkts[0], nil 456 | } 457 | 458 | func (c *ChassisTLVConn) ReadChassisTLVs(pkts []ChassisTLV) (int, error) { 459 | if len(c.buf) == c.offset { 460 | newSize := packet.DefaultBufSize 461 | if newSize < len(c.buf) { 462 | newSize = 2 * len(c.buf) 463 | } 464 | 465 | buf := make([]byte, newSize) 466 | copy(buf, c.buf[:c.offset]) 467 | c.buf = buf 468 | } 469 | 470 | r, err := c.Conn.Read(c.buf[c.offset:]) 471 | if err != nil { 472 | return 0, err 473 | } 474 | 475 | r += c.offset 476 | 477 | s := 0 478 | n := 0 479 | for i := range pkts { 480 | p := NewChassisTLVWithBuf(c.buf[s:]) 481 | 482 | pSize := p.Size() 483 | if pSize == 0 || r < s+pSize { 484 | break 485 | } 486 | 487 | pkts[i] = p 488 | s += pSize 489 | n++ 490 | } 491 | 492 | c.offset = r - s 493 | if c.offset < 0 { 494 | panic("Invalid value for offset") 495 | } 496 | 497 | c.buf = c.buf[s:] 498 | return n, nil 499 | } 500 | 501 | func (this *ChassisTLV) Init() { 502 | // Invariants. 503 | } 504 | 505 | func (this ChassisTLV) Size() int { 506 | return 3 507 | } 508 | 509 | func ToChassisTLV(p packet.Packet) (ChassisTLV, error) { 510 | if !IsChassisTLV(p) { 511 | return NewChassisTLVWithBuf(nil), errors.New("Cannot convert to lldp.ChassisTLV") 512 | } 513 | 514 | return NewChassisTLVWithBuf(p.Buf), nil 515 | } 516 | 517 | func IsChassisTLV(p packet.Packet) bool { 518 | return true 519 | } 520 | 521 | func (this ChassisTLV) TypeAndLen() uint16 { 522 | offset := this.TypeAndLenOffset() 523 | res := binary.BigEndian.Uint16(this.Buf[offset:]) 524 | return res 525 | } 526 | 527 | func (this *ChassisTLV) SetTypeAndLen(t uint16) { 528 | offset := this.TypeAndLenOffset() 529 | binary.BigEndian.PutUint16(this.Buf[offset:], t) 530 | offset += 2 531 | } 532 | 533 | func (this ChassisTLV) TypeAndLenOffset() int { 534 | offset := 0 535 | return offset 536 | } 537 | 538 | func (this ChassisTLV) Subtype() uint8 { 539 | offset := this.SubtypeOffset() 540 | res := uint8(this.Buf[offset]) 541 | return res 542 | } 543 | 544 | func (this *ChassisTLV) SetSubtype(s uint8) { 545 | offset := this.SubtypeOffset() 546 | this.Buf[offset] = byte(s) 547 | offset++ 548 | } 549 | 550 | func (this ChassisTLV) SubtypeOffset() int { 551 | offset := 2 552 | return offset 553 | } 554 | 555 | func NewChassisMacTLVWithBuf(b []byte) ChassisMacTLV { 556 | return ChassisMacTLV{ChassisTLV{packet.Packet{Buf: b}}} 557 | } 558 | 559 | func NewChassisMacTLV() ChassisMacTLV { 560 | s := 9 561 | b := make([]byte, s) 562 | p := ChassisMacTLV{ChassisTLV{packet.Packet{Buf: b}}} 563 | p.Init() 564 | return p 565 | } 566 | 567 | type ChassisMacTLV struct { 568 | ChassisTLV 569 | } 570 | 571 | func (this ChassisMacTLV) minSize() int { 572 | return 9 573 | } 574 | 575 | func (this ChassisMacTLV) Clone() (ChassisMacTLV, error) { 576 | var newBuf bytes.Buffer 577 | _, err := io.CopyN(&newBuf, bytes.NewBuffer(this.Buf), int64(this.Size())) 578 | if err != nil { 579 | return NewChassisMacTLV(), err 580 | } 581 | 582 | return NewChassisMacTLVWithBuf(newBuf.Bytes()), nil 583 | } 584 | 585 | type ChassisMacTLVConn struct { 586 | net.Conn 587 | w *bufio.Writer 588 | buf []byte 589 | offset int 590 | } 591 | 592 | func NewChassisMacTLVConn(c net.Conn) ChassisMacTLVConn { 593 | return ChassisMacTLVConn{ 594 | Conn: c, 595 | w: bufio.NewWriter(c), 596 | buf: make([]byte, packet.DefaultBufSize), 597 | } 598 | } 599 | 600 | func (c *ChassisMacTLVConn) WriteChassisMacTLV(pkt ChassisMacTLV) error { 601 | s := pkt.Size() 602 | b := pkt.Buffer()[:s] 603 | n := 0 604 | for s > 0 { 605 | var err error 606 | if n, err = c.w.Write(b); err != nil { 607 | return fmt.Errorf("Error in write: %v", err) 608 | } 609 | s -= n 610 | } 611 | 612 | return nil 613 | } 614 | 615 | func (c *ChassisMacTLVConn) WriteChassisMacTLVs(pkts []ChassisMacTLV) error { 616 | for _, p := range pkts { 617 | if err := c.WriteChassisMacTLV(p); err != nil { 618 | return err 619 | } 620 | } 621 | return nil 622 | } 623 | 624 | func (c *ChassisMacTLVConn) Flush() error { 625 | return c.w.Flush() 626 | } 627 | 628 | func (c *ChassisMacTLVConn) ReadChassisMacTLV() (ChassisMacTLV, error) { 629 | pkts := make([]ChassisMacTLV, 1) 630 | _, err := c.ReadChassisMacTLVs(pkts) 631 | if err != nil { 632 | return NewChassisMacTLV(), err 633 | } 634 | 635 | return pkts[0], nil 636 | } 637 | 638 | func (c *ChassisMacTLVConn) ReadChassisMacTLVs(pkts []ChassisMacTLV) (int, error) { 639 | if len(c.buf) == c.offset { 640 | newSize := packet.DefaultBufSize 641 | if newSize < len(c.buf) { 642 | newSize = 2 * len(c.buf) 643 | } 644 | 645 | buf := make([]byte, newSize) 646 | copy(buf, c.buf[:c.offset]) 647 | c.buf = buf 648 | } 649 | 650 | r, err := c.Conn.Read(c.buf[c.offset:]) 651 | if err != nil { 652 | return 0, err 653 | } 654 | 655 | r += c.offset 656 | 657 | s := 0 658 | n := 0 659 | for i := range pkts { 660 | p := NewChassisMacTLVWithBuf(c.buf[s:]) 661 | 662 | pSize := p.Size() 663 | if pSize == 0 || r < s+pSize { 664 | break 665 | } 666 | 667 | pkts[i] = p 668 | s += pSize 669 | n++ 670 | } 671 | 672 | c.offset = r - s 673 | if c.offset < 0 { 674 | panic("Invalid value for offset") 675 | } 676 | 677 | c.buf = c.buf[s:] 678 | return n, nil 679 | } 680 | 681 | func (this *ChassisMacTLV) Init() { 682 | this.ChassisTLV.Init() 683 | // Invariants. 684 | this.SetTypeAndLen(uint16(519)) // type_and_len 685 | this.SetSubtype(uint8(4)) // subtype 686 | } 687 | 688 | func (this ChassisMacTLV) Size() int { 689 | return 9 690 | } 691 | 692 | func ToChassisMacTLV(p ChassisTLV) (ChassisMacTLV, error) { 693 | if !IsChassisMacTLV(p) { 694 | return NewChassisMacTLVWithBuf(nil), errors.New("Cannot convert to lldp.ChassisMacTLV") 695 | } 696 | 697 | return NewChassisMacTLVWithBuf(p.Buf), nil 698 | } 699 | 700 | func IsChassisMacTLV(p ChassisTLV) bool { 701 | return p.TypeAndLen() == 519 && p.Subtype() == 4 && true 702 | } 703 | 704 | func (this ChassisMacTLV) MacAddr() [6]uint8 { 705 | offset := this.MacAddrOffset() 706 | packet_size := this.Size() 707 | size := packet_size - offset 708 | count := 6 709 | i := 0 710 | var res [6]uint8 711 | for size > 0 && count > 0 && packet_size > offset { 712 | elem := uint8(this.Buf[offset]) 713 | if size < 1 { 714 | break 715 | } 716 | size -= 1 717 | offset += 1 718 | count-- 719 | res[i] = elem 720 | i++ 721 | } 722 | return res 723 | } 724 | 725 | func (this *ChassisMacTLV) SetMacAddr(m [6]uint8) { 726 | offset := this.MacAddrOffset() 727 | for _, e := range m { 728 | this.Buf[offset] = byte(e) 729 | offset++ 730 | } 731 | } 732 | 733 | func (this ChassisMacTLV) MacAddrOffset() int { 734 | offset := 3 735 | return offset 736 | } 737 | -------------------------------------------------------------------------------- /nom/flow.go: -------------------------------------------------------------------------------- 1 | package nom 2 | 3 | import ( 4 | "encoding/gob" 5 | "fmt" 6 | "time" 7 | 8 | bh "github.com/kandoo/beehive" 9 | "github.com/kandoo/beehive/strings" 10 | ) 11 | 12 | // InPort is the input port field. 13 | type InPort UID 14 | 15 | func (in InPort) HasSameType(f Field) bool { 16 | switch f.(type) { 17 | case InPort: 18 | return true 19 | } 20 | return false 21 | } 22 | 23 | func (in InPort) Equals(f Field) bool { 24 | switch field := f.(type) { 25 | case InPort: 26 | return in == field 27 | } 28 | return false 29 | } 30 | 31 | func (in InPort) Subsumes(f Field) bool { 32 | return in.Equals(f) 33 | } 34 | 35 | func (in InPort) String() string { 36 | return fmt.Sprintf("in_port=%v", UID(in)) 37 | } 38 | 39 | // EthAddrField is a common type for EthDst and EthSrc. 40 | type EthAddrField struct { 41 | Addr MACAddr 42 | Mask MACAddr 43 | } 44 | 45 | // EthDst is the field for Ethernet destination address. 46 | type EthDst MaskedMACAddr 47 | 48 | func (e EthDst) HasSameType(f Field) bool { 49 | switch f.(type) { 50 | case EthDst: 51 | return true 52 | } 53 | return false 54 | } 55 | 56 | func (e EthDst) Equals(f Field) bool { 57 | switch field := f.(type) { 58 | case EthDst: 59 | return e == field 60 | } 61 | return false 62 | } 63 | 64 | func (e EthDst) Subsumes(f Field) bool { 65 | switch field := f.(type) { 66 | case EthDst: 67 | return MaskedMACAddr(e).Subsumes(MaskedMACAddr(field)) 68 | } 69 | return false 70 | } 71 | 72 | func (e EthDst) String() string { 73 | return fmt.Sprintf("eth_dst=%v", MaskedMACAddr(e)) 74 | } 75 | 76 | // EthSrc is the field for Ethernet source address. 77 | type EthSrc MaskedMACAddr 78 | 79 | func (e EthSrc) HasSameType(f Field) bool { 80 | switch f.(type) { 81 | case EthSrc: 82 | return true 83 | } 84 | return false 85 | } 86 | 87 | func (e EthSrc) Equals(f Field) bool { 88 | switch field := f.(type) { 89 | case EthSrc: 90 | return e == field 91 | } 92 | return false 93 | } 94 | 95 | func (e EthSrc) Subsumes(f Field) bool { 96 | switch field := f.(type) { 97 | case EthSrc: 98 | return MaskedMACAddr(e).Subsumes(MaskedMACAddr(field)) 99 | } 100 | return false 101 | } 102 | 103 | func (e EthSrc) String() string { 104 | return fmt.Sprintf("eth_src=%v", MaskedMACAddr(e)) 105 | } 106 | 107 | // EthType represents the field for an Ethernet type. 108 | type EthType uint16 109 | 110 | func (e EthType) HasSameType(f Field) bool { 111 | switch f.(type) { 112 | case EthType: 113 | return true 114 | } 115 | return false 116 | } 117 | 118 | func (e EthType) Equals(f Field) bool { 119 | switch field := f.(type) { 120 | case EthType: 121 | return e == field 122 | } 123 | return false 124 | } 125 | 126 | func (e EthType) Subsumes(f Field) bool { 127 | return e.Equals(f) 128 | } 129 | 130 | func (e EthType) String() string { 131 | return fmt.Sprintf("eth_type=%v", uint16(e)) 132 | } 133 | 134 | // VLANID represents the field for the VLAN ID. 135 | type VLANID uint16 136 | 137 | func (e VLANID) HasSameType(f Field) bool { 138 | switch f.(type) { 139 | case VLANID: 140 | return true 141 | } 142 | return false 143 | } 144 | 145 | func (e VLANID) Equals(f Field) bool { 146 | switch field := f.(type) { 147 | case VLANID: 148 | return e == field 149 | } 150 | return false 151 | } 152 | 153 | func (e VLANID) Subsumes(f Field) bool { 154 | return e.Equals(f) 155 | } 156 | 157 | func (e VLANID) String() string { 158 | return fmt.Sprintf("vlan=%v", uint16(e)) 159 | } 160 | 161 | // VLANPCP represents the field for the VLAN PCP. 162 | type VLANPCP uint8 163 | 164 | func (p VLANPCP) HasSameType(f Field) bool { 165 | _, ok := f.(VLANPCP) 166 | return ok 167 | } 168 | 169 | func (p VLANPCP) Equals(f Field) bool { 170 | if fp, ok := f.(VLANPCP); ok { 171 | return p == fp 172 | } 173 | return false 174 | } 175 | 176 | func (p VLANPCP) Subsumes(f Field) bool { 177 | return p.Equals(f) 178 | } 179 | 180 | func (p VLANPCP) String() string { 181 | return fmt.Sprintf("vlan_pcp=%v", uint8(p)) 182 | } 183 | 184 | type IPProto uint8 185 | 186 | func (p IPProto) HasSameType(f Field) bool { 187 | _, ok := f.(IPProto) 188 | return ok 189 | } 190 | 191 | func (p IPProto) Equals(f Field) bool { 192 | if fp, ok := f.(IPProto); ok { 193 | return p == fp 194 | } 195 | return false 196 | } 197 | 198 | func (p IPProto) Subsumes(f Field) bool { 199 | return p.Equals(f) 200 | } 201 | 202 | func (p IPProto) String() string { 203 | return fmt.Sprint("ip_proto=%v", uint8(p)) 204 | } 205 | 206 | type IPv4Src MaskedIPv4Addr 207 | 208 | func (ip IPv4Src) HasSameType(f Field) bool { 209 | switch f.(type) { 210 | case IPv4Src: 211 | return true 212 | } 213 | return false 214 | } 215 | 216 | func (ip IPv4Src) Equals(f Field) bool { 217 | switch field := f.(type) { 218 | case IPv4Src: 219 | return ip == field 220 | } 221 | return false 222 | } 223 | 224 | func (ip IPv4Src) Subsumes(f Field) bool { 225 | switch field := f.(type) { 226 | case IPv4Src: 227 | return MaskedIPv4Addr(ip).Subsumes(MaskedIPv4Addr(field)) 228 | } 229 | return false 230 | } 231 | 232 | func (ip IPv4Src) String() string { 233 | return MaskedIPv4Addr(ip).String() 234 | } 235 | 236 | type IPv4Dst MaskedIPv4Addr 237 | 238 | func (ip IPv4Dst) HasSameType(f Field) bool { 239 | switch f.(type) { 240 | case IPv4Dst: 241 | return true 242 | } 243 | return false 244 | } 245 | 246 | func (ip IPv4Dst) Equals(f Field) bool { 247 | switch field := f.(type) { 248 | case IPv4Dst: 249 | return ip == field 250 | } 251 | return false 252 | } 253 | 254 | func (ip IPv4Dst) Subsumes(f Field) bool { 255 | switch field := f.(type) { 256 | case IPv4Dst: 257 | return MaskedIPv4Addr(ip).Subsumes(MaskedIPv4Addr(field)) 258 | } 259 | return false 260 | } 261 | 262 | func (ip IPv4Dst) String() string { 263 | return fmt.Sprintf("ipv4_dst=%v", MaskedIPv4Addr(ip)) 264 | } 265 | 266 | type IPv6Src MaskedIPv6Addr 267 | 268 | func (ip IPv6Src) HasSameType(f Field) bool { 269 | switch f.(type) { 270 | case IPv6Src: 271 | return true 272 | } 273 | return false 274 | } 275 | 276 | func (ip IPv6Src) Equals(f Field) bool { 277 | switch field := f.(type) { 278 | case IPv6Src: 279 | return ip == field 280 | } 281 | return false 282 | } 283 | 284 | func (ip IPv6Src) Subsumes(f Field) bool { 285 | switch field := f.(type) { 286 | case IPv6Src: 287 | return MaskedIPv6Addr(ip).Subsumes(MaskedIPv6Addr(field)) 288 | } 289 | return false 290 | } 291 | 292 | func (ip IPv6Src) String() string { 293 | return fmt.Sprintf("ipv6_src=%v", MaskedIPv6Addr(ip)) 294 | } 295 | 296 | type IPv6Dst MaskedIPv6Addr 297 | 298 | func (ip IPv6Dst) HasSameType(f Field) bool { 299 | switch f.(type) { 300 | case IPv6Dst: 301 | return true 302 | } 303 | return false 304 | } 305 | 306 | func (ip IPv6Dst) Equals(f Field) bool { 307 | switch field := f.(type) { 308 | case IPv6Dst: 309 | return ip == field 310 | } 311 | return false 312 | } 313 | 314 | func (ip IPv6Dst) Subsumes(f Field) bool { 315 | switch field := f.(type) { 316 | case IPv6Dst: 317 | return MaskedIPv6Addr(ip).Subsumes(MaskedIPv6Addr(field)) 318 | } 319 | return false 320 | } 321 | 322 | func (ip IPv6Dst) String() string { 323 | return fmt.Sprintf("ipv6_dst=%v", MaskedIPv6Addr(ip)) 324 | } 325 | 326 | type TransportPortSrc uint16 327 | 328 | func (p TransportPortSrc) HasSameType(f Field) bool { 329 | switch f.(type) { 330 | case TransportPortSrc: 331 | return true 332 | } 333 | return false 334 | } 335 | 336 | func (p TransportPortSrc) Equals(f Field) bool { 337 | switch field := f.(type) { 338 | case TransportPortSrc: 339 | return p == field 340 | } 341 | return false 342 | } 343 | 344 | func (p TransportPortSrc) Subsumes(f Field) bool { 345 | return p.Equals(f) 346 | } 347 | 348 | func (p TransportPortSrc) String() string { 349 | return fmt.Sprintf("tp_port_src=%v", uint16(p)) 350 | } 351 | 352 | type TransportPortDst uint16 353 | 354 | func (p TransportPortDst) HasSameType(f Field) bool { 355 | switch f.(type) { 356 | case TransportPortDst: 357 | return true 358 | } 359 | return false 360 | } 361 | 362 | func (p TransportPortDst) Equals(f Field) bool { 363 | switch field := f.(type) { 364 | case TransportPortDst: 365 | return p == field 366 | } 367 | return false 368 | } 369 | 370 | func (p TransportPortDst) Subsumes(f Field) bool { 371 | return p.Equals(f) 372 | } 373 | 374 | func (p TransportPortDst) String() string { 375 | return fmt.Sprintf("tp_port_dst=%v", uint16(p)) 376 | } 377 | 378 | // Valid values for EthType. 379 | const ( 380 | EthTypeIPv4 EthType = 0x0800 381 | EthTypeIPv6 = 0x86DD 382 | EthTypeARP = 0x0806 383 | ) 384 | 385 | type Field interface { 386 | HasSameType(f Field) bool 387 | Equals(f Field) bool 388 | Subsumes(f Field) bool 389 | } 390 | 391 | // Match is a collection of fields that will match packets. 392 | type Match struct { 393 | Fields []Field 394 | } 395 | 396 | func (m Match) String() string { 397 | l := len(m.Fields) 398 | if l == 0 { 399 | return "match(*)" 400 | } 401 | a := make([]interface{}, len(m.Fields)) 402 | for i := range m.Fields { 403 | a[i] = m.Fields[i] 404 | } 405 | return fmt.Sprintf("match(%v)", strings.Join(a, ",")) 406 | } 407 | 408 | // AddField adds the field to the list of fields in the match. 409 | func (m *Match) AddField(f Field) { 410 | m.Fields = append(m.Fields, f) 411 | } 412 | 413 | // Clone creates a copy of the match. 414 | func (m Match) Clone() Match { 415 | clone := Match{ 416 | Fields: make([]Field, len(m.Fields)), 417 | } 418 | copy(clone.Fields, m.Fields) 419 | return clone 420 | } 421 | 422 | func (m Match) InPort() (InPort, bool) { 423 | for _, f := range m.Fields { 424 | switch field := f.(type) { 425 | case InPort: 426 | return field, true 427 | } 428 | } 429 | return InPort(0), false 430 | } 431 | 432 | func (m Match) EthType() (EthType, bool) { 433 | for _, f := range m.Fields { 434 | switch field := f.(type) { 435 | case EthType: 436 | return field, true 437 | } 438 | } 439 | return EthType(0), false 440 | } 441 | 442 | func (m Match) EthSrc() (EthSrc, bool) { 443 | for _, f := range m.Fields { 444 | switch field := f.(type) { 445 | case EthSrc: 446 | return field, true 447 | } 448 | } 449 | return EthSrc{}, false 450 | } 451 | 452 | func (m Match) EthDst() (EthDst, bool) { 453 | for _, f := range m.Fields { 454 | switch field := f.(type) { 455 | case EthDst: 456 | return field, true 457 | } 458 | } 459 | return EthDst{}, false 460 | } 461 | 462 | func (m Match) IPv4Src() (IPv4Src, bool) { 463 | for _, f := range m.Fields { 464 | switch field := f.(type) { 465 | case IPv4Src: 466 | return field, true 467 | } 468 | } 469 | return IPv4Src{}, false 470 | } 471 | 472 | func (m Match) IPv4Dst() (IPv4Dst, bool) { 473 | for _, f := range m.Fields { 474 | switch field := f.(type) { 475 | case IPv4Dst: 476 | return field, true 477 | } 478 | } 479 | return IPv4Dst{}, false 480 | } 481 | 482 | func (m Match) IPv6Src() (IPv6Src, bool) { 483 | for _, f := range m.Fields { 484 | switch field := f.(type) { 485 | case IPv6Src: 486 | return field, true 487 | } 488 | } 489 | return IPv6Src{}, false 490 | } 491 | 492 | func (m Match) IPv6Dst() (IPv6Dst, bool) { 493 | for _, f := range m.Fields { 494 | switch field := f.(type) { 495 | case IPv6Dst: 496 | return field, true 497 | } 498 | } 499 | return IPv6Dst{}, false 500 | } 501 | 502 | func (m Match) VLANID() (VLANID, bool) { 503 | for _, f := range m.Fields { 504 | switch field := f.(type) { 505 | case VLANID: 506 | return field, true 507 | } 508 | } 509 | return VLANID(0), false 510 | } 511 | 512 | func (m Match) VLANPCP() (VLANPCP, bool) { 513 | for _, f := range m.Fields { 514 | switch field := f.(type) { 515 | case VLANPCP: 516 | return field, true 517 | } 518 | } 519 | return VLANPCP(0), false 520 | } 521 | 522 | func (m Match) TransportPortSrc() (TransportPortSrc, bool) { 523 | for _, f := range m.Fields { 524 | switch field := f.(type) { 525 | case TransportPortSrc: 526 | return field, true 527 | } 528 | } 529 | return TransportPortSrc(0), false 530 | } 531 | 532 | func (m Match) TransportPortDst() (TransportPortDst, bool) { 533 | for _, f := range m.Fields { 534 | switch field := f.(type) { 535 | case TransportPortDst: 536 | return field, true 537 | } 538 | } 539 | return TransportPortDst(0), false 540 | } 541 | 542 | func (m Match) Subsumes(thatm Match) bool { 543 | for _, thisf := range m.Fields { 544 | if thatm.countFields(thisf.HasSameType) != 1 { 545 | return false 546 | } 547 | if thatm.countFields(thisf.Subsumes) != 1 { 548 | return false 549 | } 550 | } 551 | return true 552 | } 553 | 554 | func (m Match) Equals(thatm Match) bool { 555 | if len(m.Fields) != len(thatm.Fields) { 556 | return false 557 | } 558 | for _, thisf := range m.Fields { 559 | if thatm.countFields(thisf.HasSameType) != 1 { 560 | return false 561 | } 562 | if thatm.countFields(thisf.Equals) != 1 { 563 | return false 564 | } 565 | } 566 | return true 567 | } 568 | 569 | func (m Match) assertFields(checker func(f Field) bool) bool { 570 | for _, f := range m.Fields { 571 | if !checker(f) { 572 | return false 573 | } 574 | } 575 | return true 576 | } 577 | 578 | func (m Match) countFields(checker func(f Field) bool) int { 579 | count := 0 580 | for _, f := range m.Fields { 581 | if checker(f) { 582 | count++ 583 | } 584 | } 585 | return count 586 | } 587 | 588 | type Action interface { 589 | Equals(a Action) bool 590 | } 591 | 592 | type ActionForward struct { 593 | Ports []UID 594 | } 595 | 596 | func (a ActionForward) String() string { 597 | return fmt.Sprintf("forward(to=%v)", a.Ports) 598 | } 599 | 600 | func (a ActionForward) Equals(thata Action) bool { 601 | thataf, ok := thata.(ActionForward) 602 | if !ok { 603 | return false 604 | } 605 | ports := make(map[UID]struct{}) 606 | for _, p := range a.Ports { 607 | ports[p] = struct{}{} 608 | } 609 | for _, p := range thataf.Ports { 610 | if _, ok := ports[p]; !ok { 611 | return false 612 | } 613 | } 614 | return true 615 | } 616 | 617 | type ActionDrop struct { 618 | } 619 | 620 | func (a ActionDrop) Equals(thata Action) bool { 621 | _, ok := thata.(ActionDrop) 622 | return ok 623 | } 624 | 625 | func (a ActionDrop) String() string { 626 | return fmt.Sprintf("drop") 627 | } 628 | 629 | type ActionFlood struct { 630 | InPort UID 631 | } 632 | 633 | func (a ActionFlood) String() string { 634 | return fmt.Sprintf("flood(except=%v)", a.InPort) 635 | } 636 | 637 | func (a ActionFlood) Equals(thata Action) bool { 638 | thataf, ok := thata.(ActionFlood) 639 | if !ok { 640 | return false 641 | } 642 | return a.InPort == thataf.InPort 643 | } 644 | 645 | type ActionSendToController struct{} 646 | 647 | func (a ActionSendToController) Equals(thata Action) bool { 648 | _, ok := thata.(ActionSendToController) 649 | return ok 650 | } 651 | 652 | type ActionPushVLAN struct { 653 | ID VLANID 654 | } 655 | 656 | func (a ActionPushVLAN) Equals(thata Action) bool { 657 | thatap, ok := thata.(ActionPushVLAN) 658 | if !ok { 659 | return false 660 | } 661 | return a.ID == thatap.ID 662 | } 663 | 664 | type ActionPopVLAN struct{} 665 | 666 | func (a ActionPopVLAN) Equals(thata Action) bool { 667 | _, ok := thata.(ActionPopVLAN) 668 | return ok 669 | } 670 | 671 | type ActionWriteFields struct { 672 | Fields []Field 673 | } 674 | 675 | func (a ActionWriteFields) Equals(thata Action) bool { 676 | thataw, ok := thata.(ActionWriteFields) 677 | if !ok { 678 | return false 679 | } 680 | for i := range a.Fields { 681 | if !a.Fields[i].Equals(thataw.Fields[i]) { 682 | return false 683 | } 684 | } 685 | return true 686 | } 687 | 688 | // FlowEntry represents a match-action rule for a specific node. 689 | type FlowEntry struct { 690 | ID string // ID is defined by the subscriber, not globally unique. 691 | Node UID 692 | Match Match 693 | Actions []Action 694 | Priority uint16 695 | IdleTimeout time.Duration 696 | HardTimeout time.Duration 697 | } 698 | 699 | func (f FlowEntry) String() string { 700 | a := make([]interface{}, len(f.Actions)) 701 | for i := range f.Actions { 702 | a[i] = f.Actions[i] 703 | } 704 | astr := strings.Join(a, ",") 705 | return fmt.Sprintf("flow(%v=>%v,node=%v,priority=%v,idleto=%v,hardto=%v)", 706 | f.Match, astr, f.Node, f.Priority, f.IdleTimeout, f.HardTimeout) 707 | } 708 | 709 | func (f FlowEntry) Equals(thatf FlowEntry) bool { 710 | if f.Node != thatf.Node || f.Priority != thatf.Priority || 711 | len(f.Actions) != len(thatf.Actions) { 712 | 713 | return false 714 | } 715 | for i := range f.Actions { 716 | if !f.Actions[i].Equals(thatf.Actions[i]) { 717 | return false 718 | } 719 | } 720 | return f.Match.Equals(thatf.Match) 721 | } 722 | 723 | // Subsumes returns whether everything in f is equal to thatf except that f's 724 | // match subsumes thatf's match. 725 | func (f FlowEntry) Subsumes(thatf FlowEntry) bool { 726 | if f.Node != thatf.Node || f.Priority != thatf.Priority || 727 | len(f.Actions) != len(thatf.Actions) { 728 | 729 | return false 730 | } 731 | for i := range f.Actions { 732 | if !f.Actions[i].Equals(thatf.Actions[i]) { 733 | return false 734 | } 735 | } 736 | return f.Match.Subsumes(thatf.Match) 737 | } 738 | 739 | // AddFlowEntry is a message emitted to install a flow entry on a node. 740 | type AddFlowEntry struct { 741 | Subscriber bh.AppCellKey 742 | Flow FlowEntry 743 | } 744 | 745 | // DelFlowEntry is emitted to remove the flow entries with the given match. 746 | // If Exact is false, it removes all flow entries that are subsumed by the 747 | // given match. 748 | type DelFlowEntry struct { 749 | Match Match 750 | Exact bool 751 | } 752 | 753 | // FlowEntryDeleted is emitted (broadcasted and also sent to the subscriber of 754 | // the flow) when a flow is deleted. 755 | type FlowEntryDeleted struct { 756 | Flow FlowEntry 757 | } 758 | 759 | // FlowEntryAdded is emitted (broadcasted and also sent to the subscriber of the 760 | // flow) when a flow is added. If the flow already existed, the message is 761 | // emitted to the subscriber. 762 | type FlowEntryAdded struct { 763 | Flow FlowEntry 764 | } 765 | 766 | func init() { 767 | gob.Register(ActionDrop{}) 768 | gob.Register(ActionFlood{}) 769 | gob.Register(ActionForward{}) 770 | gob.Register(ActionPopVLAN{}) 771 | gob.Register(ActionPushVLAN{}) 772 | gob.Register(ActionSendToController{}) 773 | gob.Register(ActionWriteFields{}) 774 | gob.Register(AddFlowEntry{}) 775 | gob.Register(DelFlowEntry{}) 776 | gob.Register(EthAddrField{}) 777 | gob.Register(EthDst{}) 778 | gob.Register(EthSrc{}) 779 | gob.Register(EthType(0)) 780 | gob.Register(FlowEntryAdded{}) 781 | gob.Register(FlowEntryDeleted{}) 782 | gob.Register(FlowEntry{}) 783 | gob.Register(IPv4Dst{}) 784 | gob.Register(IPv4Src{}) 785 | gob.Register(IPv6Dst{}) 786 | gob.Register(IPv6Src{}) 787 | gob.Register(InPort(0)) 788 | gob.Register(Match{}) 789 | gob.Register(TransportPortDst(0)) 790 | gob.Register(TransportPortSrc(0)) 791 | gob.Register(VLANID(0)) 792 | gob.Register(VLANPCP(0)) 793 | } 794 | --------------------------------------------------------------------------------