Skip to content
Snippets Groups Projects
Commit e1226a26 authored by cedi's avatar cedi
Browse files

Implement Netlink

With this commit i'm introducing the netlink protocol to read routes
from the linux kernel routing table to the locRIB.
This also adds the functionality to insert new routes to the linux
kernel routing stack which where learned by other protocol (like e.g.
BGP).

This is done by the protocols/netlink/netlink.go object.
The netlink.go file acts as an facade around the netlink_reader.go and
netlink_writer.go.

netlink_reader itself reads in a defined reding interval new routes from
the kernel and propagates new routes to all subscribed clients.
The netlink reader can also be filtered using the same filter-api as in
adjRibIn/adjRibOut for BGP.
To read new routes to the locRIB you have to register the locRIB to the
netlink_reader using the ClientManager interface, just like you would
register the locRIB to the adjRibIn.

netlink_writer must be subscribed to the locRIB in order to write the
routes from locRIB into the kernel.
This useses the ClientManager interface, just like you would register
the adjRibOut to the locRIB.
netlink_writer itself currently does not support filters, since all
routes form the locRIB should be written to the kernel.
The filter functionality could be easily added here as well.

The netlink_writer currently does not support ecmp. This is subject of
an other commit.

The current implementation could be considered as work in progress, but
the code is mature enough to start a review on it.
parent 12829d84
No related branches found
No related tags found
No related merge requests found
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
load("@bazel_gazelle//:def.bzl", "gazelle")
# gazelle:prefix github.com/bio-routing/bio-rd
......@@ -6,3 +7,29 @@ gazelle(
external = "vendored",
prefix = "github.com/bio-routing/bio-rd",
)
go_library(
name = "go_default_library",
srcs = [
"main.go",
"main_ipv4.go",
],
importpath = "github.com/bio-routing/bio-rd",
visibility = ["//visibility:private"],
deps = [
"//config:go_default_library",
"//net:go_default_library",
"//protocols/bgp/server:go_default_library",
"//protocols/netlink:go_default_library",
"//routingtable:go_default_library",
"//routingtable/filter:go_default_library",
"//routingtable/locRIB:go_default_library",
"//vendor/github.com/sirupsen/logrus:go_default_library",
],
)
go_binary(
name = "bio-rd",
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)
......@@ -52,7 +52,7 @@
"gps/paths",
"gps/pkgtree",
"gps/verify",
"internal/fs"
"internal/fs",
]
revision = "224a564abe296670b692fe08bb63a3e4c4ad7978"
version = "v0.5.0"
......@@ -129,6 +129,21 @@
packages = ["convert"]
revision = "c05b18bd57ea723faad165126ec38ae661bc1df6"
[[projects]]
name = "github.com/vishvananda/netlink"
packages = [
".",
"nl",
]
revision = "a2ad57a690f3caf3015351d2d6e1c0b95c349752"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/vishvananda/netns"
packages = ["."]
revision = "13995c7128ccc8e51e9a6bd2b551020a27180abd"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
......@@ -152,7 +167,7 @@
name = "golang.org/x/sys"
packages = [
"unix",
"windows"
"windows",
]
revision = "4497e2df6f9e69048a54498c7affbbec3294ad47"
......@@ -165,6 +180,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "ec525bd690a1530d850c09d334bc4cb35b167cd5f308ad5d30be88974d3242d7"
inputs-digest = "b06675ba943a08e455d38c9e661639629eca92a2747f433e91e8c772edb644c1"
solver-name = "gps-cdcl"
solver-version = 1
Makefile 0 → 100644
NAME=bio-rd
all: test
$(NAME): gazelle
bazel build //:bio-rd
gazelle:
bazel run //:gazelle -- update
test: $(NAME)
bazel test //...
vendor:
bazel build //vendor/github.com/golang/dep/cmd/dep
bazel-bin/vendor/github.com/golang/dep/cmd/dep/linux_amd64_stripped/dep use
# hack: dep of dep gives us these, and it breaks gazelle
rm -rf vendor/github.com/golang/dep/cmd/dep/testdata
rm -rf vendor/github.com/golang/dep/internal/fs/testdata/symlinks/dir-symlink
clean:
bazel clean
rm $(NAME)
.PHONY: $(NAME) gazelle clean
......@@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"netlink.go",
"peer.go",
"server.go",
],
......
package config
import (
"time"
"github.com/bio-routing/bio-rd/routingtable/filter"
)
const (
RtLocal int = 255
RtMain int = 254
RtDefault int = 253
RtUnspec int = 0
)
type Netlink struct {
HoldTime time.Duration
UpdateInterval time.Duration
RoutingTable int
ImportFilter *filter.Filter // Which routes are imported from the Kernel
ExportFilter *filter.Filter // Which routes are exportet to the Kernel
}
main.go 0 → 100644
package main
import (
"net"
"os"
"time"
"github.com/bio-routing/bio-rd/config"
"github.com/bio-routing/bio-rd/protocols/bgp/server"
"github.com/bio-routing/bio-rd/protocols/netlink"
"github.com/bio-routing/bio-rd/routingtable/locRIB"
log "github.com/sirupsen/logrus"
bnet "github.com/bio-routing/bio-rd/net"
)
func strAddr(s string) uint32 {
ret, _ := bnet.StrToAddr(s)
return ret
}
func main() {
log.SetLevel(log.DebugLevel)
f, err := os.OpenFile("/var/log/bio-rd.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("error opening file: %v", err)
}
defer f.Close()
log.SetOutput(f)
log.Info("bio-routing started...\n")
cfg := &config.Global{
Listen: true,
LocalAddressList: []net.IP{
net.IPv4(169, 254, 0, 2),
},
}
rib := locRIB.New()
b := server.NewBgpServer()
startBGPServer(b, rib, cfg)
// Netlink communication
n := proto_netlink.NewNetlinkServer(&config.Netlink{
HoldTime: time.Second * 15,
UpdateInterval: time.Second * 15,
RoutingTable: config.RtMain,
}, rib)
n.Start()
go func() {
for {
log.Debugf("LocRIB count: %d", rib.Count())
log.Debugf(rib.Print())
time.Sleep(time.Second * 10)
}
}()
select {}
}
package main
import (
"time"
"github.com/bio-routing/bio-rd/routingtable"
"github.com/bio-routing/bio-rd/routingtable/locRIB"
"github.com/bio-routing/bio-rd/config"
"github.com/bio-routing/bio-rd/protocols/bgp/server"
"github.com/bio-routing/bio-rd/routingtable/filter"
log "github.com/sirupsen/logrus"
bnet "github.com/bio-routing/bio-rd/net"
)
func startBGPServer(b server.BGPServer, rib *locRIB.LocRIB, cfg *config.Global) {
err := b.Start(cfg)
if err != nil {
log.Fatalf("Unable to start BGP server: %v", err)
}
b.AddPeer(config.Peer{
AdminEnabled: true,
LocalAS: 65200,
PeerAS: 65100,
PeerAddress: bnet.IPv4FromOctets(169, 254, 0, 1),
LocalAddress: bnet.IPv4FromOctets(169, 254, 0, 2),
ReconnectInterval: time.Second * 20,
HoldTime: time.Second * 20,
KeepAlive: time.Second * 20,
Passive: false,
RouterID: b.RouterID(),
//AddPathSend: routingtable.ClientOptions{
// MaxPaths: 10,
//},
//RouteServerClient: true,
IPv4: &config.AddressFamilyConfig{
RIB: rib,
ImportFilter: filter.NewAcceptAllFilter(),
ExportFilter: filter.NewAcceptAllFilter(),
AddPathSend: routingtable.ClientOptions{
MaxPaths: 10,
},
AddPathRecv: true,
},
})
}
// +build ipv6
package main
import (
"net"
"time"
"github.com/bio-routing/bio-rd/config"
"github.com/bio-routing/bio-rd/protocols/bgp/server"
"github.com/bio-routing/bio-rd/routingtable"
"github.com/bio-routing/bio-rd/routingtable/filter"
"github.com/bio-routing/bio-rd/routingtable/locRIB"
"github.com/sirupsen/logrus"
bnet "github.com/bio-routing/bio-rd/net"
)
func startServer(b server.BGPServer, rib *locRIB.LocRIB) {
err := b.Start(&config.Global{
Listen: true,
LocalAddressList: []net.IP{
net.IP{0x20, 0x01, 0x6, 0x78, 0x1, 0xe0, 0, 0, 0, 0, 0, 0, 0, 0, 0xca, 0xfe},
},
})
if err != nil {
logrus.Fatalf("Unable to start BGP server: %v", err)
}
b.AddPeer(config.Peer{
AdminEnabled: true,
LocalAS: 65200,
PeerAS: 202739,
PeerAddress: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 1),
LocalAddress: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0xcafe),
ReconnectInterval: time.Second * 15,
HoldTime: time.Second * 90,
KeepAlive: time.Second * 30,
Passive: true,
RouterID: b.RouterID(),
IPv6: &config.AddressFamilyConfig{
RIB: rib,
ImportFilter: filter.NewAcceptAllFilter(),
ExportFilter: filter.NewDrainFilter(),
AddPathSend: routingtable.ClientOptions{
BestOnly: true,
},
},
})
b.AddPeer(config.Peer{
AdminEnabled: true,
LocalAS: 65200,
PeerAS: 65400,
PeerAddress: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0xcafe, 0, 0, 0, 5),
LocalAddress: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0xcafe),
ReconnectInterval: time.Second * 15,
HoldTime: time.Second * 90,
KeepAlive: time.Second * 30,
Passive: true,
RouterID: b.RouterID(),
IPv6: &config.AddressFamilyConfig{
RIB: rib,
ImportFilter: filter.NewDrainFilter(),
ExportFilter: filter.NewAcceptAllFilter(),
AddPathSend: routingtable.ClientOptions{
BestOnly: true,
},
},
})
}
......@@ -79,11 +79,13 @@ func IPFromString(str string) (IP, error) {
// Equal returns true if ip is equal to other
func (ip IP) Equal(other IP) bool {
return ip == other
return ip.higher == other.higher &&
ip.lower == other.lower &&
ip.ipVersion == other.ipVersion
}
// Compare compares two IP addresses (returns 0 if equal, -1 if `ip` is smaller than `other`, 1 if `ip` is greater than `other`)
func (ip IP) Compare(other IP) int {
func (ip IP) Compare(other IP) int8 {
if ip.Equal(other) {
return 0
}
......
......@@ -13,7 +13,7 @@ func TestCompare(t *testing.T) {
name string
ip IP
other IP
expected int
expected int8
}{
{
name: "equal",
......
......@@ -3,6 +3,7 @@ package net
import (
"fmt"
"math"
"net"
"strconv"
"strings"
)
......@@ -21,6 +22,16 @@ func NewPfx(addr IP, pfxlen uint8) Prefix {
}
}
func NewPfxFromIPNet(ipNet *net.IPNet) Prefix {
ones, _ := ipNet.Mask.Size()
ip, _ := IPFromBytes(ipNet.IP)
return Prefix{
addr: ip,
pfxlen: uint8(ones),
}
}
// StrToAddr converts an IP address string to it's uint32 representation
func StrToAddr(x string) (uint32, error) {
parts := strings.Split(x, ".")
......@@ -60,6 +71,20 @@ func (pfx Prefix) String() string {
return fmt.Sprintf("%s/%d", pfx.addr, pfx.pfxlen)
}
func (pfx Prefix) GetIPNet() *net.IPNet {
var dstNetwork net.IPNet
dstNetwork.IP = pfx.Addr().Bytes()
pfxLen := int(pfx.Pfxlen())
if pfx.Addr().IsIPv4() {
dstNetwork.Mask = net.CIDRMask(pfxLen, 32)
} else {
dstNetwork.Mask = net.CIDRMask(pfxLen, 128)
}
return &dstNetwork
}
// Contains checks if x is a subnet of or equal to pfx
func (pfx Prefix) Contains(x Prefix) bool {
if x.pfxlen <= pfx.pfxlen {
......@@ -94,7 +119,7 @@ func (pfx Prefix) containsIPv6(x Prefix) bool {
// Equal checks if pfx and x are equal
func (pfx Prefix) Equal(x Prefix) bool {
return pfx == x
return pfx.addr.Equal(x.addr) && pfx.pfxlen == x.pfxlen
}
// GetSupernet gets the next common supernet of pfx and x
......
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"netlink.go",
"netlink_reader.go",
"netlink_writer.go",
],
importpath = "github.com/bio-routing/bio-rd/protocols/netlink",
visibility = ["//visibility:public"],
deps = [
"//config:go_default_library",
"//net:go_default_library",
"//route:go_default_library",
"//routingtable:go_default_library",
"//routingtable/filter:go_default_library",
"//routingtable/locRIB:go_default_library",
"//vendor/github.com/sirupsen/logrus:go_default_library",
"//vendor/github.com/vishvananda/netlink:go_default_library",
],
)
package proto_netlink
import (
"github.com/bio-routing/bio-rd/config"
"github.com/bio-routing/bio-rd/routingtable"
"github.com/bio-routing/bio-rd/routingtable/locRIB"
)
type NetlinkServer struct {
locRib *locRIB.LocRIB
writer *NetlinkWriter
reader *NetlinkReader
}
func NewNetlinkServer(options *config.Netlink, locRib *locRIB.LocRIB) *NetlinkServer {
n := &NetlinkServer{
locRib: locRib,
writer: NewNetlinkWriter(options),
reader: NewNetlinkReader(options),
}
return n
}
func (n *NetlinkServer) Start() {
// connect all RIBs
options := routingtable.ClientOptions{
BestOnly: false,
EcmpOnly: false,
MaxPaths: ^uint(0), // max int
}
// 1. from locRib to Kernel
n.locRib.ClientManager.RegisterWithOptions(n.writer, options)
// 2. vom Kernel to locRib
n.reader.ClientManager.RegisterWithOptions(n.locRib, options)
// Listn for new routes from kernel
go n.reader.Read()
}
package proto_netlink
import (
"fmt"
"sync"
"time"
"github.com/bio-routing/bio-rd/config"
bnet "github.com/bio-routing/bio-rd/net"
"github.com/bio-routing/bio-rd/route"
"github.com/bio-routing/bio-rd/routingtable"
"github.com/bio-routing/bio-rd/routingtable/filter"
log "github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
)
type NetlinkReader struct {
options *config.Netlink
routingtable.ClientManager
filter *filter.Filter
mu sync.RWMutex
routes []netlink.Route
}
func NewNetlinkReader(options *config.Netlink) *NetlinkReader {
nr := &NetlinkReader{
options: options,
filter: options.ImportFilter,
}
nr.ClientManager = routingtable.NewClientManager(nr)
return nr
}
// Read routes from kernel
func (nr *NetlinkReader) Read() {
log.WithField("rt_table", nr.options.RoutingTable).Info("Started netlink server")
// Start fetching the kernel routes after the hold time
time.Sleep(nr.options.HoldTime)
for {
// Family doesn't matter. I only filter by the rt_table here
routes, err := netlink.RouteListFiltered(4, &netlink.Route{Table: nr.options.RoutingTable}, netlink.RT_FILTER_TABLE)
if err != nil {
log.WithError(err).Panic("Failed to read routes from kernel")
}
nr.propagateChanges(routes)
nr.mu.Lock()
nr.routes = routes
log.Debugf("NetlinkRouteDiff: %d", len(route.NetlinkRouteDiff(nr.routes, routes)))
nr.mu.Unlock()
time.Sleep(nr.options.UpdateInterval)
}
}
// create a path from a route
func createPathFromRoute(r *netlink.Route) (*route.Path, error) {
nlPath, err := route.NewNlPathFromRoute(r, true)
if err != nil {
return nil, fmt.Errorf("Error while creating path object from route object", err)
}
return &route.Path{
Type: route.NetlinkPathType,
NetlinkPath: nlPath,
}, nil
}
// propagate changes to all subscribed clients
func (nr *NetlinkReader) propagateChanges(routes []netlink.Route) {
nr.removePathsFromClients(routes)
nr.addPathsToClients(routes)
}
// Add given paths to clients
func (nr *NetlinkReader) addPathsToClients(routes []netlink.Route) {
for _, client := range nr.ClientManager.Clients() {
// only advertise changed routes
nr.mu.RLock()
advertise := route.NetlinkRouteDiff(routes, nr.routes)
nr.mu.RUnlock()
for _, r := range advertise {
// Is it a BIO-Written route? if so, skip it, dont advertise it
if r.Protocol == route.ProtoBio {
log.WithFields(routeLogFields(r)).Debug("Skipping bio route")
continue
}
// create pfx and path from route
pfx := bnet.NewPfxFromIPNet(r.Dst)
path, err := createPathFromRoute(&r)
if err != nil {
log.WithError(err).Error("Unable to create path")
continue
}
// Apply filter (if existing)
if nr.filter != nil {
var reject bool
// TODO: Implement filter that cann handle netlinkRoute objects
path, reject = nr.filter.ProcessTerms(pfx, path)
if reject {
log.Debug("Skipping route due to filter")
continue
}
}
log.WithFields(log.Fields{
"pfx": pfx,
"path": path,
}).Debug("NetlinkReader - client.AddPath")
client.AddPath(pfx, path)
}
}
}
// Remove given paths from clients
func (nr *NetlinkReader) removePathsFromClients(routes []netlink.Route) {
for _, client := range nr.ClientManager.Clients() {
// If there where no routes yet, just skip this funktion. There's nothing to delete
nr.mu.RLock()
if len(nr.routes) == 0 {
nr.mu.RUnlock()
break
}
// only withdraw changed routes
withdraw := route.NetlinkRouteDiff(nr.routes, routes)
nr.mu.RUnlock()
for _, r := range withdraw {
// Is it a BIO-Written route? if so, skip it, dont advertise it
if r.Protocol == route.ProtoBio {
continue
}
// create pfx and path from route
pfx := bnet.NewPfxFromIPNet(r.Dst)
path, err := createPathFromRoute(&r)
if err != nil {
log.WithError(err).Error("Unable to create path")
continue
}
// Apply filter (if existing)
if nr.filter != nil {
var reject bool
// TODO: Implement filter that cann handle netlinkRoute objects
path, reject = nr.filter.ProcessTerms(pfx, path)
if reject {
continue
}
}
log.WithFields(log.Fields{
"pfx": pfx,
"path": path,
}).Debug("NetlinkReader - client.RemovePath")
client.RemovePath(pfx, path)
}
}
}
func routeLogFields(route netlink.Route) log.Fields {
return log.Fields{
"LinkIndex": route.LinkIndex,
"ILinkIndex": route.ILinkIndex,
"Scope": route.Scope,
"Dst": route.Dst,
"Src": route.Src,
"Gw": route.Gw,
"MultiPath": route.MultiPath,
"Protocol": route.Protocol,
"Priority": route.Priority,
"Table": route.Table,
"Type": route.Type,
"Tos": route.Tos,
"Flags": route.Flags,
"MPLSDst": route.MPLSDst,
"NewDst": route.NewDst,
"Encap": route.Encap,
"MTU": route.MTU,
"AdvMSS": route.AdvMSS,
}
}
// Not supported
func (nr *NetlinkReader) AddPath(bnet.Prefix, *route.Path) error {
return fmt.Errorf("Not supported")
}
// Not supported
func (nr *NetlinkReader) RemovePath(bnet.Prefix, *route.Path) bool {
return false
}
// Not supported
func (nr *NetlinkReader) UpdateNewClient(routingtable.RouteTableClient) error {
return fmt.Errorf("Not supported")
}
func (nr *NetlinkReader) Register(routingtable.RouteTableClient) {
}
func (nr *NetlinkReader) RegisterWithOptions(routingtable.RouteTableClient, routingtable.ClientOptions) {
}
func (nr *NetlinkReader) Unregister(routingtable.RouteTableClient) {
}
func (nr *NetlinkReader) RouteCount() int64 {
nr.mu.RLock()
defer nr.mu.RUnlock()
return int64(len(nr.routes))
}
package proto_netlink
import (
"fmt"
"sync"
"github.com/bio-routing/bio-rd/config"
bnet "github.com/bio-routing/bio-rd/net"
"github.com/bio-routing/bio-rd/route"
"github.com/bio-routing/bio-rd/routingtable"
"github.com/bio-routing/bio-rd/routingtable/filter"
log "github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
)
type NetlinkWriter struct {
options *config.Netlink
filter *filter.Filter
// Routingtable for buffering, to ensure no double writes (a.k.a rtnetlink: file exists)
mu sync.RWMutex
pt map[bnet.Prefix][]*route.Path
}
func NewNetlinkWriter(options *config.Netlink) *NetlinkWriter {
return &NetlinkWriter{
options: options,
filter: options.ExportFilter,
pt: make(map[bnet.Prefix][]*route.Path),
}
}
// Not supported
func (nw *NetlinkWriter) UpdateNewClient(routingtable.RouteTableClient) error {
return fmt.Errorf("Not supported")
}
// Not supported
func (nw *NetlinkWriter) Register(routingtable.RouteTableClient) {
log.Error("Not supported")
}
// Not supported
func (nw *NetlinkWriter) RegisterWithOptions(routingtable.RouteTableClient, routingtable.ClientOptions) {
log.Error("Not supported")
}
// Not supported
func (nw *NetlinkWriter) Unregister(routingtable.RouteTableClient) {
log.Error("Not supported")
}
// RouteCount returns the number of stored routes
func (nw *NetlinkWriter) RouteCount() int64 {
nw.mu.RLock()
defer nw.mu.RUnlock()
return int64(len(nw.pt))
}
// Add a path to the Kernel using netlink
// This function is triggered by the loc_rib, cause we are subscribed as
// client in the loc_rib
func (nw *NetlinkWriter) AddPath(pfx bnet.Prefix, path *route.Path) error {
// check if for this prefix already a route is existing
existingPaths, ok := nw.pt[pfx]
// if no route exists, add that route
if existingPaths == nil || !ok {
paths := make([]*route.Path, 1)
paths = append(paths, path)
nw.pt[pfx] = paths
// add the route to kernel
return nw.addKernel(pfx, path)
}
// if the new path is already in, don't do anything
for _, ePath := range existingPaths {
if ePath.Equal(path) {
return nil
}
}
existingPaths = append(existingPaths, path)
nw.pt[pfx] = existingPaths
// now add to netlink
return nw.addKernel(pfx, path)
}
// Remove a path from the Kernel using netlink
// This function is triggered by the loc_rib, cause we are subscribed as
// client in the loc_rib
func (nw *NetlinkWriter) RemovePath(pfx bnet.Prefix, path *route.Path) bool {
// check if for this prefix already a route is existing
existingPaths, ok := nw.pt[pfx]
// if no route exists, nothing to do
if existingPaths == nil || !ok {
return true
}
// if the new path is already in: remove
removeIdx := 0
remove := false
for idx, ePath := range existingPaths {
if ePath.Equal(path) {
removeIdx = idx
remove = true
err := nw.removeKernel(pfx, path)
if err != nil {
log.WithError(err).Errorf("Error while removing path %s for prefix %s", path.String(), pfx.String())
remove = false
}
break
}
}
if remove {
existingPaths = append(existingPaths[:removeIdx], existingPaths[removeIdx+1:]...)
nw.pt[pfx] = existingPaths
}
return true
}
// Add pfx/path to kernel
func (nw *NetlinkWriter) addKernel(pfx bnet.Prefix, path *route.Path) error {
route, err := nw.createRoute(pfx, path)
if err != nil {
log.Errorf("Error while creating route: %v", err)
return fmt.Errorf("Error while creating route: %v", err)
}
log.WithFields(log.Fields{
"Prefix": pfx.String(),
"Table": route.Table,
}).Debug("AddPath to netlink")
err = netlink.RouteAdd(route)
if err != nil {
log.Errorf("Error while adding route: %v", err)
return fmt.Errorf("Error while adding route: %v", err)
}
return nil
}
// remove pfx/path from kernel
func (nw *NetlinkWriter) removeKernel(pfx bnet.Prefix, path *route.Path) error {
log.WithFields(log.Fields{
"Prefix": pfx.String(),
}).Debug("Remove from netlink")
route, err := nw.createRoute(pfx, path)
if err != nil {
return fmt.Errorf("Error while creating route: %v", err)
}
err = netlink.RouteDel(route)
if err != nil {
return fmt.Errorf("Error while removing route: %v", err)
}
return nil
}
// create a route from a prefix and a path
func (nw *NetlinkWriter) createRoute(pfx bnet.Prefix, path *route.Path) (*netlink.Route, error) {
if path.Type != route.NetlinkPathType {
}
switch path.Type {
case route.NetlinkPathType:
return nw.createRouteFromNetlink(pfx, path)
case route.BGPPathType:
return nw.createRouteFromBGPPath(pfx, path)
default:
return nil, fmt.Errorf("PathType %d is not supported for adding to netlink", path.Type)
}
}
func (nw *NetlinkWriter) createRouteFromNetlink(pfx bnet.Prefix, path *route.Path) (*netlink.Route, error) {
nlPath := path.NetlinkPath
log.WithFields(log.Fields{
"Dst": nlPath.Dst,
"Src": nlPath.Src,
"NextHop": nlPath.NextHop,
"Priority": nlPath.Priority,
"Protocol": nlPath.Protocol,
"Type": nlPath.Type,
"Table": nw.options.RoutingTable,
}).Debug("created route")
return &netlink.Route{
Dst: nlPath.Dst.GetIPNet(),
Src: nlPath.Src.Bytes(),
Gw: nlPath.NextHop.Bytes(),
Priority: nlPath.Priority,
Type: nlPath.Type,
Table: nw.options.RoutingTable, // config dependent
Protocol: route.ProtoBio, // fix
}, nil
}
func (nw *NetlinkWriter) createRouteFromBGPPath(pfx bnet.Prefix, path *route.Path) (*netlink.Route, error) {
bgpPath := path.BGPPath
log.WithFields(log.Fields{
"Dst": pfx,
"NextHop": bgpPath.NextHop,
"Protocol": "BGP",
"BGPIdentifier": bgpPath.BGPIdentifier,
"Table": nw.options.RoutingTable,
}).Debug("created route")
return &netlink.Route{
Dst: pfx.GetIPNet(),
Gw: bgpPath.NextHop.Bytes(),
Table: nw.options.RoutingTable, // config dependent
Protocol: route.ProtoBio, // fix
}, nil
}
......@@ -5,6 +5,7 @@ go_library(
srcs = [
"bgp_path.go",
"bgp_path_manager.go",
"netlink_path.go",
"path.go",
"route.go",
"static.go",
......@@ -14,7 +15,9 @@ go_library(
deps = [
"//net:go_default_library",
"//protocols/bgp/types:go_default_library",
"//vendor/github.com/sirupsen/logrus:go_default_library",
"//vendor/github.com/taktv6/tflow2/convert:go_default_library",
"//vendor/github.com/vishvananda/netlink:go_default_library",
],
)
......@@ -31,5 +34,6 @@ go_test(
"//net:go_default_library",
"//protocols/bgp/types:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/github.com/vishvananda/netlink:go_default_library",
],
)
......@@ -235,6 +235,45 @@ func (b *BGPPath) better(c *BGPPath) bool {
}
// Print all known information about a route in human readable form
func (b *BGPPath) String() string {
origin := ""
switch b.Origin {
case 0:
origin = "Incomplete"
case 1:
origin = "EGP"
case 2:
origin = "IGP"
}
bgpType := "internal"
if b.EBGP {
bgpType = "external"
}
ret := fmt.Sprintf("Local Pref: %d, ", b.LocalPref)
ret += fmt.Sprintf("Origin: %s, ", origin)
ret += fmt.Sprintf("AS Path: %v, ", b.ASPath)
ret += fmt.Sprintf("BGP type: %s, ", bgpType)
ret += fmt.Sprintf("NEXT HOP: %s, ", b.NextHop)
ret += fmt.Sprintf("MED: %d, ", b.MED)
ret += fmt.Sprintf("Path ID: %d, ", b.PathIdentifier)
ret += fmt.Sprintf("Source: %s, ", b.Source)
ret += fmt.Sprintf("Communities: %v, ", b.Communities)
ret += fmt.Sprintf("LargeCommunities: %v, ", b.LargeCommunities)
if b.OriginatorID != 0 {
oid := convert.Uint32Byte(b.OriginatorID)
ret += fmt.Sprintf("OriginatorID: %d.%d.%d.%d, ", oid[0], oid[1], oid[2], oid[3])
}
if b.ClusterList != nil {
ret += fmt.Sprintf("ClusterList %s", b.ClusterListString())
}
return ret
}
// Pretty Print all known information about a route in human readable form
func (b *BGPPath) Print() string {
origin := ""
switch b.Origin {
......
package route
import (
"fmt"
bnet "github.com/bio-routing/bio-rd/net"
log "github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
)
const (
ProtoBio = 45
)
// NetlinkPath represents a path learned via Netlink of a route
type NetlinkPath struct {
Dst bnet.Prefix
Src bnet.IP
NextHop bnet.IP // GW
Priority int
Protocol int
Type int
Table int
Kernel bool // True if the route is already installed in the kernel
}
func NewNlPathFromBgpPath(p *BGPPath) *NetlinkPath {
return &NetlinkPath{
Src: p.Source,
NextHop: p.NextHop,
Protocol: ProtoBio,
Kernel: false,
}
}
func NewNlPathFromRoute(r *netlink.Route, kernel bool) (*NetlinkPath, error) {
var src bnet.IP
var dst bnet.Prefix
if r.Src == nil && r.Dst == nil {
return nil, fmt.Errorf("Cannot create NlPath, since source and destination are both nil")
}
if r.Src == nil && r.Dst != nil {
dst = bnet.NewPfxFromIPNet(r.Dst)
if dst.Addr().IsIPv4() {
src = bnet.IPv4FromOctets(0, 0, 0, 0)
} else {
src = bnet.IPv6FromBlocks(0, 0, 0, 0, 0, 0, 0, 0)
}
}
if r.Src != nil && r.Dst == nil {
src, _ = bnet.IPFromBytes(r.Src)
if src.IsIPv4() {
dst = bnet.NewPfx(bnet.IPv4FromOctets(0, 0, 0, 0), 0)
} else {
dst = bnet.NewPfx(bnet.IPv6FromBlocks(0, 0, 0, 0, 0, 0, 0, 0), 0)
}
}
if r.Src != nil && r.Dst != nil {
src, _ = bnet.IPFromBytes(r.Src)
dst = bnet.NewPfxFromIPNet(r.Dst)
}
log.Warnf("IPFromBytes: %v goes to %v", r.Src, src)
log.Warnf("IPFromBytes: %v goes to %v", r.Dst, dst)
nextHop, _ := bnet.IPFromBytes(r.Gw)
return &NetlinkPath{
Dst: dst,
Src: src,
NextHop: nextHop,
Priority: r.Priority,
Protocol: r.Protocol,
Type: r.Type,
Table: r.Table,
Kernel: kernel,
}, nil
}
// Compare returns negative if s < t, 0 if paths are equal, positive if s > t
func (s *NetlinkPath) Select(t *NetlinkPath) int8 {
if !s.Dst.Equal(t.Dst) {
return 1
}
if s.NextHop.Compare(t.NextHop) > 0 {
return -1
}
if s.NextHop.Compare(t.NextHop) < 0 {
return 1
}
if s.Src.Compare(t.Src) > 0 {
return -1
}
if s.Src.Compare(t.Src) < 0 {
return 1
}
if s.Priority < t.Priority {
return -1
}
if s.Priority > t.Priority {
return 1
}
if s.Protocol < t.Protocol {
return -1
}
if s.Protocol > t.Protocol {
return 1
}
if s.Table < t.Table {
return -1
}
if s.Table > t.Table {
return 1
}
return 0
}
// ECMP determines if path s and t are equal in terms of ECMP
func (s *NetlinkPath) ECMP(t *NetlinkPath) bool {
return true
}
func (s *NetlinkPath) Copy() *NetlinkPath {
if s == nil {
return nil
}
cp := *s
return &cp
}
// get all known information about a route in a machine readable form
func (s *NetlinkPath) String() string {
ret := fmt.Sprintf("Destination: %s, ", s.Dst.String())
ret += fmt.Sprintf("Source: %s, ", s.Src.String())
ret += fmt.Sprintf("NextHop: %s, ", s.NextHop.String())
ret += fmt.Sprintf("Priority: %d, ", s.Priority)
ret += fmt.Sprintf("Type: %d, ", s.Type)
ret += fmt.Sprintf("Table: %d", s.Table)
return ret
}
// Pretty Print all known information about a route in human readable form
func (s *NetlinkPath) Print() string {
ret := fmt.Sprintf("\t\tDestination: %s\n", s.Dst.String())
ret += fmt.Sprintf("\t\tSource: %s\n", s.Src.String())
ret += fmt.Sprintf("\t\tNextHop: %s\n", s.NextHop.String())
ret += fmt.Sprintf("\t\tPriority: %d\n", s.Priority)
ret += fmt.Sprintf("\t\tType: %d\n", s.Type)
ret += fmt.Sprintf("\t\tTable: %d\n", s.Table)
return ret
}
......@@ -2,12 +2,16 @@ package route
import (
"fmt"
"log"
bnet "github.com/bio-routing/bio-rd/net"
)
type Path struct {
Type uint8
StaticPath *StaticPath
BGPPath *BGPPath
Type uint8
StaticPath *StaticPath
BGPPath *BGPPath
NetlinkPath *NetlinkPath
}
// Select returns negative if p < q, 0 if paths are equal, positive if p > q
......@@ -35,6 +39,8 @@ func (p *Path) Select(q *Path) int8 {
return p.BGPPath.Select(q.BGPPath)
case StaticPathType:
return p.StaticPath.Select(q.StaticPath)
case NetlinkPathType:
return p.NetlinkPath.Select(q.NetlinkPath)
}
panic("Unknown path type")
......@@ -46,6 +52,8 @@ func (p *Path) ECMP(q *Path) bool {
return p.BGPPath.ECMP(q.BGPPath)
case StaticPathType:
return p.StaticPath.ECMP(q.StaticPath)
case NetlinkPathType:
return p.NetlinkPath.ECMP(q.NetlinkPath)
}
panic("Unknown path type")
......@@ -93,6 +101,19 @@ func pathsContains(needle *Path, haystack []*Path) bool {
return false
}
func (p *Path) String() string {
switch p.Type {
case StaticPathType:
return "not implemented yet"
case BGPPathType:
return p.BGPPath.String()
case NetlinkPathType:
return p.NetlinkPath.String()
default:
return "Unknown paty type. Probably not implemented yet"
}
}
func (p *Path) Print() string {
protocol := ""
switch p.Type {
......@@ -100,6 +121,8 @@ func (p *Path) Print() string {
protocol = "static"
case BGPPathType:
protocol = "BGP"
case NetlinkPathType:
protocol = "Netlink"
}
ret := fmt.Sprintf("\tProtocol: %s\n", protocol)
......@@ -108,6 +131,8 @@ func (p *Path) Print() string {
ret += "Not implemented yet"
case BGPPathType:
ret += p.BGPPath.Print()
case NetlinkPathType:
ret += p.NetlinkPath.Print()
}
return ret
......@@ -124,3 +149,18 @@ func (p *Path) Copy() *Path {
return &cp
}
func (p *Path) NextHop() bnet.IP {
switch p.Type {
case BGPPathType:
return p.BGPPath.NextHop
case StaticPathType:
return p.StaticPath.NextHop
case NetlinkPathType:
return p.NetlinkPath.NextHop
default:
log.Panic("Type %d not implemented (yet)", p.Type)
}
return bnet.IP{}
}
......@@ -6,19 +6,27 @@ import (
"sync"
"github.com/bio-routing/bio-rd/net"
"github.com/vishvananda/netlink"
)
// StaticPathType indicats a path is a static path
const StaticPathType = 1
const (
_ = iota // 0
// BGPPathType indicates a path is a BGP path
const BGPPathType = 2
// StaticPathType indicats a path is a static path
StaticPathType
// OSPFPathType indicates a path is an OSPF path
const OSPFPathType = 3
// BGPPathType indicates a path is a BGP path
BGPPathType
// ISISPathType indicates a path is an ISIS path
const ISISPathType = 4
// OSPFPathType indicates a path is an OSPF path
OSPFPathType
// ISISPathType indicates a path is an ISIS path
ISISPathType
// NetlinkPathType indicates a path is an Netlink/Kernel path
NetlinkPathType
)
// Route links a prefix to paths
type Route struct {
......@@ -191,6 +199,44 @@ func (r *Route) PathSelection() {
r.updateEqualPathCount()
}
func (r *Route) Equal(other *Route) bool {
r.mu.Lock()
defer r.mu.Unlock()
a := r.pfx.Equal(other.pfx)
b := r.ecmpPaths == other.ecmpPaths
c := true
if r.paths == nil && other.paths == nil {
c = true
return a && b && c
}
if len(r.paths) != len(other.paths) {
c = false
return a && b && c
}
for _, myP := range r.paths {
if !r.compareItemExists(myP, other.paths) {
c = false
return a && b && c
}
}
return a && b && c
}
func (r *Route) compareItemExists(needle *Path, haystack []*Path) bool {
for _, compare := range haystack {
if needle.Equal(compare) {
return true
}
}
return false
}
func (r *Route) updateEqualPathCount() {
count := uint(1)
for i := 0; i < len(r.paths)-1; i++ {
......@@ -229,3 +275,49 @@ func (r *Route) Print() string {
return ret
}
// NetlinkRouteDiff gets the list of elements contained by a but not b
func NetlinkRouteDiff(a, b []netlink.Route) []netlink.Route {
ret := make([]netlink.Route, 0)
for _, pa := range a {
if !netlinkRoutesContains(pa, b) {
ret = append(ret, pa)
}
}
return ret
}
func netlinkRoutesContains(needle netlink.Route, haystack []netlink.Route) bool {
for _, p := range haystack {
probeMaskSize, probeMaskBits := p.Dst.Mask.Size()
needleMaskSize, needleMaskBits := needle.Dst.Mask.Size()
if p.LinkIndex == needle.LinkIndex &&
p.ILinkIndex == needle.ILinkIndex &&
p.Scope == needle.Scope &&
p.Dst.IP.Equal(needle.Dst.IP) &&
probeMaskSize == needleMaskSize &&
probeMaskBits == needleMaskBits &&
p.Src.Equal(needle.Src) &&
p.Gw.Equal(needle.Gw) &&
p.Protocol == needle.Protocol &&
p.Priority == needle.Priority &&
p.Table == needle.Table &&
p.Type == needle.Type &&
p.Tos == needle.Tos &&
p.Flags == needle.Flags &&
p.MTU == needle.MTU &&
p.AdvMSS == needle.AdvMSS {
return true
}
}
return false
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment