diff --git a/.travis.yml b/.travis.yml index 38d3d3a..b6b305a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,5 +39,12 @@ matrix: services: - docker script: test/travis_script.sh + - go: 1.7 + sudo: required + env: + - TEST=test/netlink/iptables_test.py + services: + - docker + script: test/travis_script.sh go_import_path: github.com/osrg/goplane diff --git a/Dockerfile b/Dockerfile index 5144da6..253df41 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,7 @@ FROM osrg/gobgp MAINTAINER ISHIDA Wataru +RUN apt-get install -qy iptables ENV GO15VENDOREXPERIMENT 1 RUN curl https://glide.sh/get | sh ADD . $GOPATH/src/github.com/osrg/goplane/ diff --git a/config/config.go b/config/config.go index 5c744be..88c0531 100644 --- a/config/config.go +++ b/config/config.go @@ -40,6 +40,7 @@ type Iptables struct { } type Config struct { + RouterID string `mapstructure:"router-id"` Dataplane Dataplane `mapstructure:"dataplane"` Iptables Iptables `mapstructure:"iptables"` BGP bgpconfig.BgpConfigSet `mapstructure:"bgp"` diff --git a/dataplane.go b/dataplane.go new file mode 100644 index 0000000..dd66b91 --- /dev/null +++ b/dataplane.go @@ -0,0 +1,127 @@ +// Copyright (C) 2017 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "fmt" + "net" + "sync" + + log "github.com/Sirupsen/logrus" + "github.com/osrg/goplane/config" + proto "github.com/osrg/goplane/protocol" +) + +type Dataplane struct { + routerID net.IP + protos map[proto.ProtocolType]proto.Protocol + routeEventCh chan []proto.EntryEvent + m sync.RWMutex +} + +func NewDataplane() *Dataplane { + d := &Dataplane{ + protos: make(map[proto.ProtocolType]proto.Protocol), + routeEventCh: make(chan []proto.EntryEvent, 0), + } + go func() { + log.Fatal(d.serve()) + }() + return d +} + +func (d *Dataplane) serve() error { + for { + for _, ev := range <-d.routeEventCh { + log.Info("ev:", ev) + d.m.RLock() + for _, proto := range d.protos { + if ev.From == proto.Type() || ev.Entry.Match() == nil { + continue + } + var err error + if ev.IsDel { + err = proto.DeleteEntry(ev.Entry) + } else { + err = proto.AddEntry(ev.Entry) + } + if err != nil { + log.Errorf("err: %v", err) + } + } + d.m.RUnlock() + } + } + return nil +} + +func (d *Dataplane) SetRouterID(id net.IP) error { + d.m.RLock() + defer d.m.RUnlock() + for _, proto := range d.protos { + if err := proto.SetRouterID(id); err != nil { + return err + } + } + return nil +} + +func (d *Dataplane) AddProtocol(p proto.Protocol) error { + d.m.Lock() + defer d.m.Unlock() + if _, y := d.protos[p.Type()]; y { + return fmt.Errorf("protocol %d already exists", p.Type()) + } + d.protos[p.Type()] = p + w, err := p.WatchEntry() + if err != nil { + return err + } + if w != nil { + go func() { + for { + rs, err := w.Recv() + if err != nil { + log.Fatalf("failed recv routes: %s", err) + } + d.routeEventCh <- rs + } + }() + } + return nil +} + +func (d *Dataplane) AddVirtualNetwork(c config.VirtualNetwork) error { + d.m.RLock() + defer d.m.RUnlock() + for _, proto := range d.protos { + if err := proto.AddVirtualNetwork(d.routerID.String(), c); err != nil { + return err + } + } + return nil +} + +func (d *Dataplane) DeleteVirtualNetwork(c config.VirtualNetwork) error { + d.m.RLock() + defer d.m.RUnlock() + for _, proto := range d.protos { + if err := proto.DeleteVirtualNetwork(c); err != nil { + return err + } + } + return nil +} diff --git a/main.go b/main.go index 9d47613..528366f 100644 --- a/main.go +++ b/main.go @@ -18,23 +18,23 @@ package main import ( "io/ioutil" "log/syslog" + "net" "os" "os/signal" "runtime" "strings" "syscall" + "time" log "github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus/hooks/syslog" "github.com/jessevdk/go-flags" "github.com/osrg/goplane/config" - "github.com/osrg/goplane/iptables" - "github.com/osrg/goplane/netlink" + p "github.com/osrg/goplane/protocol" + "github.com/osrg/goplane/protocol/iptables" + "github.com/osrg/goplane/protocol/netlink" - bgpapi "github.com/osrg/gobgp/api" bgpconfig "github.com/osrg/gobgp/config" - "github.com/osrg/gobgp/packet/bgp" - bgpserver "github.com/osrg/gobgp/server" ) type Dataplaner interface { @@ -157,22 +157,11 @@ func main() { go config.ReadConfigfileServe(opts.ConfigFile, opts.ConfigType, configCh, bgpConfigCh, reloadCh) reloadCh <- true - var bgpServer *bgpserver.BgpServer - if !opts.Remote { - bgpServer = bgpserver.NewBgpServer() - go bgpServer.Serve() - grpcServer := bgpapi.NewGrpcServer(bgpServer, opts.GrpcHost) - go func() { - if err := grpcServer.Serve(); err != nil { - log.Fatalf("failed to listen grpc port: %s", err) - } - }() - } + bgpProtocol := p.NewGoBGPProtocol() + dataplane := NewDataplane() + dataplane.AddProtocol(bgpProtocol) - var dataplane Dataplaner var d *config.Dataplane - var c *bgpconfig.BgpConfigSet - var fsAgent *iptables.FlowspecAgent for { select { case newConfig := <-bgpConfigCh: @@ -180,104 +169,32 @@ func main() { log.Warn("running in BGP remote mode. you can't configure BGP daemon via configuration file now") continue } - - var added, deleted, updated []bgpconfig.Neighbor - var updatePolicy bool - - if c == nil { - c = newConfig - if err := bgpServer.Start(&newConfig.Global); err != nil { - log.Fatalf("failed to set global config: %s", err) - } - if newConfig.Zebra.Config.Enabled { - if err := bgpServer.StartZebraClient(&newConfig.Zebra.Config); err != nil { - log.Fatalf("failed to set zebra config: %s", err) - } - } - if len(newConfig.Collector.Config.Url) > 0 { - if err := bgpServer.StartCollector(&newConfig.Collector.Config); err != nil { - log.Fatalf("failed to set collector config: %s", err) - } - } - for _, c := range newConfig.RpkiServers { - if err := bgpServer.AddRpki(&c.Config); err != nil { - log.Fatalf("failed to set rpki config: %s", err) - } - } - for _, c := range newConfig.BmpServers { - if err := bgpServer.AddBmp(&c.Config); err != nil { - log.Fatalf("failed to set bmp config: %s", err) - } - } - for _, c := range newConfig.MrtDump { - if len(c.Config.FileName) == 0 { - continue - } - if err := bgpServer.EnableMrt(&c.Config); err != nil { - log.Fatalf("failed to set mrt config: %s", err) - } - } - p := bgpconfig.ConfigSetToRoutingPolicy(newConfig) - if err := bgpServer.UpdatePolicy(*p); err != nil { - log.Fatalf("failed to set routing policy: %s", err) - } - - added = newConfig.Neighbors - if opts.GracefulRestart { - for i, n := range added { - if n.GracefulRestart.Config.Enabled { - added[i].GracefulRestart.State.LocalRestarting = true - } - } - } - - } else { - added, deleted, updated, updatePolicy = bgpconfig.UpdateConfig(c, newConfig) - if updatePolicy { - log.Info("Policy config is updated") - p := bgpconfig.ConfigSetToRoutingPolicy(newConfig) - bgpServer.UpdatePolicy(*p) - } - c = newConfig + if err := bgpProtocol.UpdateConfig(newConfig); err != nil { + log.Fatalf("failed to update BGP config: %s", err) } - - for i, p := range added { - log.Infof("Peer %v is added", p.Config.NeighborAddress) - bgpServer.AddNeighbor(&added[i]) - } - for i, p := range deleted { - log.Infof("Peer %v is deleted", p.Config.NeighborAddress) - bgpServer.DeleteNeighbor(&deleted[i]) - } - for i, p := range updated { - log.Infof("Peer %v is updated", p.Config.NeighborAddress) - u, _ := bgpServer.UpdateNeighbor(&updated[i]) - updatePolicy = updatePolicy || u - } - - if updatePolicy { - bgpServer.SoftResetIn("", bgp.RouteFamily(0)) - } - case newConfig := <-configCh: - if dataplane == nil { + if d == nil { switch newConfig.Dataplane.Type { case "netlink": log.Debug("new dataplane: netlink") - dataplane = netlink.NewDataplane(newConfig, opts.GrpcHost) - go func() { - err := dataplane.Serve() - if err != nil { - log.Errorf("dataplane finished with err: %s", err) - } - }() + dataplane.AddProtocol(netlink.NewNetlinkProtocol()) default: log.Errorf("Invalid dataplane type(%s). dataplane engine can't be started", newConfig.Dataplane.Type) } + + time.Sleep(time.Millisecond) + + if err := dataplane.SetRouterID(net.ParseIP(newConfig.RouterID)); err != nil { + log.Fatal(err) + } + + if newConfig.Iptables.Enabled { + dataplane.AddProtocol(iptables.NewIPTablesProtocol(newConfig.Iptables)) + } } + d = &newConfig.Dataplane as, ds := config.UpdateConfig(d, newConfig.Dataplane) - d = &newConfig.Dataplane for _, v := range as { log.Infof("VirtualNetwork %s is added", v.RD) @@ -288,16 +205,6 @@ func main() { dataplane.DeleteVirtualNetwork(v) } - if fsAgent == nil && newConfig.Iptables.Enabled { - fsAgent = iptables.NewFlowspecAgent(opts.GrpcHost, newConfig.Iptables) - go func() { - err := fsAgent.Serve() - if err != nil { - log.Errorf("flowspec agent finished with err: %s", err) - } - }() - } - case sig := <-sigCh: switch sig { case syscall.SIGHUP: diff --git a/netlink/dataplane.go b/netlink/dataplane.go deleted file mode 100644 index c7451e7..0000000 --- a/netlink/dataplane.go +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package netlink - -import ( - "fmt" - "net" - "time" - - log "github.com/Sirupsen/logrus" - "github.com/osrg/gobgp/client" - bgpconfig "github.com/osrg/gobgp/config" - "github.com/osrg/gobgp/packet/bgp" - "github.com/osrg/gobgp/table" - "github.com/osrg/goplane/config" - "github.com/vishvananda/netlink" - "gopkg.in/tomb.v2" -) - -type Dataplane struct { - t tomb.Tomb - config *config.Config - modRibCh chan []*table.Path - advPathCh chan *table.Path - vnMap map[string]*VirtualNetwork - addVnCh chan config.VirtualNetwork - delVnCh chan config.VirtualNetwork - grpcHost string - client *client.Client - routerId string - localAS uint32 -} - -func (d *Dataplane) getNexthop(path *table.Path) (int, net.IP, int) { - var flags int - if path == nil || path.IsLocal() { - return 0, nil, flags - } - nh := path.GetNexthop() - if nh.To4() != nil { - return 0, nh.To4(), flags - } - list, err := netlink.NeighList(0, netlink.FAMILY_V6) - if err != nil { - log.Errorf("failed to get neigh list: %s", err) - return 0, nil, flags - } - var neigh *netlink.Neigh - for _, n := range list { - if n.IP.Equal(nh) { - neigh = &n - break - } - } - if neigh == nil { - log.Warnf("no neighbor info for %s", path) - return 0, nil, flags - } - list, err = netlink.NeighList(neigh.LinkIndex, netlink.FAMILY_V4) - if err != nil { - log.Errorf("failed to get neigh list: %s", err) - return 0, nil, flags - } - flags = int(netlink.FLAG_ONLINK) - for _, n := range list { - if n.HardwareAddr.String() == neigh.HardwareAddr.String() { - return n.LinkIndex, n.IP.To4(), flags - } - } - nh = net.IPv4(169, 254, 0, 1) - err = netlink.NeighAdd(&netlink.Neigh{ - LinkIndex: neigh.LinkIndex, - State: netlink.NUD_PERMANENT, - IP: nh, - HardwareAddr: neigh.HardwareAddr, - }) - if err != nil { - log.Errorf("neigh add: %s", err) - } - return neigh.LinkIndex, nh, flags -} - -func (d *Dataplane) modRib(paths []*table.Path) error { - if len(paths) == 0 { - return nil - } - p := paths[0] - - dst, _ := netlink.ParseIPNet(p.GetNlri().String()) - route := &netlink.Route{ - Dst: dst, - Src: net.ParseIP(d.routerId), - } - - if len(paths) == 1 { - if p.IsLocal() { - return nil - } - link, gw, flags := d.getNexthop(p) - route.Gw = gw - route.LinkIndex = link - route.Flags = flags - } else { - mp := make([]*netlink.NexthopInfo, 0, len(paths)) - for _, path := range paths { - if path.IsLocal() { - continue - } - link, gw, flags := d.getNexthop(path) - mp = append(mp, &netlink.NexthopInfo{ - Gw: gw, - LinkIndex: link, - Flags: flags, - }) - } - if len(mp) == 0 { - return nil - } - route.MultiPath = mp - } - if p.IsWithdraw { - log.Info("del route:", route) - return netlink.RouteDel(route) - } - log.Info("add route:", route) - return netlink.RouteReplace(route) -} - -func (d *Dataplane) monitorBest() error { - - watcher, err := d.client.MonitorRIB(bgp.RF_IPv4_UC, true) - if err != nil { - return err - } - for { - dst, err := watcher.Recv() - if err != nil { - return err - } - d.modRibCh <- dst.GetAllKnownPathList() - } - return nil -} - -func (d *Dataplane) Serve() error { - for { - var s *bgpconfig.Global - client, err := client.New(d.grpcHost) - if err != nil { - log.Errorf("%s", err) - goto ERR - } - d.client = client - s, err = d.client.GetServer() - if err != nil { - log.Errorf("%s", err) - goto ERR - } - d.routerId = s.Config.RouterId - d.localAS = s.Config.As - if d.routerId != "" && d.localAS != 0 { - break - } - ERR: - log.Debug("BGP server is not ready..waiting...") - time.Sleep(time.Second * 10) - } - - lo, err := netlink.LinkByName("lo") - if err != nil { - return fmt.Errorf("failed to get lo") - } - - addrList, err := netlink.AddrList(lo, netlink.FAMILY_ALL) - if err != nil { - return fmt.Errorf("failed to get addr list of lo") - } - - addr, err := netlink.ParseAddr(d.routerId + "/32") - if err != nil { - return fmt.Errorf("failed to parse addr: %s", d.routerId) - } - - exist := false - for _, a := range addrList { - if a.Equal(*addr) { - exist = true - } - } - - if !exist { - log.Debugf("add route to lo") - err = netlink.AddrAdd(lo, addr) - if err != nil { - return fmt.Errorf("failed to add addr %s to lo", addr) - } - } - - d.advPathCh <- table.NewPath(nil, bgp.NewIPAddrPrefix(uint8(32), d.routerId), false, []bgp.PathAttributeInterface{ - bgp.NewPathAttributeNextHop("0.0.0.0"), - bgp.NewPathAttributeOrigin(bgp.BGP_ORIGIN_ATTR_TYPE_IGP), - }, time.Now(), false) - d.t.Go(d.monitorBest) - - for { - select { - case <-d.t.Dying(): - log.Error("dying! ", d.t.Err()) - return nil - case paths := <-d.modRibCh: - err = d.modRib(paths) - if err != nil { - log.Error("failed to mod rib: ", err) - } - case p := <-d.advPathCh: - _, err := d.client.AddPath([]*table.Path{p}) - if err != nil { - log.Error("failed to adv path: ", err) - } - case v := <-d.addVnCh: - vn := NewVirtualNetwork(v, d.routerId, d.grpcHost) - d.vnMap[v.RD] = vn - d.t.Go(vn.Serve) - case v := <-d.delVnCh: - vn := d.vnMap[v.RD] - vn.Stop() - delete(d.vnMap, v.RD) - } - } -} - -func (d *Dataplane) AddVirtualNetwork(c config.VirtualNetwork) error { - d.addVnCh <- c - return nil -} - -func (d *Dataplane) DeleteVirtualNetwork(c config.VirtualNetwork) error { - d.delVnCh <- c - return nil -} - -func NewDataplane(c *config.Config, grpcHost string) *Dataplane { - modRibCh := make(chan []*table.Path, 16) - advPathCh := make(chan *table.Path, 16) - addVnCh := make(chan config.VirtualNetwork) - delVnCh := make(chan config.VirtualNetwork) - return &Dataplane{ - config: c, - modRibCh: modRibCh, - advPathCh: advPathCh, - addVnCh: addVnCh, - delVnCh: delVnCh, - vnMap: make(map[string]*VirtualNetwork), - grpcHost: grpcHost, - } -} diff --git a/netlink/stringer.go b/netlink/stringer.go deleted file mode 100644 index db34d83..0000000 --- a/netlink/stringer.go +++ /dev/null @@ -1,170 +0,0 @@ -// generated by stringer -type=NUD_TYPE,RTMGRP_TYPE,RTM_TYPE,NTF_TYPE,NDA_TYPE -output=stringer.go const.go; DO NOT EDIT - -package netlink - -import "fmt" - -const ( - _NUD_TYPE_name_0 = "NUD_NONENUD_INCOMPLETENUD_REACHABLE" - _NUD_TYPE_name_1 = "NUD_STALE" - _NUD_TYPE_name_2 = "NUD_DELAY" - _NUD_TYPE_name_3 = "NUD_PROBE" - _NUD_TYPE_name_4 = "NUD_FAILED" - _NUD_TYPE_name_5 = "NUD_NOARP" - _NUD_TYPE_name_6 = "NUD_PERMANENT" -) - -var ( - _NUD_TYPE_index_0 = [...]uint8{0, 8, 22, 35} - _NUD_TYPE_index_1 = [...]uint8{0, 9} - _NUD_TYPE_index_2 = [...]uint8{0, 9} - _NUD_TYPE_index_3 = [...]uint8{0, 9} - _NUD_TYPE_index_4 = [...]uint8{0, 10} - _NUD_TYPE_index_5 = [...]uint8{0, 9} - _NUD_TYPE_index_6 = [...]uint8{0, 13} -) - -func (i NUD_TYPE) String() string { - switch { - case 0 <= i && i <= 2: - return _NUD_TYPE_name_0[_NUD_TYPE_index_0[i]:_NUD_TYPE_index_0[i+1]] - case i == 4: - return _NUD_TYPE_name_1 - case i == 8: - return _NUD_TYPE_name_2 - case i == 16: - return _NUD_TYPE_name_3 - case i == 32: - return _NUD_TYPE_name_4 - case i == 64: - return _NUD_TYPE_name_5 - case i == 128: - return _NUD_TYPE_name_6 - default: - return fmt.Sprintf("NUD_TYPE(%d)", i) - } -} - -const ( - _RTMGRP_TYPE_name_0 = "RTMGRP_LINKRTMGRP_NOTIFYRTMGRP_NEIGHRTMGRP_TCRTMGRP_IPV4_IFADDRRTMGRP_IPV4_MROUTERTMGRP_IPV4_ROUTERTMGRP_IPV4_RULERTMGRP_IPV6_IFADDRRTMGRP_IPV6_MROUTERTMGRP_IPV6_ROUTERTMGRP_IPV6_IFINFORTMGRP_DECnet_IFADDR" - _RTMGRP_TYPE_name_1 = "RTMGRP_DECnet_ROUTE" - _RTMGRP_TYPE_name_2 = "RTMGRP_IPV6_PREFIX" -) - -var ( - _RTMGRP_TYPE_index_0 = [...]uint8{0, 11, 24, 36, 45, 63, 81, 98, 114, 132, 150, 167, 185, 205} - _RTMGRP_TYPE_index_1 = [...]uint8{0, 19} - _RTMGRP_TYPE_index_2 = [...]uint8{0, 18} -) - -func (i RTMGRP_TYPE) String() string { - switch { - case 1 <= i && i <= 13: - i -= 1 - return _RTMGRP_TYPE_name_0[_RTMGRP_TYPE_index_0[i]:_RTMGRP_TYPE_index_0[i+1]] - case i == 15: - return _RTMGRP_TYPE_name_1 - case i == 17: - return _RTMGRP_TYPE_name_2 - default: - return fmt.Sprintf("RTMGRP_TYPE(%d)", i) - } -} - -const _RTM_TYPE_name = "RTM_NEWLINKRTM_DELLINKRTM_GETLINKRTM_SETLINKRTM_NEWADDRRTM_DELADDRRTM_GETADDRRTM_NEWROUTERTM_DELROUTERTM_GETROUTERTM_NEWNEIGHRTM_DELNEIGHRTM_GETNEIGHRTM_NEWRULERTM_DELRULERTM_GETRULERTM_NEWQDISCRTM_DELQDISCRTM_GETQDISCRTM_NEWTCLASSRTM_DELTCLASSRTM_GETTCLASSRTM_NEWTFILTERRTM_DELTFILTERRTM_GETTFILTERRTM_NEWACTIONRTM_DELACTIONRTM_GETACTIONRTM_NEWPREFIXRTM_GETMULTICASTRTM_GETANYCASTRTM_NEWNEIGHTBLRTM_GETNEIGHTBLRTM_SETNEIGHTBLRTM_NEWNDUSEROPTRTM_NEWADDRLABELRTM_DELADDRLABELRTM_GETADDRLABELRTM_GETDCBRTM_SETDCBRTM_NEWNETCONFRTM_GETNETCONFRTM_NEWMDBRTM_DELMDBRTM_GETMDB" - -var _RTM_TYPE_map = map[RTM_TYPE]string{ - 16: _RTM_TYPE_name[0:11], - 17: _RTM_TYPE_name[11:22], - 18: _RTM_TYPE_name[22:33], - 19: _RTM_TYPE_name[33:44], - 20: _RTM_TYPE_name[44:55], - 21: _RTM_TYPE_name[55:66], - 22: _RTM_TYPE_name[66:77], - 24: _RTM_TYPE_name[77:89], - 25: _RTM_TYPE_name[89:101], - 26: _RTM_TYPE_name[101:113], - 28: _RTM_TYPE_name[113:125], - 29: _RTM_TYPE_name[125:137], - 30: _RTM_TYPE_name[137:149], - 32: _RTM_TYPE_name[149:160], - 33: _RTM_TYPE_name[160:171], - 34: _RTM_TYPE_name[171:182], - 36: _RTM_TYPE_name[182:194], - 37: _RTM_TYPE_name[194:206], - 38: _RTM_TYPE_name[206:218], - 40: _RTM_TYPE_name[218:231], - 41: _RTM_TYPE_name[231:244], - 42: _RTM_TYPE_name[244:257], - 44: _RTM_TYPE_name[257:271], - 45: _RTM_TYPE_name[271:285], - 46: _RTM_TYPE_name[285:299], - 48: _RTM_TYPE_name[299:312], - 49: _RTM_TYPE_name[312:325], - 50: _RTM_TYPE_name[325:338], - 52: _RTM_TYPE_name[338:351], - 58: _RTM_TYPE_name[351:367], - 62: _RTM_TYPE_name[367:381], - 64: _RTM_TYPE_name[381:396], - 66: _RTM_TYPE_name[396:411], - 67: _RTM_TYPE_name[411:426], - 68: _RTM_TYPE_name[426:442], - 72: _RTM_TYPE_name[442:458], - 73: _RTM_TYPE_name[458:474], - 74: _RTM_TYPE_name[474:490], - 78: _RTM_TYPE_name[490:500], - 79: _RTM_TYPE_name[500:510], - 80: _RTM_TYPE_name[510:524], - 82: _RTM_TYPE_name[524:538], - 84: _RTM_TYPE_name[538:548], - 85: _RTM_TYPE_name[548:558], - 86: _RTM_TYPE_name[558:568], -} - -func (i RTM_TYPE) String() string { - if str, ok := _RTM_TYPE_map[i]; ok { - return str - } - return fmt.Sprintf("RTM_TYPE(%d)", i) -} - -const ( - _NTF_TYPE_name_0 = "NTF_USENTF_SELF" - _NTF_TYPE_name_1 = "NTF_MASTER" - _NTF_TYPE_name_2 = "NTF_PROXY" - _NTF_TYPE_name_3 = "NTF_ROUTER" -) - -var ( - _NTF_TYPE_index_0 = [...]uint8{0, 7, 15} - _NTF_TYPE_index_1 = [...]uint8{0, 10} - _NTF_TYPE_index_2 = [...]uint8{0, 9} - _NTF_TYPE_index_3 = [...]uint8{0, 10} -) - -func (i NTF_TYPE) String() string { - switch { - case 1 <= i && i <= 2: - i -= 1 - return _NTF_TYPE_name_0[_NTF_TYPE_index_0[i]:_NTF_TYPE_index_0[i+1]] - case i == 4: - return _NTF_TYPE_name_1 - case i == 8: - return _NTF_TYPE_name_2 - case i == 128: - return _NTF_TYPE_name_3 - default: - return fmt.Sprintf("NTF_TYPE(%d)", i) - } -} - -const _NDA_TYPE_name = "NDA_UNSPECNDA_DSTNDA_LLADDRNDA_CACHEINFONDA_PROBESNDA_VLANNDA_PORTNDA_VNINDA_IFINDEX" - -var _NDA_TYPE_index = [...]uint8{0, 10, 17, 27, 40, 50, 58, 66, 73, 84} - -func (i NDA_TYPE) String() string { - if i+1 >= NDA_TYPE(len(_NDA_TYPE_index)) { - return fmt.Sprintf("NDA_TYPE(%d)", i) - } - return _NDA_TYPE_name[_NDA_TYPE_index[i]:_NDA_TYPE_index[i+1]] -} diff --git a/netlink/virtualnetwork.go b/netlink/virtualnetwork.go deleted file mode 100644 index 564aa6e..0000000 --- a/netlink/virtualnetwork.go +++ /dev/null @@ -1,525 +0,0 @@ -// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package netlink - -import ( - "fmt" - "net" - "syscall" - "time" - - log "github.com/Sirupsen/logrus" - "github.com/osrg/gobgp/client" - "github.com/osrg/gobgp/packet/bgp" - "github.com/osrg/gobgp/table" - "github.com/osrg/goplane/config" - "github.com/vishvananda/netlink" - "github.com/vishvananda/netlink/nl" - "gopkg.in/tomb.v2" -) - -type netlinkEvent struct { - mac net.HardwareAddr - ip net.IP - isWithdraw bool -} - -type VirtualNetwork struct { - t tomb.Tomb - connMap map[string]net.Conn - config config.VirtualNetwork - multicastCh chan *table.Path - macadvCh chan *table.Path - floodCh chan []byte - netlinkCh chan *netlinkEvent - grpcHost string - client *client.Client - routerId string -} - -func (n *VirtualNetwork) Stop() { - n.t.Kill(fmt.Errorf("admin stop")) -} - -func (n *VirtualNetwork) modVrf(withdraw bool) error { - rd, err := bgp.ParseRouteDistinguisher(n.config.RD) - if err != nil { - return err - } - rt, err := bgp.ParseRouteTarget(n.config.RD) - if err != nil { - return err - } - if withdraw { - return n.client.DeleteVRF(n.config.RD) - } - return n.client.AddVRF(n.config.RD, 0, rd, []bgp.ExtendedCommunityInterface{rt}, []bgp.ExtendedCommunityInterface{rt}) -} - -func (n *VirtualNetwork) Serve() error { - client, err := client.New(n.grpcHost) - if err != nil { - log.Fatalf("%s", err) - } - n.client = client - - log.Debugf("vtep intf: %s", n.config.VtepInterface) - link, err := netlink.LinkByName(n.config.VtepInterface) - master := 0 - if err == nil { - log.Debug("link type:", link.Type()) - vtep := link.(*netlink.Vxlan) - err = netlink.LinkSetDown(vtep) - log.Debugf("set %s down", n.config.VtepInterface) - if err != nil { - return fmt.Errorf("failed to set link %s down", n.config.VtepInterface) - } - master = vtep.MasterIndex - log.Debugf("del %s", n.config.VtepInterface) - err = netlink.LinkDel(link) - if err != nil { - return fmt.Errorf("failed to del %s", n.config.VtepInterface) - } - } - - if master > 0 { - b, _ := netlink.LinkByIndex(master) - br := b.(*netlink.Bridge) - err = netlink.LinkSetDown(br) - log.Debugf("set %s down", br.LinkAttrs.Name) - if err != nil { - return fmt.Errorf("failed to set %s down", br.LinkAttrs.Name) - } - log.Debugf("del %s", br.LinkAttrs.Name) - err = netlink.LinkDel(br) - if err != nil { - return fmt.Errorf("failed to del %s", br.LinkAttrs.Name) - } - } - - brName := fmt.Sprintf("br%d", n.config.VNI) - - b, err := netlink.LinkByName(brName) - if err == nil { - br := b.(*netlink.Bridge) - err = netlink.LinkSetDown(br) - log.Debugf("set %s down", br.LinkAttrs.Name) - if err != nil { - return fmt.Errorf("failed to set %s down", br.LinkAttrs.Name) - } - log.Debugf("del %s", br.LinkAttrs.Name) - err = netlink.LinkDel(br) - if err != nil { - return fmt.Errorf("failed to del %s", br.LinkAttrs.Name) - } - } - - br := &netlink.Bridge{ - LinkAttrs: netlink.LinkAttrs{ - Name: brName, - }, - } - - log.Debugf("add %s", brName) - err = netlink.LinkAdd(br) - if err != nil { - return fmt.Errorf("failed to add link %s. %s", brName, err) - } - err = netlink.LinkSetUp(br) - if err != nil { - return fmt.Errorf("failed to set %s up", brName) - } - - link = &netlink.Vxlan{ - LinkAttrs: netlink.LinkAttrs{ - Name: n.config.VtepInterface, - }, - VxlanId: int(n.config.VNI), - SrcAddr: net.ParseIP(n.routerId), - } - - log.Debugf("add %s", n.config.VtepInterface) - err = netlink.LinkAdd(link) - if err != nil { - return fmt.Errorf("failed to add link %s. %s", n.config.VtepInterface, err) - } - err = netlink.LinkSetUp(link) - if err != nil { - return fmt.Errorf("failed to set %s up", n.config.VtepInterface) - } - - err = netlink.LinkSetMaster(link, br) - if err != nil { - return fmt.Errorf("failed to set master %s dev %s", brName, n.config.VtepInterface) - } - - for _, member := range n.config.MemberInterfaces { - m, err := netlink.LinkByName(member) - if err != nil { - log.Errorf("can't find %s", member) - continue - } - err = netlink.LinkSetUp(m) - if err != nil { - return fmt.Errorf("failed to set %s up", member) - } - err = netlink.LinkSetMaster(m, br) - if err != nil { - return fmt.Errorf("failed to set master %s dev %s", brName, member) - } - } - - withdraw := false - err = n.modVrf(withdraw) - if err != nil { - log.Fatal(err) - } - - err = n.sendMulticast(withdraw) - if err != nil { - log.Fatal(err) - } - - n.t.Go(n.monitorBest) - n.t.Go(n.monitorNetlink) - - for _, member := range n.config.SniffInterfaces { - n.t.Go(func() error { - return n.sniffPkt(member) - }) - } - - for { - select { - case <-n.t.Dying(): - log.Errorf("stop virtualnetwork %s", n.config.RD) - for h, conn := range n.connMap { - log.Debugf("close udp connection to %s", h) - conn.Close() - } - withdraw = true - n.modVrf(withdraw) - return nil - case p := <-n.multicastCh: - e := p.GetNlri().(*bgp.EVPNNLRI).RouteTypeData.(*bgp.EVPNMulticastEthernetTagRoute) - if e.ETag != n.config.Etag || p.GetNexthop().String() == "0.0.0.0" { - continue - } - err = n.modConnMap(p) - if err != nil { - log.Errorf("mod conn failed. kill main loop. err: %s", err) - return err - } - case p := <-n.macadvCh: - e := p.GetNlri().(*bgp.EVPNNLRI).RouteTypeData.(*bgp.EVPNMacIPAdvertisementRoute) - if e.ETag != n.config.Etag || p.GetNexthop().String() == "0.0.0.0" { - continue - } - err = n.modFdb(p) - if err != nil { - log.Errorf("mod fdb failed. kill main loop. err: %s", err) - return err - } - case p := <-n.floodCh: - err = n.flood(p) - if err != nil { - log.Errorf("flood failed. kill main loop. err: %s", err) - return err - } - case e := <-n.netlinkCh: - err = n.modPath(e) - if err != nil { - log.Errorf("modpath failed. kill main loop. err: %s", err) - return err - } - } - } -} - -func (f *VirtualNetwork) modConnMap(path *table.Path) error { - addr := path.GetNexthop().String() - e := path.GetNlri().(*bgp.EVPNNLRI).RouteTypeData.(*bgp.EVPNMulticastEthernetTagRoute) - etag := e.ETag - log.Debugf("mod cannection map: nh %s, vtep addr %s etag %d withdraw %t", addr, path.GetNlri(), etag, path.IsWithdraw) - if path.IsWithdraw { - _, ok := f.connMap[addr] - if !ok { - return fmt.Errorf("can't find %s conn", addr) - } - - f.connMap[addr].Close() - delete(f.connMap, addr) - } else { - _, ok := f.connMap[addr] - if ok { - log.Debugf("refresh. close connection to %s", addr) - f.connMap[addr].Close() - delete(f.connMap, addr) - } - udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, f.config.VxlanPort)) - if err != nil { - log.Fatal(err) - } - - log.Debugf("connect to %s", addr) - conn, err := net.DialUDP("udp", nil, udpAddr) - if err != nil { - log.Warnf("failed to dial UDP(%s) %s", addr, err) - return nil - } - f.connMap[addr] = conn - } - log.WithFields(log.Fields{ - "Topic": "virtualnetwork", - "Etag": f.config.Etag, - }).Debugf("connMap: %s", f.connMap) - return nil -} - -func (f *VirtualNetwork) modFdb(path *table.Path) error { - log.WithFields(log.Fields{ - "Topic": "VirtualNetwork", - "Etag": f.config.Etag, - }).Debugf("modFdb new path, prefix: %s, nexthop: %s, withdraw: %t", path.GetNlri(), path.GetNexthop(), path.IsWithdraw) - - e := path.GetNlri().(*bgp.EVPNNLRI).RouteTypeData.(*bgp.EVPNMacIPAdvertisementRoute) - mac := e.MacAddress - ip := path.GetNexthop() - - link, err := netlink.LinkByName(f.config.VtepInterface) - if err != nil { - log.WithFields(log.Fields{ - "Topic": "VirtualNetwork", - "Etag": f.config.Etag, - }).Debugf("failed lookup link by name: %s", f.config.VtepInterface) - return nil - } - - n := &netlink.Neigh{ - LinkIndex: link.Attrs().Index, - Family: int(netlink.NDA_VNI), - State: int(netlink.NUD_NOARP | netlink.NUD_PERMANENT), - Type: syscall.RTM_NEWNEIGH, - Flags: int(netlink.NTF_SELF), - IP: ip, - HardwareAddr: mac, - } - - if path.IsWithdraw { - err = netlink.NeighDel(n) - if err != nil { - log.WithFields(log.Fields{ - "Topic": "VirtualNetwork", - "Etag": f.config.Etag, - }).Errorf("failed to del fdb: %s, %s", n, err) - } - } else { - err = netlink.NeighAppend(n) - if err != nil { - log.WithFields(log.Fields{ - "Topic": "VirtualNetwork", - "Etag": f.config.Etag, - }).Debugf("failed to add fdb: %s, %s", n, err) - } - } - return err -} - -func (f *VirtualNetwork) flood(pkt []byte) error { - vxlanHeader := NewVXLAN(f.config.VNI) - b := vxlanHeader.Serialize() - b = append(b, pkt...) - - for _, c := range f.connMap { - cnt, err := c.Write(b) - log.WithFields(log.Fields{ - "Topic": "VirtualNetwork", - "Etag": f.config.Etag, - }).Debugf("send to %s: cnt:%d, err:%s", c.RemoteAddr(), cnt, err) - if err != nil { - return err - } - } - - return nil -} - -func (n *VirtualNetwork) sendMulticast(withdraw bool) error { - - pattrs := []bgp.PathAttributeInterface{} - - pattrs = append(pattrs, bgp.NewPathAttributeOrigin(bgp.BGP_ORIGIN_ATTR_TYPE_IGP)) - - var rd bgp.RouteDistinguisherInterface - multicastEtag := &bgp.EVPNMulticastEthernetTagRoute{ - RD: rd, - IPAddressLength: uint8(32), - IPAddress: net.ParseIP(n.routerId), - ETag: uint32(n.config.Etag), - } - nlri := bgp.NewEVPNNLRI(bgp.EVPN_INCLUSIVE_MULTICAST_ETHERNET_TAG, 0, multicastEtag) - nexthop := "0.0.0.0" - pattrs = append(pattrs, bgp.NewPathAttributeMpReachNLRI(nexthop, []bgp.AddrPrefixInterface{nlri})) - - id := &bgp.IngressReplTunnelID{ - Value: net.ParseIP(n.routerId), - } - pattrs = append(pattrs, bgp.NewPathAttributePmsiTunnel(bgp.PMSI_TUNNEL_TYPE_INGRESS_REPL, false, 0, id)) - - path := table.NewPath(nil, nlri, withdraw, pattrs, time.Now(), false) - _, err := n.client.AddVRFPath(n.config.RD, []*table.Path{path}) - return err -} - -func (f *VirtualNetwork) modPath(n *netlinkEvent) error { - pattrs := []bgp.PathAttributeInterface{} - - macIpAdv := &bgp.EVPNMacIPAdvertisementRoute{ - ESI: bgp.EthernetSegmentIdentifier{ - Type: bgp.ESI_ARBITRARY, - }, - MacAddressLength: 48, - MacAddress: n.mac, - IPAddressLength: 0, - Labels: []uint32{uint32(f.config.VNI)}, - ETag: uint32(f.config.Etag), - } - nlri := bgp.NewEVPNNLRI(bgp.EVPN_ROUTE_TYPE_MAC_IP_ADVERTISEMENT, 0, macIpAdv) - nexthop := "0.0.0.0" - pattrs = append(pattrs, bgp.NewPathAttributeMpReachNLRI(nexthop, []bgp.AddrPrefixInterface{nlri})) - - pattrs = append(pattrs, bgp.NewPathAttributeOrigin(bgp.BGP_ORIGIN_ATTR_TYPE_IGP)) - - isTransitive := true - o := bgp.NewOpaqueExtended(isTransitive) - o.SubType = bgp.EC_SUBTYPE_ENCAPSULATION - o.Value = &bgp.EncapExtended{bgp.TUNNEL_TYPE_VXLAN} - pattrs = append(pattrs, bgp.NewPathAttributeExtendedCommunities([]bgp.ExtendedCommunityInterface{o})) - path := table.NewPath(nil, nlri, n.isWithdraw, pattrs, time.Now(), false) - - _, err := f.client.AddPath([]*table.Path{path}) - return err -} - -func (n *VirtualNetwork) monitorBest() error { - watcher, err := n.client.MonitorRIB(bgp.RF_EVPN, true) - if err != nil { - return err - } - for { - dst, err := watcher.Recv() - if err != nil { - return err - } - path := dst.GetAllKnownPathList()[0] - nlri := path.GetNlri() - - switch nlri.(*bgp.EVPNNLRI).RouteType { - case bgp.EVPN_ROUTE_TYPE_MAC_IP_ADVERTISEMENT: - n.macadvCh <- path - case bgp.EVPN_INCLUSIVE_MULTICAST_ETHERNET_TAG: - n.multicastCh <- path - } - } -} - -func (f *VirtualNetwork) sniffPkt(ifname string) error { - conn, err := NewPFConn(ifname) - if err != nil { - return err - } - buf := make([]byte, 2048) - for { - n, err := conn.Read(buf) - if err != nil { - log.Errorf("failed to recv from %s, err: %s", conn, err) - return err - } - log.WithFields(log.Fields{ - "Topic": "VirtualNetwork", - "Etag": f.config.Etag, - }).Debugf("recv from %s, len: %d", conn, n) - f.floodCh <- buf[:n] - } -} - -func (f *VirtualNetwork) monitorNetlink() error { - s, err := nl.Subscribe(syscall.NETLINK_ROUTE, uint(RTMGRP_NEIGH), uint(RTMGRP_LINK), uint(RTMGRP_NOTIFY)) - if err != nil { - return err - } - - idxs := make([]int, 0, len(f.config.SniffInterfaces)) - for _, member := range f.config.SniffInterfaces { - link, err := netlink.LinkByName(member) - if err != nil { - log.Errorf("failed to get link %s", member) - return err - } - log.WithFields(log.Fields{ - "Topic": "VirtualNetwork", - "Etag": f.config.Etag, - }).Debugf("monitoring: %s, index: %d", link.Attrs().Name, link.Attrs().Index) - idxs = append(idxs, link.Attrs().Index) - } - - for { - msgs, err := s.Receive() - if err != nil { - return err - } - - for _, msg := range msgs { - t := RTM_TYPE(msg.Header.Type) - switch t { - case RTM_NEWNEIGH, RTM_DELNEIGH: - n, _ := netlink.NeighDeserialize(msg.Data) - for _, idx := range idxs { - if n.LinkIndex == idx { - log.WithFields(log.Fields{ - "Topic": "VirtualNetwork", - "Etag": f.config.Etag, - }).Debugf("mac: %s, ip: %s, index: %d, family: %s, state: %s, type: %s, flags: %s", n.HardwareAddr, n.IP, n.LinkIndex, NDA_TYPE(n.Family), NUD_TYPE(n.State), RTM_TYPE(n.Type), NTF_TYPE(n.Flags)) - var withdraw bool - if t == RTM_DELNEIGH { - withdraw = true - } - f.netlinkCh <- &netlinkEvent{n.HardwareAddr, n.IP, withdraw} - break - } - } - } - } - } -} - -func NewVirtualNetwork(config config.VirtualNetwork, routerId, grpcHost string) *VirtualNetwork { - macadvCh := make(chan *table.Path, 16) - multicastCh := make(chan *table.Path, 16) - floodCh := make(chan []byte, 16) - netlinkCh := make(chan *netlinkEvent, 16) - - return &VirtualNetwork{ - config: config, - connMap: map[string]net.Conn{}, - macadvCh: macadvCh, - multicastCh: multicastCh, - floodCh: floodCh, - netlinkCh: netlinkCh, - routerId: routerId, - grpcHost: grpcHost, - } -} diff --git a/protocol/gobgp.go b/protocol/gobgp.go new file mode 100644 index 0000000..be075fa --- /dev/null +++ b/protocol/gobgp.go @@ -0,0 +1,419 @@ +// Copyright (C) 2017 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package protocol + +import ( + "fmt" + "net" + "time" + + log "github.com/Sirupsen/logrus" + bgpapi "github.com/osrg/gobgp/api" + bgpconfig "github.com/osrg/gobgp/config" + "github.com/osrg/gobgp/packet/bgp" + bgpserver "github.com/osrg/gobgp/server" + bgptable "github.com/osrg/gobgp/table" + "github.com/osrg/goplane/config" +) + +type GoBGPEntry struct { + nlri bgp.AddrPrefixInterface + path []*bgptable.Path + vrf *config.VirtualNetwork +} + +func (e *GoBGPEntry) NLRI() bgp.AddrPrefixInterface { + return e.nlri +} + +func (e *GoBGPEntry) Match() Match { + f := bgp.AfiSafiToRouteFamily(e.nlri.AFI(), e.nlri.SAFI()) + switch f { + case bgp.RF_IPv4_UC, bgp.RF_IPv6_UC: + _, n, _ := net.ParseCIDR(e.nlri.String()) + return &LPMatch{ + Prefix: n, + } + case bgp.RF_EVPN: + switch t := e.nlri.(*bgp.EVPNNLRI).RouteTypeData.(type) { + case *bgp.EVPNMacIPAdvertisementRoute: + return &L2VPNMatch{ + VRF: *e.vrf, + MAC: t.MacAddress, + IP: t.IPAddress, + } + case *bgp.EVPNMulticastEthernetTagRoute: + return &L2VPNMcastMatch{ + VRF: *e.vrf, + } + } + case bgp.RF_FS_IPv4_UC: + m := make(map[bgp.BGPFlowSpecType]bgp.FlowSpecComponentInterface) + for _, v := range e.nlri.(*bgp.FlowSpecIPv4Unicast).Value { + m[v.Type()] = v + } + match := &ACLMatch{} + if v, ok := m[bgp.FLOW_SPEC_TYPE_DST_PREFIX]; ok { + _, dst, _ := net.ParseCIDR(v.(*bgp.FlowSpecDestinationPrefix).Prefix.String()) + match.DstIPPrefix = dst + } + if v, ok := m[bgp.FLOW_SPEC_TYPE_SRC_PREFIX]; ok { + _, src, _ := net.ParseCIDR(v.(*bgp.FlowSpecDestinationPrefix).Prefix.String()) + match.SrcIPPrefix = src + } + if v, ok := m[bgp.FLOW_SPEC_TYPE_IP_PROTO]; ok { + if len(v.(*bgp.FlowSpecComponent).Items) != 1 { + log.Errorf("ip proto len must be 1") + } + match.IPProto = v.(*bgp.FlowSpecComponent).Items[0].Value + } + return match + } + return nil +} + +func (e *GoBGPEntry) Action() Action { + f := bgp.AfiSafiToRouteFamily(e.nlri.AFI(), e.nlri.SAFI()) + switch f { + case bgp.RF_FS_IPv4_UC: + return &DropAction{} + default: + info := make([]*NexthopInfo, 0, len(e.path)) + for _, p := range e.path { + info = append(info, &NexthopInfo{ + Nexthop: p.GetNexthop(), + }) + } + return &ViaAction{ + Nexthops: info, + } + } +} + +type GoBGPProtocol struct { + server *bgpserver.BgpServer + pathCh chan *bgptable.Path + config *bgpconfig.BgpConfigSet +} + +func NewGoBGPProtocol() *GoBGPProtocol { + server := bgpserver.NewBgpServer() + go server.Serve() + proto := &GoBGPProtocol{ + server: server, + pathCh: make(chan *bgptable.Path), + } + grpcServer := bgpapi.NewGrpcServer(server, ":50051") + go func() { + if err := grpcServer.Serve(); err != nil { + log.Fatalf("failed to listen grpc port: %s", err) + } + }() + go proto.serve() + return proto +} + +func (p *GoBGPProtocol) Type() ProtocolType { + return PROTO_GOBGP +} + +func (p *GoBGPProtocol) SetRouterID(id net.IP) error { + return nil +} + +func (p *GoBGPProtocol) UpdateConfig(newConfig *bgpconfig.BgpConfigSet) error { + var added, deleted, updated []bgpconfig.Neighbor + var updatePolicy bool + + bgpServer := p.server + + if p.config == nil { + p.config = newConfig + if err := bgpServer.Start(&newConfig.Global); err != nil { + return fmt.Errorf("failed to set global config: %s", err) + } + if newConfig.Zebra.Config.Enabled { + if err := bgpServer.StartZebraClient(&newConfig.Zebra.Config); err != nil { + return fmt.Errorf("failed to set zebra config: %s", err) + } + } + if len(newConfig.Collector.Config.Url) > 0 { + if err := bgpServer.StartCollector(&newConfig.Collector.Config); err != nil { + return fmt.Errorf("failed to set collector config: %s", err) + } + } + for _, c := range newConfig.RpkiServers { + if err := bgpServer.AddRpki(&c.Config); err != nil { + return fmt.Errorf("failed to set rpki config: %s", err) + } + } + for _, c := range newConfig.BmpServers { + if err := bgpServer.AddBmp(&c.Config); err != nil { + return fmt.Errorf("failed to set bmp config: %s", err) + } + } + for _, c := range newConfig.MrtDump { + if len(c.Config.FileName) == 0 { + continue + } + if err := bgpServer.EnableMrt(&c.Config); err != nil { + return fmt.Errorf("failed to set mrt config: %s", err) + } + } + p := bgpconfig.ConfigSetToRoutingPolicy(newConfig) + if err := bgpServer.UpdatePolicy(*p); err != nil { + return fmt.Errorf("failed to set routing policy: %s", err) + } + + added = newConfig.Neighbors + // if opts.GracefulRestart { + // for i, n := range added { + // if n.GracefulRestart.Config.Enabled { + // added[i].GracefulRestart.State.LocalRestarting = true + // } + // } + // } + + } else { + added, deleted, updated, updatePolicy = bgpconfig.UpdateConfig(p.config, newConfig) + if updatePolicy { + log.Info("Policy config is updated") + policy := bgpconfig.ConfigSetToRoutingPolicy(newConfig) + bgpServer.UpdatePolicy(*policy) + } + p.config = newConfig + } + + for i, p := range added { + log.Infof("Peer %v is added", p.Config.NeighborAddress) + bgpServer.AddNeighbor(&added[i]) + } + for i, p := range deleted { + log.Infof("Peer %v is deleted", p.Config.NeighborAddress) + bgpServer.DeleteNeighbor(&deleted[i]) + } + for i, p := range updated { + log.Infof("Peer %v is updated", p.Config.NeighborAddress) + u, _ := bgpServer.UpdateNeighbor(&updated[i]) + updatePolicy = updatePolicy || u + } + + if updatePolicy { + bgpServer.SoftResetIn("", bgp.RouteFamily(0)) + } + + return nil +} + +func (p *GoBGPProtocol) serve() error { + for { + select { + case path := <-p.pathCh: + log.Info(path) + if _, err := p.server.AddPath("", []*bgptable.Path{path}); err != nil { + log.Fatal(err) + return err + } + } + } +} + +func (p *GoBGPProtocol) makePath(e Entry) (*bgptable.Path, error) { + var nlri bgp.AddrPrefixInterface + if e.Action().Type() != ACTION_VIA { + return nil, fmt.Errorf("unsupported action type: %d", e.Action().Type()) + } + via := e.Action().(*ViaAction).Nexthops[0].Nexthop + if via == nil { + via = net.ParseIP("0.0.0.0") + } + pattrs := []bgp.PathAttributeInterface{bgp.NewPathAttributeOrigin(bgp.BGP_ORIGIN_ATTR_TYPE_IGP)} + mp := true + + routerID := net.ParseIP(p.server.GetServer().Config.RouterId) + + switch e.Match().Type() { + case MATCH_LP: + m := e.Match().(*LPMatch) + n := m.Prefix + ones, _ := n.Mask.Size() + if n.IP.To4() == nil { + nlri = bgp.NewIPv6AddrPrefix(uint8(ones), n.IP.String()) + } else { + mp = false + nlri = bgp.NewIPAddrPrefix(uint8(ones), n.IP.String()) + pattrs = append(pattrs, bgp.NewPathAttributeNextHop(via.String())) + } + case MATCH_L2VPN: + m := e.Match().(*L2VPNMatch) + rd, _ := bgp.ParseRouteDistinguisher(m.VRF.RD) + macIpAdv := &bgp.EVPNMacIPAdvertisementRoute{ + RD: rd, + ESI: bgp.EthernetSegmentIdentifier{ + Type: bgp.ESI_ARBITRARY, + }, + MacAddressLength: 48, + MacAddress: m.MAC, + IPAddressLength: 0, + Labels: []uint32{uint32(m.VRF.VNI)}, + ETag: uint32(m.VRF.Etag), + } + nlri = bgp.NewEVPNNLRI(bgp.EVPN_ROUTE_TYPE_MAC_IP_ADVERTISEMENT, 0, macIpAdv) + + isTransitive := true + o := bgp.NewOpaqueExtended(isTransitive) + o.SubType = bgp.EC_SUBTYPE_ENCAPSULATION + o.Value = &bgp.EncapExtended{bgp.TUNNEL_TYPE_VXLAN} + pattrs = append(pattrs, bgp.NewPathAttributeExtendedCommunities([]bgp.ExtendedCommunityInterface{o})) + case MATCH_L2VPN_MCAST: + m := e.Match().(*L2VPNMcastMatch) + rd, _ := bgp.ParseRouteDistinguisher(m.VRF.RD) + multicastEtag := &bgp.EVPNMulticastEthernetTagRoute{ + RD: rd, + IPAddressLength: uint8(32), + IPAddress: routerID, + ETag: uint32(m.VRF.Etag), + } + nlri = bgp.NewEVPNNLRI(bgp.EVPN_INCLUSIVE_MULTICAST_ETHERNET_TAG, 0, multicastEtag) + id := &bgp.IngressReplTunnelID{ + Value: routerID, + } + pattrs = append(pattrs, bgp.NewPathAttributePmsiTunnel(bgp.PMSI_TUNNEL_TYPE_INGRESS_REPL, false, 0, id)) + + } + if mp { + pattrs = append(pattrs, bgp.NewPathAttributeMpReachNLRI(via.String(), []bgp.AddrPrefixInterface{nlri})) + } + return bgptable.NewPath(nil, nlri, false, pattrs, time.Now(), false), nil +} + +func (p *GoBGPProtocol) AddEntry(e Entry) error { + path, err := p.makePath(e) + if err != nil { + return err + } + p.pathCh <- path + return nil +} + +func (p *GoBGPProtocol) DeleteEntry(e Entry) error { + path, err := p.makePath(e) + if err != nil { + return err + } + path.IsWithdraw = true + p.pathCh <- path + return nil +} + +type GoBGPEntryWatcher struct { + watcher *bgpserver.Watcher +} + +func makeEntryEvents(paths [][]*bgptable.Path) []EntryEvent { + events := make([]EntryEvent, 0, len(paths)) + for _, ps := range paths { + list := make([]*bgptable.Path, 0, len(ps)) + isDel := false + for _, p := range ps { + if p.IsLocal() { + continue + } + if p.IsWithdraw { + isDel = true + } + list = append(list, p) + } + if len(list) == 0 { + continue + } + var vrf *config.VirtualNetwork + nlri := list[0].GetNlri() + family := bgp.AfiSafiToRouteFamily(nlri.AFI(), nlri.SAFI()) + if family == bgp.RF_EVPN { + vrf = &config.VirtualNetwork{ + RD: nlri.(*bgp.EVPNNLRI).RD().String(), + } + } + events = append(events, EntryEvent{ + Entry: &GoBGPEntry{ + nlri: nlri, + path: list, + vrf: vrf, + }, + IsDel: isDel, + From: PROTO_GOBGP, + }) + } + return events +} + +func (w *GoBGPEntryWatcher) Recv() ([]EntryEvent, error) { + for { + e := (<-w.watcher.Event()).(*bgpserver.WatchEventBestPath) + var pathList [][]*bgptable.Path + if len(e.MultiPathList) > 0 { + pathList = e.MultiPathList + } else { + pathList = make([][]*bgptable.Path, 0, len(e.PathList)) + for _, p := range e.PathList { + pathList = append(pathList, []*bgptable.Path{p}) + } + } + events := makeEntryEvents(pathList) + if len(events) > 0 { + return events, nil + } + } + return nil, nil +} + +func (w *GoBGPEntryWatcher) Close() error { + w.watcher.Stop() + return nil +} + +func (p *GoBGPProtocol) WatchEntry() (EntryWatcher, error) { + watcher := p.server.Watch(bgpserver.WatchBestPath(true)) + return &GoBGPEntryWatcher{ + watcher: watcher, + }, nil +} + +func (p *GoBGPProtocol) AddVirtualNetwork(routerID string, c config.VirtualNetwork) error { + rd, err := bgp.ParseRouteDistinguisher(c.RD) + if err != nil { + return err + } + rt, err := bgp.ParseRouteTarget(c.RD) + if err != nil { + return err + } + err = p.server.AddVrf(c.RD, 0, rd, []bgp.ExtendedCommunityInterface{rt}, []bgp.ExtendedCommunityInterface{rt}) + if err != nil { + return err + } + return p.AddEntry(&BaseEntry{ + match: &L2VPNMcastMatch{ + VRF: c, + }, + action: &ViaAction{}, + }) +} + +func (p *GoBGPProtocol) DeleteVirtualNetwork(c config.VirtualNetwork) error { + return p.server.DeleteVrf(c.RD) +} diff --git a/iptables/flowspec.go b/protocol/iptables/iptables.go similarity index 61% rename from iptables/flowspec.go rename to protocol/iptables/iptables.go index 4d1bbaa..5596f13 100644 --- a/iptables/flowspec.go +++ b/protocol/iptables/iptables.go @@ -1,4 +1,4 @@ -// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +// Copyright (C) 2017 Nippon Telegraph and Telephone Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,17 +17,21 @@ package iptables import ( "fmt" - "io" + "net" log "github.com/Sirupsen/logrus" "github.com/coreos/go-iptables/iptables" - "github.com/osrg/gobgp/client" "github.com/osrg/gobgp/packet/bgp" - bgptable "github.com/osrg/gobgp/table" "github.com/osrg/goplane/config" + proto "github.com/osrg/goplane/protocol" ) -func FlowSpec2IptablesRule(nlri []bgp.FlowSpecComponentInterface, attr []bgp.PathAttributeInterface) ([]string, error) { +type ev struct { + nlri bgp.AddrPrefixInterface + isDel bool +} + +func FlowSpec2IptablesRule(nlri []bgp.FlowSpecComponentInterface) ([]string, error) { spec := make([]string, 0, len(nlri)) m := make(map[bgp.BGPFlowSpecType]bgp.FlowSpecComponentInterface) for _, v := range nlri { @@ -61,12 +65,24 @@ func FlowSpec2IptablesRule(nlri []bgp.FlowSpecComponentInterface, attr []bgp.Pat return spec, nil } -type FlowspecAgent struct { - config config.Iptables - grpcHost string +type IPTablesProtocol struct { + config config.Iptables + ch chan *ev + list []*bgp.FlowSpecNLRI +} + +func NewIPTablesProtocol(c config.Iptables) *IPTablesProtocol { + p := &IPTablesProtocol{ + config: c, + ch: make(chan *ev), + list: make([]*bgp.FlowSpecNLRI, 0), + } + go p.serve() + return p } -func (a *FlowspecAgent) Serve() error { +func (p *IPTablesProtocol) serve() error { + ipt, err := iptables.New() if err != nil { log.Fatalf("%s", err) @@ -74,8 +90,8 @@ func (a *FlowspecAgent) Serve() error { table := "filter" chain := "FLOWSPEC" - if a.config.Chain != "" { - chain = a.config.Chain + if p.config.Chain != "" { + chain = p.config.Chain } if err := ipt.ClearChain(table, chain); err != nil { @@ -83,38 +99,15 @@ func (a *FlowspecAgent) Serve() error { } log.Infof("cleared iptables chain: %s, table: %s", chain, table) - ch := make(chan *bgptable.Path, 16) - - go func() { - client, err := client.New(a.grpcHost) - if err != nil { - log.Fatalf("%s", err) - } - - watcher, err := client.MonitorRIB(bgp.RF_FS_IPv4_UC, true) - if err != nil { - log.Fatalf("%s", err) - } - - for { - d, err := watcher.Recv() - if err == io.EOF { - break - } else if err != nil { - log.Fatalf("%s", err) - } - for _, p := range d.GetAllKnownPathList() { - ch <- p - } + for ev := range p.ch { + f := bgp.AfiSafiToRouteFamily(ev.nlri.AFI(), ev.nlri.SAFI()) + if f != bgp.RF_FS_IPv4_UC { + continue } - }() - - list := make([]*bgp.FlowSpecNLRI, 0, 16) - for p := range ch { - nlri := &p.GetNlri().(*bgp.FlowSpecIPv4Unicast).FlowSpecNLRI + nlri := &ev.nlri.(*bgp.FlowSpecIPv4Unicast).FlowSpecNLRI - spec, err := FlowSpec2IptablesRule(nlri.Value, p.GetPathAttrs()) + spec, err := FlowSpec2IptablesRule(nlri.Value) if err != nil { log.Warnf("failed to convert flowspec spec to iptables rule: %s", err) continue @@ -122,9 +115,9 @@ func (a *FlowspecAgent) Serve() error { idx := 0 var q *bgp.FlowSpecNLRI - if p.IsWithdraw { + if ev.isDel { found := false - for idx, q = range list { + for idx, q = range p.list { result, err := bgp.CompareFlowSpecNLRI(nlri, q) if err != nil { log.Fatalf("%s", err) @@ -137,7 +130,7 @@ func (a *FlowspecAgent) Serve() error { if !found { log.Warnf("not found: %s", nlri) } - list = append(list[:idx], list[idx+1:]...) + p.list = append(p.list[:idx], p.list[idx+1:]...) if err := ipt.Delete(table, chain, spec...); err != nil { log.Errorf("failed to delete: %s", err) } else { @@ -145,14 +138,14 @@ func (a *FlowspecAgent) Serve() error { } } else { found := false - for idx, q = range list { + for idx, q = range p.list { result, err := bgp.CompareFlowSpecNLRI(nlri, q) if err != nil { log.Fatalf("%s", err) } if result > 0 { found = true - list = append(list[:idx], append([]*bgp.FlowSpecNLRI{nlri}, list[idx:]...)...) + p.list = append(p.list[:idx], append([]*bgp.FlowSpecNLRI{nlri}, p.list[idx:]...)...) idx += 1 break } else if result == 0 { @@ -162,8 +155,8 @@ func (a *FlowspecAgent) Serve() error { } if !found { - list = append(list, nlri) - idx = len(list) + p.list = append(p.list, nlri) + idx = len(p.list) } if y, _ := ipt.Exists(table, chain, spec...); y { @@ -178,9 +171,43 @@ func (a *FlowspecAgent) Serve() error { return nil } -func NewFlowspecAgent(grpcHost string, c config.Iptables) *FlowspecAgent { - return &FlowspecAgent{ - config: c, - grpcHost: grpcHost, +func (p *IPTablesProtocol) AddEntry(e proto.Entry) error { + if e.Match().Type() != proto.MATCH_ACL { + return nil } + p.ch <- &ev{ + nlri: e.(*proto.GoBGPEntry).NLRI(), + } + return nil +} + +func (p *IPTablesProtocol) DeleteEntry(e proto.Entry) error { + if e.Match().Type() != proto.MATCH_ACL { + return nil + } + p.ch <- &ev{ + nlri: e.(*proto.GoBGPEntry).NLRI(), + isDel: true, + } + return nil +} + +func (p *IPTablesProtocol) WatchEntry() (proto.EntryWatcher, error) { + return nil, nil +} + +func (p *IPTablesProtocol) SetRouterID(net.IP) error { + return nil +} + +func (p *IPTablesProtocol) AddVirtualNetwork(string, config.VirtualNetwork) error { + return nil +} + +func (p *IPTablesProtocol) DeleteVirtualNetwork(config.VirtualNetwork) error { + return nil +} + +func (p *IPTablesProtocol) Type() proto.ProtocolType { + return proto.PROTO_IPTABLES } diff --git a/netlink/const.go b/protocol/netlink/const.go similarity index 99% rename from netlink/const.go rename to protocol/netlink/const.go index 649e8fe..48181ce 100644 --- a/netlink/const.go +++ b/protocol/netlink/const.go @@ -169,3 +169,7 @@ const ( NTF_PROXY NTF_TYPE = 0x08 NTF_ROUTER NTF_TYPE = 0x80 ) + +const ( + RTPROT_GOPLANE = 0x11 +) diff --git a/protocol/netlink/netlink.go b/protocol/netlink/netlink.go new file mode 100644 index 0000000..a74cd21 --- /dev/null +++ b/protocol/netlink/netlink.go @@ -0,0 +1,419 @@ +// Copyright (C) 2017 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package netlink + +import ( + "fmt" + "net" + "sync" + "syscall" + + log "github.com/Sirupsen/logrus" + "github.com/vishvananda/netlink" + "github.com/vishvananda/netlink/nl" + + "github.com/osrg/goplane/config" + proto "github.com/osrg/goplane/protocol" +) + +type NetlinkRouteEntry struct { + route *netlink.Route +} + +func (e *NetlinkRouteEntry) Match() proto.Match { + return &proto.LPMatch{ + Prefix: e.route.Dst, + } +} + +func (e *NetlinkRouteEntry) Action() proto.Action { + if len(e.route.MultiPath) == 0 { + return &proto.ViaAction{ + Nexthops: []*proto.NexthopInfo{ + &proto.NexthopInfo{ + Nexthop: e.route.Gw, + LinkIndex: e.route.LinkIndex, + }, + }, + } + } + nhs := make([]*proto.NexthopInfo, 0, len(e.route.MultiPath)) + for _, m := range e.route.MultiPath { + nhs = append(nhs, &proto.NexthopInfo{ + Nexthop: m.Gw, + LinkIndex: m.LinkIndex, + }) + } + return &proto.ViaAction{ + Nexthops: nhs, + } +} + +type L2VPNEntry struct { + vrf *config.VirtualNetwork + mac net.HardwareAddr + ip net.IP + nh net.IP +} + +func (e *L2VPNEntry) Match() proto.Match { + return &proto.L2VPNMatch{ + VRF: *e.vrf, + MAC: e.mac, + IP: e.ip, + } +} + +func (e *L2VPNEntry) Action() proto.Action { + return &proto.ViaAction{ + Nexthops: []*proto.NexthopInfo{ + &proto.NexthopInfo{ + Nexthop: e.nh, + }, + }, + } +} + +func newL2VPNEntry(vrf *config.VirtualNetwork, mac net.HardwareAddr, ip, nh net.IP) *L2VPNEntry { + return &L2VPNEntry{ + vrf: vrf, + mac: mac, + ip: ip, + nh: nh, + } +} + +type NetlinkProtocol struct { + vnMap map[string]*VirtualNetwork + m sync.RWMutex + routerID net.IP +} + +func NewNetlinkProtocol() *NetlinkProtocol { + p := &NetlinkProtocol{ + vnMap: make(map[string]*VirtualNetwork), + } + return p +} + +func (p *NetlinkProtocol) SetRouterID(id net.IP) error { + if p.routerID == nil { + p.routerID = id + + lo, err := netlink.LinkByName("lo") + if err != nil { + return fmt.Errorf("failed to get lo") + } + + addrList, err := netlink.AddrList(lo, netlink.FAMILY_ALL) + if err != nil { + return fmt.Errorf("failed to get addr list of lo") + } + + addr, err := netlink.ParseAddr(p.routerID.String() + "/32") + if err != nil { + return fmt.Errorf("failed to parse addr: %s", p.routerID) + } + + exist := false + for _, a := range addrList { + if a.Equal(*addr) { + exist = true + } + } + + if !exist { + log.Debugf("add route to lo") + addr.Scope = 254 + err = netlink.AddrAdd(lo, addr) + if err != nil { + return fmt.Errorf("failed to add addr %s to lo", addr) + } + } + } + return nil +} + +func (p *NetlinkProtocol) Type() proto.ProtocolType { + return proto.PROTO_NETLINK +} + +func (p *NetlinkProtocol) getNexthop(e proto.Entry) ([]*netlink.NexthopInfo, error) { + var flags int + d := e.Match().(*proto.LPMatch) + v := e.Action().(*proto.ViaAction) + nhs := make([]*netlink.NexthopInfo, 0, len(v.Nexthops)) + for _, nh := range v.Nexthops { + gw := nh.Nexthop + if (d.Prefix.IP.To4() != nil) && (gw.To4() != nil) { + nhs = append(nhs, &netlink.NexthopInfo{ + Gw: gw, + Flags: flags, + }) + continue + } + list, err := netlink.NeighList(0, netlink.FAMILY_V6) + if err != nil { + return nil, fmt.Errorf("failed to get neigh list: %s", err) + } + var neigh *netlink.Neigh + for _, n := range list { + if n.IP.Equal(nh.Nexthop) { + neigh = &n + break + } + } + if neigh == nil { + return nil, fmt.Errorf("no neighbor info for %s", d.Prefix.String()) + } + list, err = netlink.NeighList(neigh.LinkIndex, netlink.FAMILY_V4) + if err != nil { + return nil, fmt.Errorf("failed to get neigh list: %s", err) + } + flags = int(netlink.FLAG_ONLINK) + for _, n := range list { + if n.HardwareAddr.String() == neigh.HardwareAddr.String() { + nhs = append(nhs, &netlink.NexthopInfo{ + LinkIndex: n.LinkIndex, + Gw: n.IP.To4(), + Flags: flags, + }) + goto NEXT + } + } + gw = net.IPv4(169, 254, 0, 1) + err = netlink.NeighAdd(&netlink.Neigh{ + LinkIndex: neigh.LinkIndex, + State: netlink.NUD_PERMANENT, + IP: gw, + HardwareAddr: neigh.HardwareAddr, + }) + if err != nil { + return nil, fmt.Errorf("neigh add: %s", err) + } + nhs = append(nhs, &netlink.NexthopInfo{ + LinkIndex: neigh.LinkIndex, + Gw: gw, + Flags: flags, + }) + NEXT: + } + return nhs, nil +} + +func (p *NetlinkProtocol) modLPEntry(e proto.Entry, del bool) error { + if e.Action().Type() != proto.ACTION_VIA { + return fmt.Errorf("unsupported action type: %d", e.Action().Type()) + } + nhs, err := p.getNexthop(e) + if err != nil { + return err + } + if len(nhs) == 0 { + return nil + } + route := &netlink.Route{ + Src: p.routerID, + Dst: e.Match().(*proto.LPMatch).Prefix, + MultiPath: nhs, + Protocol: RTPROT_GOPLANE, + } + log.Info("route: %s", route) + if del { + return netlink.RouteDel(route) + } + return netlink.RouteReplace(route) +} + +func (p *NetlinkProtocol) modL2VPNEntry(e proto.Entry, del bool) error { + p.m.RLock() + defer p.m.RUnlock() + m := e.Match().(*proto.L2VPNMatch) + if vn, y := p.vnMap[m.VRF.RD]; !y { + return fmt.Errorf("vrf %s not found", m.VRF) + } else { + return vn.ModFDB(e, del) + } +} + +func (p *NetlinkProtocol) modL2VPNMcastEntry(e proto.Entry, del bool) error { + p.m.RLock() + defer p.m.RUnlock() + m := e.Match().(*proto.L2VPNMcastMatch) + if vn, y := p.vnMap[m.VRF.RD]; !y { + return fmt.Errorf("vrf %s not found", m.VRF) + } else { + return vn.ModConnMap(e, del) + } +} + +func (p *NetlinkProtocol) AddEntry(e proto.Entry) error { + switch e.Match().Type() { + case proto.MATCH_L2VPN: + return p.modL2VPNEntry(e, false) + case proto.MATCH_L2VPN_MCAST: + return p.modL2VPNMcastEntry(e, false) + case proto.MATCH_LP: + return p.modLPEntry(e, false) + default: + return nil + } +} + +func (p *NetlinkProtocol) DeleteEntry(e proto.Entry) error { + switch e.Match().Type() { + case proto.MATCH_L2VPN: + return p.modL2VPNEntry(e, true) + case proto.MATCH_L2VPN_MCAST: + return p.modL2VPNMcastEntry(e, true) + case proto.MATCH_LP: + return p.modLPEntry(e, true) + default: + return nil + } +} + +func (p *NetlinkProtocol) AddVirtualNetwork(routerID string, c config.VirtualNetwork) error { + p.m.Lock() + defer p.m.Unlock() + v := NewVirtualNetwork(routerID, c) + p.vnMap[c.RD] = v + go v.Serve() + return nil +} + +func (p *NetlinkProtocol) DeleteVirtualNetwork(c config.VirtualNetwork) error { + p.m.Lock() + defer p.m.Unlock() + v := p.vnMap[c.RD] + v.Stop() + delete(p.vnMap, v.config.RD) + return nil +} + +func (p *NetlinkProtocol) getVRFByLinkIndex(index int) *config.VirtualNetwork { + p.m.RLock() + defer p.m.RUnlock() + for _, v := range p.vnMap { + for _, i := range v.config.MemberInterfaces { + link, _ := netlink.LinkByName(i) + if link.Attrs().Index == index { + return &v.config + } + } + } + return nil +} + +type NetlinkEntryWatcher struct { + socket *nl.NetlinkSocket + p *NetlinkProtocol + neighCh chan []syscall.NetlinkMessage + linkCh chan netlink.LinkUpdate + routeCh chan netlink.RouteUpdate + closeCh chan struct{} +} + +func (w *NetlinkEntryWatcher) Recv() ([]proto.EntryEvent, error) { + for { + select { + case msgs := <-w.neighCh: + events := make([]proto.EntryEvent, 0, len(msgs)) + for _, msg := range msgs { + t := RTM_TYPE(msg.Header.Type) + withdraw := false + switch t { + case RTM_DELNEIGH: + withdraw = true + fallthrough + case RTM_NEWNEIGH: + n, _ := netlink.NeighDeserialize(msg.Data) + if vrf := w.p.getVRFByLinkIndex(n.LinkIndex); vrf != nil { + events = append(events, proto.EntryEvent{ + Entry: newL2VPNEntry(vrf, n.HardwareAddr, n.IP, nil), + IsDel: withdraw, + From: proto.PROTO_NETLINK, + }) + } + } + } + if len(events) > 0 { + return events, nil + } + case ev := <-w.linkCh: + log.Info("link ev:", ev) + case ev := <-w.routeCh: + log.Info("route ev:", ev) + if ev.Route.Protocol == RTPROT_GOPLANE { + continue + } + return []proto.EntryEvent{proto.EntryEvent{ + Entry: &NetlinkRouteEntry{ + route: &ev.Route, + }, + IsDel: ev.Type == uint16(RTM_DELROUTE), + From: proto.PROTO_NETLINK, + }}, nil + } + } + return nil, nil +} + +func (w *NetlinkEntryWatcher) Close() error { + close(w.closeCh) + w.socket.Close() + return nil +} + +func (w *NetlinkEntryWatcher) serve() error { + if err := netlink.LinkSubscribe(w.linkCh, w.closeCh); err != nil { + return err + } + if err := netlink.RouteSubscribe(w.routeCh, w.closeCh); err != nil { + return err + } + for { + msgs, err := w.socket.Receive() + if err != nil { + log.Fatal(err) + } + w.neighCh <- msgs + } + return nil +} + +func (p *NetlinkProtocol) WatchEntry() (proto.EntryWatcher, error) { + s, err := nl.Subscribe(syscall.NETLINK_ROUTE, uint(RTMGRP_NEIGH), uint(RTMGRP_LINK), uint(RTMGRP_NOTIFY), uint(RTMGRP_IPV4_IFADDR)) + if err != nil { + return nil, err + } + neighCh := make(chan []syscall.NetlinkMessage) + linkCh := make(chan netlink.LinkUpdate) + routeCh := make(chan netlink.RouteUpdate) + closeCh := make(chan struct{}) + w := &NetlinkEntryWatcher{ + socket: s, + p: p, + neighCh: neighCh, + linkCh: linkCh, + routeCh: routeCh, + closeCh: closeCh, + } + go func() { + log.Fatal(w.serve()) + }() + return w, nil +} diff --git a/netlink/pf_packet.go b/protocol/netlink/pf_packet.go similarity index 100% rename from netlink/pf_packet.go rename to protocol/netlink/pf_packet.go diff --git a/protocol/netlink/virtualnetwork.go b/protocol/netlink/virtualnetwork.go new file mode 100644 index 0000000..06e6d35 --- /dev/null +++ b/protocol/netlink/virtualnetwork.go @@ -0,0 +1,303 @@ +// Copyright (C) 2017 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package netlink + +import ( + "fmt" + "net" + "syscall" + + log "github.com/Sirupsen/logrus" + "github.com/osrg/goplane/config" + proto "github.com/osrg/goplane/protocol" + "github.com/vishvananda/netlink" + "gopkg.in/tomb.v2" +) + +type VirtualNetwork struct { + t tomb.Tomb + connMap map[string]net.Conn + config config.VirtualNetwork + floodCh chan []byte + routerId string +} + +func (n *VirtualNetwork) Stop() { + n.t.Kill(fmt.Errorf("admin stop")) +} + +func (n *VirtualNetwork) Serve() error { + log.Debugf("vtep intf: %s", n.config.VtepInterface) + link, err := netlink.LinkByName(n.config.VtepInterface) + master := 0 + if err == nil { + log.Debug("link type:", link.Type()) + vtep := link.(*netlink.Vxlan) + err = netlink.LinkSetDown(vtep) + log.Debugf("set %s down", n.config.VtepInterface) + if err != nil { + return fmt.Errorf("failed to set link %s down", n.config.VtepInterface) + } + master = vtep.MasterIndex + log.Debugf("del %s", n.config.VtepInterface) + err = netlink.LinkDel(link) + if err != nil { + return fmt.Errorf("failed to del %s", n.config.VtepInterface) + } + } + + if master > 0 { + b, _ := netlink.LinkByIndex(master) + br := b.(*netlink.Bridge) + err = netlink.LinkSetDown(br) + log.Debugf("set %s down", br.LinkAttrs.Name) + if err != nil { + return fmt.Errorf("failed to set %s down", br.LinkAttrs.Name) + } + log.Debugf("del %s", br.LinkAttrs.Name) + err = netlink.LinkDel(br) + if err != nil { + return fmt.Errorf("failed to del %s", br.LinkAttrs.Name) + } + } + + brName := fmt.Sprintf("br%d", n.config.VNI) + + b, err := netlink.LinkByName(brName) + if err == nil { + br := b.(*netlink.Bridge) + err = netlink.LinkSetDown(br) + log.Debugf("set %s down", br.LinkAttrs.Name) + if err != nil { + return fmt.Errorf("failed to set %s down", br.LinkAttrs.Name) + } + log.Debugf("del %s", br.LinkAttrs.Name) + err = netlink.LinkDel(br) + if err != nil { + return fmt.Errorf("failed to del %s", br.LinkAttrs.Name) + } + } + + br := &netlink.Bridge{ + LinkAttrs: netlink.LinkAttrs{ + Name: brName, + }, + } + + log.Debugf("add %s", brName) + err = netlink.LinkAdd(br) + if err != nil { + return fmt.Errorf("failed to add link %s. %s", brName, err) + } + err = netlink.LinkSetUp(br) + if err != nil { + return fmt.Errorf("failed to set %s up", brName) + } + + link = &netlink.Vxlan{ + LinkAttrs: netlink.LinkAttrs{ + Name: n.config.VtepInterface, + }, + VxlanId: int(n.config.VNI), + SrcAddr: net.ParseIP(n.routerId), + } + + log.Debugf("add %s", n.config.VtepInterface) + err = netlink.LinkAdd(link) + if err != nil { + return fmt.Errorf("failed to add link %s. %s", n.config.VtepInterface, err) + } + err = netlink.LinkSetUp(link) + if err != nil { + return fmt.Errorf("failed to set %s up", n.config.VtepInterface) + } + + err = netlink.LinkSetMaster(link, br) + if err != nil { + return fmt.Errorf("failed to set master %s dev %s", brName, n.config.VtepInterface) + } + + for _, member := range n.config.MemberInterfaces { + m, err := netlink.LinkByName(member) + if err != nil { + log.Errorf("can't find %s", member) + continue + } + err = netlink.LinkSetUp(m) + if err != nil { + return fmt.Errorf("failed to set %s up", member) + } + err = netlink.LinkSetMaster(m, br) + if err != nil { + return fmt.Errorf("failed to set master %s dev %s", brName, member) + } + } + + for _, member := range n.config.SniffInterfaces { + n.t.Go(func() error { + return n.sniffPkt(member) + }) + } + + for { + select { + case <-n.t.Dying(): + log.Errorf("stop virtualnetwork %s", n.config.RD) + for h, conn := range n.connMap { + log.Debugf("close udp connection to %s", h) + conn.Close() + } + return nil + case p := <-n.floodCh: + err = n.flood(p) + if err != nil { + log.Errorf("flood failed. kill main loop. err: %s", err) + return err + } + } + } +} + +func (f *VirtualNetwork) ModConnMap(e proto.Entry, del bool) error { + addr := e.Action().(*proto.ViaAction).Nexthops[0].Nexthop.String() + log.Debugf("mod cannection map: nh %s, vtep addr %s withdraw %t", addr, del) + if del { + _, ok := f.connMap[addr] + if !ok { + return fmt.Errorf("can't find %s conn", addr) + } + + f.connMap[addr].Close() + delete(f.connMap, addr) + } else { + _, ok := f.connMap[addr] + if ok { + log.Debugf("refresh. close connection to %s", addr) + f.connMap[addr].Close() + delete(f.connMap, addr) + } + udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, f.config.VxlanPort)) + if err != nil { + log.Fatal(err) + } + + log.Debugf("connect to %s", addr) + conn, err := net.DialUDP("udp", nil, udpAddr) + if err != nil { + log.Warnf("failed to dial UDP(%s) %s", addr, err) + return nil + } + f.connMap[addr] = conn + } + return nil +} + +func (f *VirtualNetwork) ModFDB(e proto.Entry, del bool) error { + mac := e.Match().(*proto.L2VPNMatch).MAC + nh := e.Action().(*proto.ViaAction).Nexthops[0].Nexthop + log.WithFields(log.Fields{ + "Topic": "VirtualNetwork", + "Etag": f.config.Etag, + }).Debugf("modFdb new path, mac: %s, nexthop: %s, withdraw: %t", mac, nh, del) + + link, err := netlink.LinkByName(f.config.VtepInterface) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "VirtualNetwork", + "Etag": f.config.Etag, + }).Debugf("failed lookup link by name: %s", f.config.VtepInterface) + return nil + } + + n := &netlink.Neigh{ + LinkIndex: link.Attrs().Index, + Family: int(netlink.NDA_VNI), + State: int(netlink.NUD_NOARP | netlink.NUD_PERMANENT), + Type: syscall.RTM_NEWNEIGH, + Flags: int(netlink.NTF_SELF), + IP: nh, + HardwareAddr: mac, + } + + if del { + err = netlink.NeighDel(n) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "VirtualNetwork", + "Etag": f.config.Etag, + }).Errorf("failed to del fdb: %s, %s", n, err) + } + } else { + err = netlink.NeighAppend(n) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "VirtualNetwork", + "Etag": f.config.Etag, + }).Debugf("failed to add fdb: %s, %s", n, err) + } + } + return err +} + +func (f *VirtualNetwork) flood(pkt []byte) error { + vxlanHeader := NewVXLAN(f.config.VNI) + b := vxlanHeader.Serialize() + b = append(b, pkt...) + + for _, c := range f.connMap { + cnt, err := c.Write(b) + log.WithFields(log.Fields{ + "Topic": "VirtualNetwork", + "Etag": f.config.Etag, + }).Debugf("send to %s: cnt:%d, err:%s", c.RemoteAddr(), cnt, err) + if err != nil { + return err + } + } + + return nil +} + +func (f *VirtualNetwork) sniffPkt(ifname string) error { + conn, err := NewPFConn(ifname) + if err != nil { + return err + } + buf := make([]byte, 2048) + for { + n, err := conn.Read(buf) + if err != nil { + log.Errorf("failed to recv from %s, err: %s", conn, err) + return err + } + log.WithFields(log.Fields{ + "Topic": "VirtualNetwork", + "Etag": f.config.Etag, + }).Debugf("recv from %s, len: %d", conn, n) + f.floodCh <- buf[:n] + } +} + +func NewVirtualNetwork(routerID string, config config.VirtualNetwork) *VirtualNetwork { + floodCh := make(chan []byte, 16) + + return &VirtualNetwork{ + config: config, + connMap: map[string]net.Conn{}, + floodCh: floodCh, + routerId: routerID, + } +} diff --git a/netlink/vxlan.go b/protocol/netlink/vxlan.go similarity index 100% rename from netlink/vxlan.go rename to protocol/netlink/vxlan.go diff --git a/protocol/protocol.go b/protocol/protocol.go new file mode 100644 index 0000000..4c00c54 --- /dev/null +++ b/protocol/protocol.go @@ -0,0 +1,196 @@ +// Copyright (C) 2017 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package protocol + +import ( + "fmt" + "net" + + "github.com/osrg/goplane/config" +) + +type Family int + +const ( + _ Family = iota + V4 + V6 +) + +type ProtocolType int + +const ( + _ ProtocolType = iota + PROTO_NETLINK + PROTO_IPTABLES + PROTO_GOBGP +) + +type MatchType int + +const ( + _ MatchType = iota + MATCH_LP // Longest Prefix Match + MATCH_L2VPN // L2VPN Match + MATCH_L2VPN_MCAST + MATCH_ACL +) + +type LPMatch struct { + Prefix *net.IPNet +} + +func (m *LPMatch) Type() MatchType { + return MATCH_LP +} + +func (m *LPMatch) String() string { + return m.Prefix.String() +} + +type L2VPNMatch struct { + VRF config.VirtualNetwork + MAC net.HardwareAddr + IP net.IP +} + +func (m *L2VPNMatch) Type() MatchType { + return MATCH_L2VPN +} + +func (m *L2VPNMatch) String() string { + return fmt.Sprintf("{VRF: %s, MAC: %s, IP: %s}", m.VRF.RD, m.MAC, m.IP) +} + +type L2VPNMcastMatch struct { + VRF config.VirtualNetwork +} + +func (m *L2VPNMcastMatch) Type() MatchType { + return MATCH_L2VPN_MCAST +} + +func (m *L2VPNMcastMatch) String() string { + return fmt.Sprintf("{VRF: %s}", m.VRF.RD) +} + +type ACLMatch struct { + SrcIPPrefix *net.IPNet + DstIPPrefix *net.IPNet + IPProto int +} + +func (m *ACLMatch) Type() MatchType { + return MATCH_ACL +} + +func (m *ACLMatch) String() string { + return fmt.Sprintf("{SRC: %s, DST: %s, IPProto: %d", m.SrcIPPrefix, m.DstIPPrefix, m.IPProto) +} + +type Match interface { + Type() MatchType + String() string +} + +type ActionType int + +const ( + _ ActionType = iota + ACTION_VIA // Nexthop Action + ACTION_DROP +) + +type NexthopInfo struct { + Nexthop net.IP + LinkIndex int +} + +func (i *NexthopInfo) String() string { + return fmt.Sprintf("{Nexthop: %s, Link: %d}", i.Nexthop, i.LinkIndex) +} + +type ViaAction struct { + Nexthops []*NexthopInfo +} + +func (a *ViaAction) Type() ActionType { + return ACTION_VIA +} + +func (a *ViaAction) String() string { + return fmt.Sprintf("%v", a.Nexthops) +} + +type DropAction struct { +} + +func (a *DropAction) Type() ActionType { + return ACTION_DROP +} + +func (a *DropAction) String() string { + return "DROP" +} + +type Action interface { + Type() ActionType + String() string +} + +type BaseEntry struct { + match Match + action Action +} + +func (e *BaseEntry) Match() Match { + return e.match +} + +func (e *BaseEntry) Action() Action { + return e.action +} + +type Entry interface { + Match() Match + Action() Action +} + +type EntryEvent struct { + Entry Entry + IsDel bool + From ProtocolType +} + +type EntryWatcher interface { + Recv() ([]EntryEvent, error) + Close() error +} + +type Protocol interface { + Type() ProtocolType + AddEntry(Entry) error + DeleteEntry(Entry) error + WatchEntry() (EntryWatcher, error) + + SetRouterID(net.IP) error + + AddVirtualNetwork(string, config.VirtualNetwork) error + DeleteVirtualNetwork(config.VirtualNetwork) error + // + // AddVRFRoute(config.VirtualNetwork, Route) error + // DeleteVRFRoute(config.VirtualNetwork, Route) error +} diff --git a/test/iptables/Dockerfile b/test/iptables/Dockerfile deleted file mode 100644 index 1367519..0000000 --- a/test/iptables/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -# goplane (part of Ryu SDN Framework) -# - -FROM osrg/gobgp - -MAINTAINER ISHIDA Wataru - -RUN apt-get install -qy iptables -COPY goplane /go/src/github.com/osrg/goplane/ -RUN go install -a github.com/osrg/gobgp/gobgp -RUN go get -v github.com/osrg/goplane/goplaned -RUN go install github.com/osrg/goplane/goplaned diff --git a/test/iptables/README.md b/test/iptables/README.md deleted file mode 100644 index 4d0e893..0000000 --- a/test/iptables/README.md +++ /dev/null @@ -1,96 +0,0 @@ -goplane iptables/flowspec demo -=== - -This demo shows remote ACL configuration using [BGP/FLOWSPEC](https://tools.ietf.org/html/rfc5575) and iptables. - -## How to run -you only need to type 3 commands to play (tested in Ubuntu trusty and xenial). - -1. install dependent python package to run demo.py - - ``` - $ pip install -r ./pip-requires.txt - ``` -2. install docker and other dependent tools. also create goplane container. - - ``` - $ sudo ./demo.py prepare - ``` -3. run and play! - - ``` - $ sudo ./demo.py - ``` - -## How to play - -demo.py boots 3 goplane containers (g1, g2, g3) in the following topology -and starts goplaned and gobgpd. - -``` - 40.0.0.0/24 - ------ 10.0.0.0/24 ------ 30.0.0.0/24 ------ - | g1 |-------------| g2 |-----------------| g3 | - ------.1 .2------.1 .2------ -``` - -Check BGP sessions comes up using `gobgp neighbor` command. -It will take about 10 seconds to establish BGP sessions. - -```shell -$ docker exec -it g2 gobgp neighbor -Peer AS Up/Down State |#Advertised Received Accepted -192.168.10.2 65000 00:08:28 Establ | 0 0 0 -192.168.10.4 65000 00:08:29 Establ | 0 0 0 -``` - -For the full documentation of gobgp command, see [gobgp](https://github.com/osrg/gobgp/blob/master/docs/sources/cli-command-syntax.md). - -Next, check we can ping to `g3` (30.0.0.2 and 40.0.0.2) from g1. - -```shell -$ docker exec -it g1 ping 30.0.0.2 -PING 30.0.0.2 (30.0.0.2): 56 data bytes -64 bytes from 30.0.0.2: icmp_seq=0 ttl=63 time=0.155 ms -^C--- 30.0.0.2 ping statistics --- -1 packets transmitted, 1 packets received, 0% packet loss -round-trip min/avg/max/stddev = 0.155/0.155/0.155/0.000 ms - -$ docker exec -it g1 ping 40.0.0.2 -PING 40.0.0.2 (40.0.0.2): 56 data bytes -64 bytes from 40.0.0.2: icmp_seq=0 ttl=63 time=0.116 ms -^C--- 40.0.0.2 ping statistics --- -1 packets transmitted, 1 packets received, 0% packet loss -round-trip min/avg/max/stddev = 0.116/0.116/0.116/0.000 ms -``` - -Say, `g1` is the end-user, `g2` is your border router, and `g3` is your host -serving two IP address (30.0.0.2, 40.0.0.2). - -If you want to stop a traffic destined for 30.0.0.2 from `g1` at upstream -router `g2`, you can inject a flowspec route to do that from `g3` and remotely -configure `g2`'s ACL rules. Try the next command. - -```shell -$ docker exec -it g3 gobgp global rib -a ipv4-flowspec add match destination 30.0.0.2/32 then discard -``` - -Try pinging from `g1` to `g3` (30.0.0.2 and 40.0.0.2). -If everything is working fine, ping to 30.0.0.2 won't succeed, but 40.0.0.2 will. - -Let's check iptables configuration at `g2` - -```shell -$ docker exec -it g2 iptables -L -Chain INPUT (policy ACCEPT) -target prot opt source destination - -Chain FORWARD (policy ACCEPT) -target prot opt source destination -DROP all -- anywhere 30.0.0.2 - -Chain OUTPUT (policy ACCEPT) -target prot opt source destination -``` - -You can see iptables at `g2` is remotely configured by `g3` diff --git a/test/iptables/demo.py b/test/iptables/demo.py deleted file mode 100755 index 0eb9f31..0000000 --- a/test/iptables/demo.py +++ /dev/null @@ -1,386 +0,0 @@ -#!/usr/bin/env python -# Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -# implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from fabric.context_managers import shell_env -from fabric.api import local -from fabric import colors -from optparse import OptionParser -import netaddr -import toml -import itertools -import os -import sys - -TEST_BASE_DIR = '/tmp/goplane' - -def install_docker_and_tools(): - print "start install packages of test environment." - local("apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys " - "36A1D7869245C8950F966E92D8576A8BA88D21E9", capture=True) - local('sh -c "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"', - capture=True) - local("apt-get update", capture=True) - local("apt-get install lxc-docker bridge-utils", capture=True) - local("ln -sf /usr/bin/docker.io /usr/local/bin/docker", capture=True) - local("gpasswd -a `whoami` docker", capture=True) - local("wget https://raw.github.com/jpetazzo/pipework/master/pipework -O /usr/local/bin/pipework", - capture=True) - local("chmod 755 /usr/local/bin/pipework", capture=True) - local("docker pull osrg/gobgp", capture=True) - update_goplane() - -def update_goplane(): - local("cp Dockerfile ../../../") - local("cd ../../../ && docker build --no-cache -t goplane . && rm Dockerfile") - -def get_bridges(): - return local("brctl show | awk 'NR > 1{print $1}'", - capture=True).split('\n') - - -def get_containers(): - output = local("docker ps -a | awk 'NR > 1 {print $NF}'", capture=True) - if output == '': - return [] - return output.split('\n') - - -class CmdBuffer(list): - def __init__(self, delim='\n'): - super(CmdBuffer, self).__init__() - self.delim = delim - - def __lshift__(self, value): - self.append(value) - - def __str__(self): - return self.delim.join(self) - - -class Bridge(object): - def __init__(self, name, subnet='', with_ip=True): - self.name = name - self.with_ip = with_ip - if with_ip: - self.subnet = netaddr.IPNetwork(subnet) - - def f(): - for host in self.subnet: - yield host - self._ip_generator = f() - # throw away first network address - self.next_ip_address() - - if self.name in get_bridges(): - self.delete() - - local("ip link add {0} type bridge".format(self.name), capture=True) - local("ip link set up dev {0}".format(self.name), capture=True) - - if with_ip: - self.ip_addr = self.next_ip_address() - local("ip addr add {0} dev {1}".format(self.ip_addr, self.name), - capture=True) - - self.ctns = [] - - def next_ip_address(self): - return "{0}/{1}".format(self._ip_generator.next(), - self.subnet.prefixlen) - - def addif(self, ctn, name='', mac=''): - if name == '': - name = self.name - self.ctns.append(ctn) - if self.with_ip: - ctn.pipework(self, self.next_ip_address(), name) - else: - ctn.pipework(self, '0/0', name) - - if mac != '': - ctn.local("ip link set addr {0} dev {1}".format(mac, name)) - - def delete(self): - local("ip link set down dev {0}".format(self.name), capture=True) - local("ip link delete {0} type bridge".format(self.name), capture=True) - - -class Container(object): - def __init__(self, name, image): - self.name = name - self.image = image - self.shared_volumes = [] - self.ip_addrs = [] - self.is_running = False - - if self.name in get_containers(): - self.stop() - - def run(self): - c = CmdBuffer(' ') - c << "docker run --privileged=true --net=none" - for sv in self.shared_volumes: - c << "-v {0}:{1}".format(sv[0], sv[1]) - c << "--name {0} -id {1}".format(self.name, self.image) - - self.id = local(str(c), capture=True) - self.is_running = True - self.local("ip li set up dev lo") - return 0 - - def stop(self): - ret = local("docker rm -f " + self.name, capture=True) - self.is_running = False - return ret - - def pipework(self, bridge, ip_addr, intf_name=""): - if not self.is_running: - print colors.yellow('call run() before pipeworking') - return - c = CmdBuffer(' ') - c << "pipework {0}".format(bridge.name) - - if intf_name != "": - c << "-i {0}".format(intf_name) - else: - intf_name = "eth1" - c << "{0} {1}".format(self.name, ip_addr) - self.ip_addrs.append((intf_name, ip_addr, bridge)) - return local(str(c), capture=True) - - def local(self, cmd): - return local("docker exec -it {0} {1}".format(self.name, cmd)) - - -class BGPContainer(Container): - - WAIT_FOR_BOOT = 0 - RETRY_INTERVAL = 5 - - def __init__(self, name, asn, router_id, ctn_image_name): - self.config_dir = "{0}/{1}".format(TEST_BASE_DIR, name) - local('if [ -e {0} ]; then rm -r {0}; fi'.format(self.config_dir)) - local('mkdir -p {0}'.format(self.config_dir)) - self.asn = asn - self.router_id = router_id - self.peers = {} - self.routes = [] - self.policies = {} - super(BGPContainer, self).__init__(name, ctn_image_name) - - def run(self): - self.create_config() - super(BGPContainer, self).run() - - def add_peer(self, peer, passwd='', evpn=False, is_rs_client=False, - policies=None, passive=False): - neigh_addr = '' - for me, you in itertools.product(self.ip_addrs, peer.ip_addrs): - if me[2] == you[2]: - neigh_addr = you[1] - - if neigh_addr == '': - raise Exception('peer {0} seems not ip reachable'.format(peer)) - - if not policies: - policies = [] - - self.peers[peer] = {'neigh_addr': neigh_addr, - 'passwd': passwd, - 'evpn': evpn, - 'is_rs_client': is_rs_client, - 'policies': policies, - 'passive' : passive} - self.create_config() - - def del_peer(self, peer): - del self.peers[peer] - self.create_config() - if self.is_running: - self.reload_config() - - def create_config(self): - raise Exception('implement create_config() method') - - def reload_config(self): - raise Exception('implement reload_config() method') - - -class GoPlaneContainer(BGPContainer): - - PEER_TYPE_INTERNAL = 'internal' - PEER_TYPE_EXTERNAL = 'external' - SHARED_VOLUME = '/root/shared_volume' - - def __init__(self, name, asn, router_id, ctn_image_name='goplane', - log_level='debug'): - super(GoPlaneContainer, self).__init__(name, asn, router_id, - ctn_image_name) - self.shared_volumes.append((self.config_dir, self.SHARED_VOLUME)) - self.vns = [] - self.log_level = 'debug' - - def start_goplane(self): - c = CmdBuffer() - c << '#!/bin/bash' - c << 'gobgpd -f {0}/gobgpd.conf -l {1} -p > ' \ - '{0}/gobgpd.log 2>&1'.format(self.SHARED_VOLUME, self.log_level) - cmd = 'echo "{0:s}" > {1}/start_gobgp.sh'.format(c, self.config_dir) - local(cmd, capture=True) - cmd = "chmod 755 {0}/start_gobgp.sh".format(self.config_dir) - local(cmd, capture=True) - cmd = 'docker exec -d {0} {1}/start_gobgp.sh'.format(self.name, - self.SHARED_VOLUME) - local(cmd, capture=True) - - c << 'goplaned -f {0}/goplaned.conf -l {1} -p > ' \ - '{0}/goplaned.log 2>&1'.format(self.SHARED_VOLUME, self.log_level) - cmd = 'echo "{0:s}" > {1}/start_goplane.sh'.format(c, self.config_dir) - local(cmd, capture=True) - cmd = "chmod 755 {0}/start_goplane.sh".format(self.config_dir) - local(cmd, capture=True) - cmd = 'docker exec -d {0} {1}/start_goplane.sh'.format(self.name, - self.SHARED_VOLUME) - local(cmd, capture=True) - - def run(self): - super(GoPlaneContainer, self).run() - return self.WAIT_FOR_BOOT - - def create_goplane_config(self): - config = {'iptables': {'enabled': True, 'chain': 'FORWARD'}} - - with open('{0}/goplaned.conf'.format(self.config_dir), 'w') as f: - print colors.yellow(toml.dumps(config)) - f.write(toml.dumps(config)) - - def create_gobgp_config(self): - config = {'global': {'config': {'as': self.asn, 'router-id': self.router_id}}} - for peer, info in self.peers.iteritems(): - if self.asn == peer.asn: - peer_type = self.PEER_TYPE_INTERNAL - else: - peer_type = self.PEER_TYPE_EXTERNAL - - afi_safi_list = [] - version = netaddr.IPNetwork(info['neigh_addr']).version - if version == 4: - afi_safi_list.append({'config': {'afi-safi-name': 'ipv4-unicast'}}) - elif version == 6: - afi_safi_list.append({'config': {'afi-safi-name': 'ipv6-unicast'}}) - else: - Exception('invalid ip address version. {0}'.format(version)) - - afi_safi_list.append({'config': {'afi-safi-name': 'ipv4-flowspec'}}) - - n = {'config': { - 'neighbor-address': info['neigh_addr'].split('/')[0], - 'peer-as': peer.asn, - 'local-as': self.asn, - 'auth-password': info['passwd'], - }, - 'afi-safis': afi_safi_list, - } - - if 'neighbors' not in config: - config['neighbors'] = [] - - config['neighbors'].append(n) - - with open('{0}/gobgpd.conf'.format(self.config_dir), 'w') as f: - print colors.yellow(toml.dumps(config)) - f.write(toml.dumps(config)) - - def create_config(self): - self.create_gobgp_config() - self.create_goplane_config() - - def reload_config(self): - cmd = 'docker exec {0} /usr/bin/pkill gobgpd -SIGHUP'.format(self.name) - local(cmd, capture=True) - cmd = 'docker exec {0} /usr/bin/pkill goplaned -SIGHUP'.format(self.name) - local(cmd, capture=True) - - -if __name__ == '__main__': - - parser = OptionParser(usage="usage: %prog [prepare|update|clean]") - options, args = parser.parse_args() - - os.chdir(os.path.abspath(os.path.dirname(__file__))) - - if os.getegid() != 0: - print "execute as root" - sys.exit(1) - - if len(args) > 0: - if args[0] == 'prepare': - install_docker_and_tools() - sys.exit(0) - elif args[0] == 'update': - update_goplane() - sys.exit(0) - elif args[0] == 'clean': - for ctn in get_containers(): - if ctn[0] == 'g': - local("docker rm -f {0}".format(ctn), capture=True) - - for i in range(3): - name = "br0" + str(i+1) - local("ip link set down dev {0}".format(name), capture=True) - local("ip link delete {0} type bridge".format(name), capture=True) - - sys.exit(0) - else: - print "usage: demo.py [prepare|update|clean]" - sys.exit(1) - - g1 = GoPlaneContainer(name='g1', asn=65000, router_id='192.168.0.1') - g2 = GoPlaneContainer(name='g2', asn=65000, router_id='192.168.0.2') - g3 = GoPlaneContainer(name='g3', asn=65000, router_id='192.168.0.3') - ctns = [g1, g2, g3] - - [ctn.run() for ctn in ctns] - - br01 = Bridge(name='br01', subnet='192.168.10.0/24') - [br01.addif(ctn, 'eth1') for ctn in ctns] - - g1.add_peer(g2) - g2.add_peer(g1) - g2.add_peer(g3) - g3.add_peer(g2) - - [ctn.start_goplane() for ctn in ctns] - - br02 = Bridge(name='br02', with_ip=False) - br02.addif(g1, 'eth2') - br02.addif(g2, 'eth2') - - br03 = Bridge(name='br03', with_ip=False) - br03.addif(g2, 'eth3') - br03.addif(g3, 'eth2') - - g1.local("ip a add 10.0.0.1/24 dev eth2") - g2.local("ip a add 10.0.0.2/24 dev eth2") - - g2.local("ip a add 30.0.0.1/24 dev eth3") - g3.local("ip a add 30.0.0.2/24 dev eth2") - g2.local("ip a add 40.0.0.1/24 dev eth3") - g3.local("ip a add 40.0.0.2/24 dev eth2") - - g1.local("ip route add default via 10.0.0.2") - g3.local("ip route add default via 30.0.0.1") - diff --git a/test/netlink/base.py b/test/netlink/base.py index 3586013..8b02411 100755 --- a/test/netlink/base.py +++ b/test/netlink/base.py @@ -234,13 +234,14 @@ class GoPlaneContainer(BGPContainer): SHARED_VOLUME = '/root/shared_volume' def __init__(self, name, asn, router_id, ctn_image_name='osrg/goplane', - log_level='debug', bgp_remote=False): + log_level='debug', bgp_remote=False, iptables=False): super(GoPlaneContainer, self).__init__(name, asn, router_id, ctn_image_name) self.shared_volumes.append((self.config_dir, self.SHARED_VOLUME)) self.vns = [] self.log_level = log_level self.bgp_remote = bgp_remote + self.iptables = iptables def start_goplane(self): if self.bgp_remote: @@ -278,7 +279,9 @@ def create_goplane_config(self): 'sniff-interfaces': info['member'], 'member-interfaces': info['member']}) - config = {'dataplane': dplane_config} + config = {'dataplane': dplane_config, 'router-id': self.router_id} + if self.iptables: + config['iptables'] = {'enabled': True, 'chain': 'FORWARD'} if not self.bgp_remote: bgp_config = self.create_gobgp_config() @@ -320,6 +323,8 @@ def create_gobgp_config(self): ] n = {'config': {'neighbor-interface': info['interface']}, 'afi-safis': afi_safi_list} + if self.iptables: + afi_safi_list.append({'config': {'afi-safi-name': 'ipv4-flowspec'}}) if len(info['passwd']) > 0: n['config']['auth-password'] = info['passwd'] diff --git a/test/netlink/iptables_test.py b/test/netlink/iptables_test.py new file mode 100644 index 0000000..9feb05d --- /dev/null +++ b/test/netlink/iptables_test.py @@ -0,0 +1,93 @@ +# Copyright (C) 2017 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest +import nose +import os +import sys +import time +import logging +import json +from noseplugin import OptionParser +from base import * + +class Test(unittest.TestCase): + + @classmethod + def setUpClass(cls): + + g1 = GoPlaneContainer(name='g1', asn=65000, router_id='192.168.0.1', iptables=True) + g2 = GoPlaneContainer(name='g2', asn=65001, router_id='192.168.0.2', iptables=True) + g3 = GoPlaneContainer(name='g3', asn=65002, router_id='192.168.0.3', iptables=True) + bgps = [g1, g2, g3] + + ctns = bgps + [ctn.run() for ctn in ctns] + + br01 = Bridge(name='br01', subnet='192.168.10.0/24') + [br01.addif(ctn, 'eth1') for ctn in [g1, g2]] + + br02 = Bridge(name='br02', subnet='192.168.20.0/24') + [br02.addif(ctn, 'eth2') for ctn in [g2, g3]] + + g1.add_peer(g2) + g2.add_peer(g1) + + g2.add_peer(g3) + g3.add_peer(g2) + + [ctn.start_goplane() for ctn in bgps] + + cls.ctns = {ctn.name: ctn for ctn in ctns} + + def test_01_neighbor_established(self): + for i in range(20): + if all(v['state']['session-state'] == 'established' for v in json.loads(self.ctns['g1'].local('gobgp neighbor -j'))): + logging.debug('all peers got established') + return + time.sleep(1) + raise Exception('timeout') + + def ping(self, ip, expect='true'): + for i in range(10): + out = self.ctns['g1'].local("bash -c 'ping --numeric -c 1 {0} 2>&1 > /dev/null && echo true || echo false'".format(ip)).strip() + if out == expect: + logging.debug('ping ok') + return + time.sleep(1) + raise Exception('timeout') + + def test_02_ping_check(self): + self.ping(self.ctns['g2'].router_id) + self.ping(self.ctns['g3'].router_id) + + def test_03_add_flowspec_rule(self): + self.ctns['g3'].local("gobgp g ri add -a ipv4-flowspec match destination {0}/32 then discard".format(self.ctns['g3'].router_id)) + self.ping(self.ctns['g2'].router_id) + self.ping(self.ctns['g3'].router_id, 'false') + + def test_03_del_flowspec_rule(self): + self.ctns['g3'].local("gobgp g ri del -a ipv4-flowspec match destination {0}/32 then discard".format(self.ctns['g3'].router_id)) + self.ping(self.ctns['g2'].router_id) + self.ping(self.ctns['g3'].router_id) + + +if __name__ == '__main__': + if os.geteuid() is not 0: + print "you are not root." + sys.exit(1) + logging.basicConfig(stream=sys.stderr) + nose.main(argv=sys.argv, addplugins=[OptionParser()], + defaultTest=sys.argv[0]) diff --git a/test/netlink/mpls_test.py b/test/netlink/mpls_test.py new file mode 100644 index 0000000..e478be9 --- /dev/null +++ b/test/netlink/mpls_test.py @@ -0,0 +1,90 @@ +# Copyright (C) 2017 Nippon Telegraph and Telephone Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest +import nose +import os +import sys +import time +import logging +import json +from noseplugin import OptionParser +from base import * + +class Test(unittest.TestCase): + + @classmethod + def setUpClass(cls): + c1 = Container(name='c1', image='osrg/gobgp') + c2 = Container(name='c2', image='osrg/gobgp') + pe1 = Container(name='pe1', image='osrg/gobgp') + pe2 = Container(name='pe2', image='osrg/gobgp') + p1 = Container(name='p1', image='osrg/gobgp') + p2 = Container(name='p2', image='osrg/gobgp') + + ctns = [c1, c2, pe1, pe2, p1, p2] + for c in ctns: + c.shared_volumes = [('/home/vagrant', '/root')] + [ctn.run() for ctn in ctns] + + br01 = Bridge(name='br01', subnet='192.168.10.0/24') + br01.addif(c1) + br01.addif(pe1) + + br02 = Bridge(name='br02', subnet='192.168.20.0/24') + br02.addif(c2) + br02.addif(pe2) + + br03 = Bridge(name='br03', subnet='192.168.30.0/24') + br03.addif(pe1) + br03.addif(p1) + + br04 = Bridge(name='br04', subnet='192.168.40.0/24') + br04.addif(pe2) + br04.addif(p1) + +# br05 = Bridge(name='br05', subnet='192.168.50.0/24') +# br05.addif(pe1) +# br05.addif(p2) +# +# br06 = Bridge(name='br06', subnet='192.168.60.0/24') +# br06.addif(pe2) +# br06.addif(p2) + + [ctn.local('sysctl -w net.mpls.platform_labels=10000') for ctn in ctns] + [[ctn.local('sysctl -w net.mpls.conf.{0}.input=1'.format(i[0])) for i in ctn.ip_addrs + [['lo']]] for ctn in ctns] + + cls.ctns = {ctn.name: ctn for ctn in ctns} + + def test_01_neighbor_established(self): + ip = '/root/.ghq/git.kernel.org/pub/scm/linux/kernel/git/shemminger/iproute2/ip/ip' + self.ctns['c1'].local('{0} r add 192.168.20.0/24 dev eth0 via 192.168.10.2'.format(ip)) + self.ctns['pe1'].local('{0} r add 192.168.20.0/24 encap mpls 100 dev eth1 via inet 192.168.30.2'.format(ip)) + self.ctns['p1'].local('{0} -f mpls r add 100 as to 200 dev eth1 via inet 192.168.40.1'.format(ip)) +# self.ctns['pe2'].local('{0} -f mpls r add 200 dev eth0 via inet 192.168.20.1'.format(ip)) + self.ctns['pe2'].local('{0} -f mpls r add 200 dev lo via inet 192.168.20.2'.format(ip)) + + self.ctns['c2'].local('{0} r add 192.168.10.0/24 dev eth0 via 192.168.20.2'.format(ip)) + self.ctns['pe2'].local('{0} r add 192.168.10.0/24 encap mpls 300 dev eth1 via inet 192.168.40.2'.format(ip)) + self.ctns['p1'].local('{0} -f mpls r add 300 as to 400 dev eth0 via inet 192.168.30.1'.format(ip)) + self.ctns['pe1'].local('{0} -f mpls r add 400 dev eth0 via inet 192.168.10.1'.format(ip)) + +if __name__ == '__main__': + if os.geteuid() is not 0: + print "you are not root." + sys.exit(1) + logging.basicConfig(stream=sys.stderr) + nose.main(argv=sys.argv, addplugins=[OptionParser()], + defaultTest=sys.argv[0])