Added MP-BGP for IPv4 over IPv6 session and config flags

Signed-off-by: Patryk Strusiewicz-Surmacki <patryk-pawel.strusiewicz-surmacki@external.telekom.de>
This commit is contained in:
Patryk Strusiewicz-Surmacki
2024-11-28 18:29:48 +01:00
parent 3b16caa38c
commit 0832a7227c
7 changed files with 310 additions and 163 deletions

View File

@@ -60,7 +60,7 @@ func main() {
fmt.Println("error: ", err)
}
ServerConn.WriteTo(buf[0:n])
ServerConn.WriteTo(buf[0:n], addr)
}
}
}

View File

@@ -7,10 +7,12 @@ import (
"strconv"
"strings"
"github.com/golang/protobuf/ptypes" //nolint
//nolint
"github.com/golang/protobuf/ptypes/any"
"github.com/kube-vip/kube-vip/pkg/vip"
api "github.com/osrg/gobgp/v3/api"
"github.com/vishvananda/netlink"
"github.com/osrg/gobgp/v3/pkg/server"
"google.golang.org/protobuf/types/known/anypb"
)
// AddPeer will add peers to the BGP configuration
@@ -41,8 +43,10 @@ func (b *Server) AddPeer(peer Peer) (err error) {
RemoteAddress: peer.Address,
RemotePort: uint32(179),
},
}
AfiSafis: []*api.AfiSafi{
if b.c.MpbgpNexthop != "" {
p.AfiSafis = []*api.AfiSafi{
{
Config: &api.AfiSafiConfig{
Family: &api.Family{
@@ -61,120 +65,51 @@ func (b *Server) AddPeer(peer Peer) (err error) {
Enabled: true,
},
},
},
}
var ipv6Address *string
if b.c.SourceIP != "" {
p.Transport.LocalAddress = b.c.SourceIP
// Resolve the local interface by SourceIP
iface, err := GetInterfaceByIP(b.c.SourceIP)
if err != nil {
return fmt.Errorf("failed to get interface by IP: %v", err)
}
// Get the non link-local IPv6 address on that interface
ipv6, err := GetNonLinkLocalIPv6(iface)
peer.setMpbgpOptions(b.c)
ipv4Address, ipv6Address, err := peer.findMpbgpAddresses(p, b.c)
if err != nil {
return fmt.Errorf("failed to get non link-local IPv6 address: %v", err)
return fmt.Errorf("failed to get MP-BGP addresses: %w", err)
}
ipv6Address = &ipv6
}
if b.c.SourceIF != "" {
p.Transport.BindInterface = b.c.SourceIF
iface, err := net.InterfaceByName(b.c.SourceIF)
if err != nil {
return fmt.Errorf("failed to get interface by name: %v", err)
mask := "128"
address := ipv4Address
family := api.Family_AFI_IP
if vip.IsIPv4(p.Conf.NeighborAddress) {
mask = "32"
address = ipv6Address
family = api.Family_AFI_IP6
}
// Get the non link-local IPv6 address on that interface
ipv6, err := GetNonLinkLocalIPv6(iface)
if err != nil {
return fmt.Errorf("failed to get non link-local IPv6 address: %v", err)
}
ipv6Address = &ipv6
}
if ipv6Address != nil {
err := b.s.AddDefinedSet(context.Background(), &api.AddDefinedSetRequest{
err = b.s.AddDefinedSet(context.Background(), &api.AddDefinedSetRequest{
DefinedSet: &api.DefinedSet{
DefinedType: api.DefinedType_NEIGHBOR,
Name: fmt.Sprintf("peer-%s", p.Conf.NeighborAddress),
List: []string{fmt.Sprintf("%s/32", p.Conf.NeighborAddress)},
List: []string{fmt.Sprintf("%s/%s", p.Conf.NeighborAddress, mask)},
},
})
if err != nil {
return fmt.Errorf("failed to add defined set: %v", err)
}
err = b.s.AddPolicy(context.Background(), &api.AddPolicyRequest{
Policy: &api.Policy{
Name: fmt.Sprintf("peer-%s", p.Conf.NeighborAddress),
Statements: []*api.Statement{
{
Conditions: &api.Conditions{
AfiSafiIn: []*api.Family{
{
Afi: api.Family_AFI_IP6,
Safi: api.Family_SAFI_UNICAST,
},
},
NeighborSet: &api.MatchSet{
Type: api.MatchSet_ANY,
Name: fmt.Sprintf("peer-%s", p.Conf.NeighborAddress),
},
},
Actions: &api.Actions{
RouteAction: api.RouteAction_ACCEPT,
Nexthop: &api.NexthopAction{
Address: *ipv6Address,
},
},
},
{
Conditions: &api.Conditions{
NeighborSet: &api.MatchSet{
Type: api.MatchSet_ANY,
Name: fmt.Sprintf("peer-%s", p.Conf.NeighborAddress),
},
},
Actions: &api.Actions{
RouteAction: api.RouteAction_ACCEPT,
},
},
},
},
})
if err != nil {
return fmt.Errorf("failed to add policy: %v", err)
if address != "" {
if err := insertPolicy(b.s, address, p, family); err != nil {
return fmt.Errorf("failed to add policy: %w", err)
}
}
} else {
if b.c.SourceIP != "" {
p.Transport.LocalAddress = b.c.SourceIP
}
err = b.s.AddPolicyAssignment(context.Background(), &api.AddPolicyAssignmentRequest{
Assignment: &api.PolicyAssignment{
Name: "global",
Direction: api.PolicyDirection_EXPORT,
Policies: []*api.Policy{
{
Name: fmt.Sprintf("peer-%s", p.Conf.NeighborAddress),
},
},
},
})
if err != nil {
return fmt.Errorf("failed to add policy assignment: %v", err)
if b.c.SourceIF != "" {
p.Transport.BindInterface = b.c.SourceIF
}
}
err = b.s.AddPeer(context.Background(), &api.AddPeerRequest{
Peer: p,
})
if err != nil {
if err := b.s.AddPeer(context.Background(), &api.AddPeerRequest{Peer: p}); err != nil {
return fmt.Errorf("failed to add peer: %v", err)
}
@@ -185,19 +120,19 @@ func (b *Server) getPath(ip net.IP) (path *api.Path) {
isV6 := ip.To4() == nil
//nolint
originAttr, _ := ptypes.MarshalAny(&api.OriginAttribute{
originAttr, _ := anypb.New(&api.OriginAttribute{
Origin: 0,
})
if !isV6 {
//nolint
nlri, _ := ptypes.MarshalAny(&api.IPAddressPrefix{
nlri, _ := anypb.New(&api.IPAddressPrefix{
Prefix: ip.String(),
PrefixLen: 32,
})
//nolint
nhAttr, _ := ptypes.MarshalAny(&api.NextHopAttribute{
nhAttr, _ := anypb.New(&api.NextHopAttribute{
NextHop: "0.0.0.0", // gobgp will fill this
})
@@ -211,7 +146,7 @@ func (b *Server) getPath(ip net.IP) (path *api.Path) {
}
} else {
//nolint
nlri, _ := ptypes.MarshalAny(&api.IPAddressPrefix{
nlri, _ := anypb.New(&api.IPAddressPrefix{
Prefix: ip.String(),
PrefixLen: 128,
})
@@ -222,7 +157,7 @@ func (b *Server) getPath(ip net.IP) (path *api.Path) {
}
//nolint
mpAttr, _ := ptypes.MarshalAny(&api.MpReachNLRIAttribute{
mpAttr, _ := anypb.New(&api.MpReachNLRIAttribute{
Family: v6Family,
NextHops: []string{"::"}, // gobgp will fill this
Nlris: []*any.Any{nlri},
@@ -241,11 +176,13 @@ func (b *Server) getPath(ip net.IP) (path *api.Path) {
func ParseBGPPeerConfig(config string) (bgpPeers []Peer, err error) {
peers := strings.Split(config, ",")
if len(peers) == 0 {
return nil, fmt.Errorf("No BGP Peer configurations found")
return nil, fmt.Errorf("no BGP Peer configurations found")
}
for x := range peers {
peerStr := peers[x]
config := strings.Split(peerStr, "/")
peerStr = config[0]
if peerStr == "" {
continue
}
@@ -288,11 +225,33 @@ func ParseBGPPeerConfig(config string) (bgpPeers []Peer, err error) {
}
}
var mpbgpNexthop, mpbgpIPv4, mpbgpIPv6 string
if len(config) > 1 {
configData := strings.Split(config[1], ";")
for _, cfg := range configData {
c := strings.Split(cfg, "=")
switch c[0] {
case "mpbgp_nexthop":
mpbgpNexthop = c[1]
case "mpbgp_ipv4":
mpbgpIPv4 = c[1]
case "mpbgp_ipv6":
mpbgpIPv6 = c[1]
default:
return nil, fmt.Errorf("peer configuration parameter '%s' is not supported", c[0])
}
}
}
peerConfig := Peer{
Address: address,
AS: uint32(ASNumber),
Password: password,
MultiHop: multiHop,
Address: address,
AS: uint32(ASNumber),
Password: password,
MultiHop: multiHop,
MpbgpNexthop: mpbgpNexthop,
MpbgpIPv4: mpbgpIPv4,
MpbgpIPv6: mpbgpIPv6,
}
bgpPeers = append(bgpPeers, peerConfig)
@@ -300,56 +259,73 @@ func ParseBGPPeerConfig(config string) (bgpPeers []Peer, err error) {
return
}
// GetInterfaceByIP returns the network interface that has the specified IP address assigned.
func GetInterfaceByIP(ipAddr string) (*net.Interface, error) {
ip := net.ParseIP(ipAddr)
if ip == nil {
return nil, fmt.Errorf("invalid IP address: %s", ipAddr)
func insertPolicy(s *server.BgpServer, address string, p *api.Peer, family api.Family_Afi) error {
familyType := "v4"
if family == api.Family_AFI_IP6 {
familyType = "v6"
}
links, err := netlink.LinkList()
setName := fmt.Sprintf("peer-%s", p.Conf.NeighborAddress)
policyName := fmt.Sprintf("%s-%s", setName, familyType)
policy := &api.Policy{
Name: policyName,
Statements: []*api.Statement{
{
Conditions: &api.Conditions{
AfiSafiIn: []*api.Family{
{
Afi: family,
Safi: api.Family_SAFI_UNICAST,
},
},
NeighborSet: &api.MatchSet{
Type: api.MatchSet_ANY,
Name: setName,
},
},
Actions: &api.Actions{
RouteAction: api.RouteAction_ACCEPT,
Nexthop: &api.NexthopAction{
Address: address,
},
},
},
{
Conditions: &api.Conditions{
NeighborSet: &api.MatchSet{
Type: api.MatchSet_ANY,
Name: setName,
},
},
Actions: &api.Actions{
RouteAction: api.RouteAction_ACCEPT,
},
},
},
}
err := s.AddPolicy(context.Background(), &api.AddPolicyRequest{
Policy: policy,
})
if err != nil {
return nil, fmt.Errorf("failed to list network interfaces: %v", err)
return fmt.Errorf("failed to add policy: %w", err)
}
for _, link := range links {
addrs, err := netlink.AddrList(link, netlink.FAMILY_ALL)
if err != nil {
return nil, fmt.Errorf("failed to list addresses for interface %s: %v", link.Attrs().Name, err)
}
for _, addr := range addrs {
if addr.IP.Equal(ip) {
iface, err := net.InterfaceByIndex(link.Attrs().Index)
if err != nil {
return nil, fmt.Errorf("failed to get interface by index: %v", err)
}
return iface, nil
}
}
}
return nil, fmt.Errorf("no interface found with IP address: %s", ipAddr)
}
// GetNonLinkLocalIPv6 returns the first non link-local IPv6 address on the given interface.
func GetNonLinkLocalIPv6(iface *net.Interface) (string, error) {
addrs, err := iface.Addrs()
err = s.AddPolicyAssignment(context.Background(), &api.AddPolicyAssignmentRequest{
Assignment: &api.PolicyAssignment{
Name: "global",
Direction: api.PolicyDirection_EXPORT,
Policies: []*api.Policy{
{
Name: policy.Name,
},
},
},
})
if err != nil {
return "", fmt.Errorf("failed to list addresses for interface %s: %v", iface.Name, err)
return fmt.Errorf("failed to add policy assignment: %v", err)
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok {
continue
}
ip := ipNet.IP
if ip.To4() == nil && !ip.IsLinkLocalUnicast() {
return ip.String(), nil
}
}
return "", fmt.Errorf("no non link-local IPv6 address found on interface %s", iface.Name)
return nil
}

View File

@@ -13,15 +13,15 @@ import (
// NewBGPServer takes a configuration and returns a running BGP server instance
func NewBGPServer(c *Config, peerStateChangeCallback func(*api.WatchEventResponse_PeerEvent)) (b *Server, err error) {
if c.AS == 0 {
return nil, fmt.Errorf("You need to provide AS")
return nil, fmt.Errorf("you need to provide AS")
}
if c.SourceIP != "" && c.SourceIF != "" {
return nil, fmt.Errorf("SourceIP and SourceIF are mutually exclusive")
return nil, fmt.Errorf("sourceIP and SourceIF are mutually exclusive")
}
if len(c.Peers) == 0 {
return nil, fmt.Errorf("You need to provide at least one peer")
return nil, fmt.Errorf("you need to provide at least one peer")
}
b = &Server{

View File

@@ -1,21 +1,120 @@
package bgp
import gobgp "github.com/osrg/gobgp/v3/pkg/server"
import (
"fmt"
"net"
"github.com/kube-vip/kube-vip/pkg/vip"
api "github.com/osrg/gobgp/v3/api"
gobgp "github.com/osrg/gobgp/v3/pkg/server"
"github.com/vishvananda/netlink"
)
// Peer defines a BGP Peer
type Peer struct {
Address string
AS uint32
Password string
MultiHop bool
Address string
AS uint32
Password string
MultiHop bool
MpbgpNexthop string
MpbgpIPv4 string
MpbgpIPv6 string
}
func (p *Peer) setMpbgpOptions(server *Config) {
if p.MpbgpNexthop == "" {
p.MpbgpNexthop = server.MpbgpNexthop
}
if p.MpbgpIPv4 == "" {
p.MpbgpIPv4 = server.MpbgpIPv4
}
if p.MpbgpIPv6 == "" {
p.MpbgpIPv6 = server.MpbgpIPv6
}
}
func (p *Peer) findMpbgpAddresses(ap *api.Peer, server *Config) (string, string, error) {
var ipv4Address, ipv6Address string
switch p.MpbgpNexthop {
case "fixed":
ap.Transport.LocalAddress = server.SourceIP
if p.MpbgpIPv4 == "" && p.MpbgpIPv6 == "" {
return "", "", fmt.Errorf("to use MP-BGP with fixed address at least one IPv4 or IPv6 address has to be provided [current - IPv4: %s, IPv6: %s]",
p.MpbgpIPv4, p.MpbgpIPv6)
}
if p.MpbgpIPv4 != "" {
if net.ParseIP(p.MpbgpIPv4) == nil {
return "", "", fmt.Errorf("provided address '%s' is not a valid IPv4 address", p.MpbgpIPv4)
}
}
if p.MpbgpIPv6 != "" {
if net.ParseIP(p.MpbgpIPv6) == nil {
return "", "", fmt.Errorf("provided address '%s' is not a valid IPv6 address", p.MpbgpIPv6)
}
}
ipv4Address = p.MpbgpIPv4
ipv6Address = p.MpbgpIPv6
case "auto_sourceip":
ap.Transport.LocalAddress = server.SourceIP
// Resolve the local interface by SourceIP
iface, err := vip.GetInterfaceByIP(server.SourceIP)
if err != nil {
return "", "", fmt.Errorf("failed to get interface by IP: %v", err)
}
if vip.IsIPv4(server.SourceIP) {
// Get the non link-local IPv6 address on that interface
ipv6Address, err = vip.GetNonLinkLocalIP(iface, netlink.FAMILY_V6)
if err != nil {
return "", "", fmt.Errorf("failed to get non link-local IPv6 address: %v", err)
}
} else {
// Get the non link-local IPv4 address on that interface
ipv4Address, err = vip.GetNonLinkLocalIP(iface, netlink.FAMILY_V4)
if err != nil {
return "", "", fmt.Errorf("failed to get non link-local IPv4 address: %v", err)
}
}
case "auto_sourceif":
ap.Transport.BindInterface = server.SourceIF
iface, err := netlink.LinkByName(server.SourceIF)
if err != nil {
return "", "", fmt.Errorf("failed to get interface by name: %v", err)
}
// Get the non link-local IPv4 address on that interface
ipv4Address, err = vip.GetNonLinkLocalIP(&iface, netlink.FAMILY_V4)
if err != nil {
return "", "", fmt.Errorf("failed to get non link-local IPv4 address: %v", err)
}
// Get the non link-local IPv6 address on that interface
ipv6Address, err = vip.GetNonLinkLocalIP(&iface, netlink.FAMILY_V6)
if err != nil {
return "", "", fmt.Errorf("failed to get non link-local IPv6 address: %v", err)
}
default:
return "", "", fmt.Errorf("option %s for MP-BPG nexthop is not supported", server.MpbgpNexthop)
}
return ipv4Address, ipv6Address, nil
}
// Config defines the BGP server configuration
type Config struct {
AS uint32
RouterID string
SourceIP string
SourceIF string
AS uint32
RouterID string
SourceIP string
SourceIF string
MpbgpNexthop string
MpbgpIPv4 string
MpbgpIPv6 string
HoldTime uint64
KeepaliveInterval uint64

View File

@@ -444,6 +444,24 @@ func ParseEnvironment(c *Config) error {
c.BGPConfig.Peers = peers
}
// MPBGP mode
env = os.Getenv(mpbgpNexthop)
if env != "" {
c.BGPConfig.MpbgpNexthop = env
}
// MPBGP fixed IPv4
env = os.Getenv(mpbgpIPv4)
if env != "" {
c.BGPConfig.MpbgpIPv4 = env
}
// MPBGP fixed IPv6
env = os.Getenv(mpbgpIPv6)
if env != "" {
c.BGPConfig.MpbgpIPv6 = env
}
// BGP Peer mutlihop
env = os.Getenv(bgpMultiHop)
if env != "" {

View File

@@ -125,6 +125,13 @@ const (
// bgpKeepaliveInterval defines bgp timers keepalive interval
bgpKeepaliveInterval = "bgp_keepalive_interval"
// mpbgpNexthop defines MPBGP mode
mpbgpNexthop = "mpbgp_nexthop"
// mpbgpIPv4 defines fixed IPv4 to be used with MPBGP
mpbgpIPv4 = "mpbgp_ipv4"
// mpbgpIPv6 defines fixed IPv6 to be used with MPBGP
mpbgpIPv6 = "mpbgp_ipv6"
// vipWireguard - defines if wireguard will be used for vips
vipWireguard = "vip_wireguard" //nolint

View File

@@ -202,3 +202,50 @@ func Split(values string) []string {
}
return result
}
// GetInterfaceByIP returns the network interface that has the specified IP address assigned.
func GetInterfaceByIP(ipAddr string) (*netlink.Link, error) {
ip := net.ParseIP(ipAddr)
if ip == nil {
return nil, fmt.Errorf("invalid IP address: %s", ipAddr)
}
links, err := netlink.LinkList()
if err != nil {
return nil, fmt.Errorf("failed to list network interfaces: %v", err)
}
for i := range links {
addrs, err := netlink.AddrList(links[i], netlink.FAMILY_ALL)
if err != nil {
return nil, fmt.Errorf("failed to list addresses for interface %s: %v", links[i].Attrs().Name, err)
}
for _, addr := range addrs {
if addr.IP.Equal(ip) {
return &links[i], nil
}
}
}
return nil, fmt.Errorf("no interface found with IP address: %s", ipAddr)
}
// GetNonLinkLocalIP returns the first non link-local IPv4/IPv6 address on the given interface.
func GetNonLinkLocalIP(iface *netlink.Link, family int) (string, error) {
a, err := netlink.AddrList(*iface, family)
if err != nil {
return "", fmt.Errorf("failed to list addresses for interface %s: %v", (*iface).Attrs().Name, err)
}
for _, addr := range a {
if addr.IPNet != nil {
ip := addr.IPNet.IP
if !ip.IsLinkLocalUnicast() {
return ip.String(), nil
}
}
}
return "", fmt.Errorf("failed to find non-local IP on interface: %s", (*iface).Attrs().Name)
}