Skip to content

Commit

Permalink
Experimental: Advertise container network with BGP in ipvlan l3
Browse files Browse the repository at this point in the history
  • Loading branch information
YujiOshima committed May 24, 2016
1 parent 3f771e3 commit 4b8de9e
Show file tree
Hide file tree
Showing 9 changed files with 828 additions and 6 deletions.
15 changes: 10 additions & 5 deletions drivers/ipvlan/ipvlan.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ const (
vethLen = 7
containerVethPrefix = "eth"
vethPrefix = "veth"
ipvlanType = "ipvlan" // driver type name
modeL2 = "l2" // ipvlan mode l2 is the default
modeL3 = "l3" // ipvlan L3 mode
parentOpt = "parent" // parent interface -o parent
modeOpt = "_mode" // ipvlan mode ux opt suffix
ipvlanType = "ipvlan" // driver type name
modeL2 = "l2" // ipvlan mode l2 is the default
modeL3 = "l3" // ipvlan L3 mode
parentOpt = "parent" // parent interface -o parent
modeOpt = "_mode" // ipvlan mode ux opt suffix
bgpNeighborOpt = "bgp-neighbor" // BGP neighbor address
vrfOpt = "vrf" // BGP vrf ID
asOpt = "asnum" // BGP AS number default 65000
remoteAsOpt = "rasnum" // BGP remote AS number dafault 65000
subnetAdvertise = "subnet-advertise" // Advertise IP Subnet with BGP
)

var driverModeOpt = ipvlanType + modeOpt // mode -o ipvlan_mode
Expand Down
13 changes: 13 additions & 0 deletions drivers/ipvlan/ipvlan_joinleave.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo,
if err := jinfo.AddStaticRoute(defaultRoute.Destination, defaultRoute.RouteType, defaultRoute.NextHop); err != nil {
return fmt.Errorf("failed to set an ipvlan l3 mode ipv4 default gateway: %v", err)
}
if n.config.SubnetAdvertise == "" {
//Advertise container route as /32 route
advIP := &net.IPNet{IP: ep.addr.IP, Mask: net.IPv4Mask(255, 255, 255, 255)}
err = routemanager.AdvertiseNewRoute(advIP.String(), n.config.VrfID)
if err != nil {
return err
}
}
logrus.Debugf("Ipvlan Endpoint Joined with IPv4_Addr: %s, Ipvlan_Mode: %s, Parent: %s",
ep.addr.IP.String(), n.config.IpvlanMode, n.config.Parent)
// If the endpoint has a v6 address, set a v6 default route
Expand Down Expand Up @@ -130,6 +138,11 @@ func (d *driver) Leave(nid, eid string) error {
if err != nil {
return err
}
if network.config.IpvlanMode == modeL3 && network.config.SubnetAdvertise == "" {
//Withdraw container route as /32 route
witdIP := &net.IPNet{IP: endpoint.addr.IP, Mask: net.IPv4Mask(255, 255, 255, 255)}
err = routemanager.WithdrawRoute(witdIP.String(), network.config.VrfID)
}
if endpoint == nil {
return fmt.Errorf("could not find endpoint with id %s", eid)
}
Expand Down
67 changes: 66 additions & 1 deletion drivers/ipvlan/ipvlan_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package ipvlan

import (
"fmt"

"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/stringid"
Expand All @@ -11,6 +10,7 @@ import (
"github.com/docker/libnetwork/options"
"github.com/docker/libnetwork/osl"
"github.com/docker/libnetwork/types"
"strconv"
)

// CreateNetwork the network for the specified driver type
Expand Down Expand Up @@ -113,6 +113,34 @@ func (d *driver) createNetwork(config *configuration) error {
endpoints: endpointTable{},
config: config,
}
if config.IpvlanMode == modeL3 {
if routemanager == nil {
InitRouteMonitering(config.ASnum, config.RemoteASnum)
}
err := routemanager.CreateVrfNetwork(config.Parent, config.VrfID)
if err != nil {
return err
}
if config.BgpNeighbor != "" {
routemanager.DiscoverNew(false, config.BgpNeighbor)
}
if config.SubnetAdvertise != "" {
if config.Ipv4Subnets != nil {
for _, subnet := range config.Ipv4Subnets {
err := routemanager.AdvertiseNewRoute(subnet.SubnetIP, config.VrfID)
if err != nil {
return err
}
}
for _, subnet := range config.Ipv6Subnets {
err := routemanager.AdvertiseNewRoute(subnet.SubnetIP, config.VrfID)
if err != nil {
return err
}
}
}
}
}
// add the *network
d.addNetwork(n)

Expand Down Expand Up @@ -147,6 +175,23 @@ func (d *driver) DeleteNetwork(nid string) error {
}
}
}
if n.config.SubnetAdvertise != "" {
//Advertise container network subnet
if n.config.Ipv4Subnets != nil {
for _, subnet := range n.config.Ipv4Subnets {
err := routemanager.WithdrawRoute(subnet.SubnetIP, n.config.VrfID)
if err != nil {
return err
}
}
for _, subnet := range n.config.Ipv6Subnets {
err := routemanager.WithdrawRoute(subnet.SubnetIP, n.config.VrfID)
if err != nil {
return err
}
}
}
}
// delete the *network
d.deleteNetwork(nid)
// delete the network record from persistent cache
Expand Down Expand Up @@ -211,6 +256,26 @@ func (config *configuration) fromOptions(labels map[string]string) error {
case driverModeOpt:
// parse driver option '-o ipvlan_mode'
config.IpvlanMode = value
case bgpNeighborOpt:
// parse driver option '-o bgp-neighbor'
config.BgpNeighbor = value
case vrfOpt:
// parse driver option '-o vrf'
_, err := strconv.Atoi(value)
if err != nil {
logrus.Errorf("vrf ID must be numeral")
return err
}
config.VrfID = value
case asOpt:
// parse driver options '-o asnum'
config.ASnum = value
case remoteAsOpt:
// parse driver options '-o rasnum'
config.RemoteASnum = value
case subnetAdvertise:
// set driver options '-o subnet-advertise'
config.SubnetAdvertise = "True"
}
}
return nil
Expand Down
5 changes: 5 additions & 0 deletions drivers/ipvlan/ipvlan_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ type configuration struct {
CreatedSlaveLink bool
Ipv4Subnets []*ipv4Subnet
Ipv6Subnets []*ipv6Subnet
BgpNeighbor string
VrfID string
ASnum string
RemoteASnum string
SubnetAdvertise string
}

type ipv4Subnet struct {
Expand Down
122 changes: 122 additions & 0 deletions drivers/ipvlan/l3_del_routes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package ipvlan

import (
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/vishvananda/netlink"
"net"
)

// Cleanup links with netlink syscalls with a scope of:
// RT_SCOPE_LINK = 0xfd (253)
// RT_SCOPE_UNIVERSE = 0x0 (0)
func cleanExistingRoutes(ifaceStr string) error {
iface, err := netlink.LinkByName(ifaceStr)
ipvlanParentIface, err := netlink.LinkByName(ifaceStr)
if err != nil {
log.Errorf("Error occoured finding the linux link [ %s ] from netlink: %s", ipvlanParentIface.Attrs().Name, err)
return err
}
routes, err := netlink.RouteList(iface, netlink.FAMILY_V4)
if err != nil {
log.Errorf("Unable to retreive netlink routes: %s", err)
return err
}
ifaceIP, err := getIfaceIP(ifaceStr)
if err != nil {
log.Errorf("Unable to retreive a usable IP via ethernet interface: %s", ifaceStr)
return err
}
for _, route := range routes {
if route.Dst == nil {
log.Debugf("Ignoring route [ %v ] Dst is nil", route)
continue
}
if netOverlaps(ifaceIP, route.Dst) == true {
log.Debugf("Ignoring route [ %v ] as it is associated to the [ %s ] interface", ifaceIP, ifaceStr)
} else if route.Scope == 0x0 || route.Scope == 0xfd {
// Remove link and universal routes from the docker host ipvlan interface
log.Debugf("Cleaning static route cache for the destination: [ %s ]", route.Dst)
err := delRoute(route, ipvlanParentIface)
if err != nil {
log.Errorf("Error deleting static route cache for Destination: [ %s ] and Nexthop [ %s ] Error: %s", route.Dst, route.Gw, err)
}
}
}
return nil
}

func verifyRoute(bgpRoute *net.IPNet) {
networks, err := netlink.RouteList(nil, netlink.FAMILY_V4)
if err != nil {
return
}
for _, network := range networks {
if network.Dst != nil && netOverlaps(bgpRoute, network.Dst) {
log.Errorf("The network [ %v ] learned via BGP conflicts with an existing route on this host [ %v ]", bgpRoute, network.Dst)
return
}
}
return
}

func netOverlaps(netX *net.IPNet, netY *net.IPNet) bool {
if firstIP, _ := networkRange(netX); netY.Contains(firstIP) {
return true
}
if firstIP, _ := networkRange(netY); netX.Contains(firstIP) {
return true
}
return false
}

func networkRange(network *net.IPNet) (net.IP, net.IP) {
var (
netIP = network.IP.To4()
firstIP = netIP.Mask(network.Mask)
lastIP = net.IPv4(0, 0, 0, 0).To4()
)

for i := 0; i < len(lastIP); i++ {
lastIP[i] = netIP[i] | ^network.Mask[i]
}
return firstIP, lastIP
}

func getIfaceIP(name string) (*net.IPNet, error) {
iface, err := netlink.LinkByName(name)
if err != nil {
return nil, err
}
addrs, err := netlink.AddrList(iface, netlink.FAMILY_V4)
if err != nil {
return nil, err
}
if len(addrs) == 0 {
return nil, fmt.Errorf("Interface %v has no IP addresses", name)
}
if len(addrs) > 1 {
log.Debugf("Interface %v has more than 1 IPv4 address. Default is %v\n", name, addrs[0].IP)
}
return addrs[0].IPNet, nil
}

// delRoute deletes any netlink route
func delRoute(route netlink.Route, iface netlink.Link) error {
return netlink.RouteDel(&netlink.Route{
Scope: route.Scope,
LinkIndex: iface.Attrs().Index,
Dst: route.Dst,
Gw: route.Gw,
})
}

// delRemoteRoute deletes a host-scoped route to a device.
func delRemoteRoute(neighborNetwork *net.IPNet, nextHop net.IP, iface netlink.Link) error {
return netlink.RouteDel(&netlink.Route{
Scope: netlink.SCOPE_UNIVERSE,
LinkIndex: iface.Attrs().Index,
Dst: neighborNetwork,
Gw: nextHop,
})
}
27 changes: 27 additions & 0 deletions drivers/ipvlan/l3_route_common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ipvlan

import (
"net"
)

type ribCache struct {
BgpTable map[string]*ribLocal
}

// Unmarshalled BGP update binding for simplicity
type ribLocal struct {
BgpPrefix *net.IPNet
OriginatorIP net.IP
NextHop net.IP
Age int
Best bool
IsWithdraw bool
IsHostRoute bool
IsLocal bool
AsPath string
RT string
}

type ribTest struct {
BgpTable map[string]*ribLocal
}
21 changes: 21 additions & 0 deletions drivers/ipvlan/l3_route_interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ipvlan

var routemanager routingInterface

type host struct {
isself bool
Address string
}

type routingInterface interface {
CreateVrfNetwork(ParentIface string, vrfID string) error
AdvertiseNewRoute(localPrefix string, vrfID string) error
WithdrawRoute(localPrefix string, vrfID string) error
DiscoverNew(isself bool, Address string) error
DiscoverDelete(isself bool, Address string) error
}

// InitRouteMonitering initialize and start maniternig routing table of host
func InitRouteMonitering(as string, ras string) {
routemanager = NewBgpRouteManager(as, ras)
}
Loading

0 comments on commit 4b8de9e

Please sign in to comment.