From f120bceaf332c9c4283a01089afcc7d82d6c5d71 Mon Sep 17 00:00:00 2001 From: Ben Bettridge Date: Thu, 30 Nov 2023 19:20:11 +1300 Subject: [PATCH 1/3] Add new CLI args for active/active setup --- client.go | 4 +-- helpers.go | 74 ++++++++++++++++++++++++++++++----------------- main.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 122 insertions(+), 41 deletions(-) diff --git a/client.go b/client.go index 5662362..e8c4915 100644 --- a/client.go +++ b/client.go @@ -141,10 +141,10 @@ func (c *Client) AddStaticRoute(nh string, p IPNet, cm string) error { // AddRoutes adds a static route for all IPs monitored func (c *Client) AddRoutes() error { for _, ip := range *c.ips { - if err := c.AddStaticRoute("", ip, c.community); err != nil { + if err := c.AddStaticRoute("", ip, ip.community); err != nil { return err } - log.WithFields(log.Fields{"Topic": "Route", "Route": ip, "Community": c.community}).Info("added route") + log.WithFields(log.Fields{"Topic": "Route", "Route": ip, "Community": ip.community}).Info("added route") } return nil } diff --git a/helpers.go b/helpers.go index 7092c33..25dcff7 100644 --- a/helpers.go +++ b/helpers.go @@ -13,16 +13,19 @@ import ( ) // IPNet is a extension of net.IPNet with some addons -type IPNet net.IPNet +type IPNet struct { + ip net.IPNet + community string +} func (i IPNet) String() string { - j, _ := i.Mask.Size() - return fmt.Sprintf("%v/%v", i.IP, j) + j, _ := i.ip.Mask.Size() + return fmt.Sprintf("%v/%v", i.ip.IP, j) } // Plen returns the prefix len as uint func (i IPNet) Plen() uint32 { - j, _ := i.Mask.Size() + j, _ := i.ip.Mask.Size() return uint32(j) } @@ -34,8 +37,23 @@ func IPNetFromAddr(a net.Addr) (*IPNet, error) { } return &IPNet{ - IP: p.IP, - Mask: p.Mask, + ip: *p, + }, nil +} + +// IPNetFromString converts a string to an IPNet +// If the supplied string does not have a CIDR notation, it is assumed to be /32 +func IPNetFromString(s string) (*IPNet, error) { + if !strings.Contains(s, "/") { + s = fmt.Sprintf("%s/32", s) + } + _, p, err := net.ParseCIDR(s) + if err != nil { + return nil, err + } + + return &IPNet{ + ip: *p, }, nil } @@ -70,12 +88,12 @@ func getPath(p IPNet, nh string, myCom string) (*api.Path, error) { } nlri, _ := ptypes.MarshalAny(&api.IPAddressPrefix{ - Prefix: p.IP.String(), + Prefix: p.ip.IP.String(), PrefixLen: p.Plen(), }) var family *api.Family - if p.IP.To4() == nil { + if p.ip.IP.To4() == nil { family = &api.Family{ Afi: api.Family_AFI_IP6, Safi: api.Family_SAFI_UNICAST, @@ -111,7 +129,7 @@ func getPath(p IPNet, nh string, myCom string) (*api.Path, error) { }, nil } -//get all local IPs elegible to be elastic IP +// get all local IPs elegible to be elastic IP func getIPs(v6Mask int, allIfs bool) (*[]IPNet, error) { var addrs []net.Addr var err error @@ -135,54 +153,56 @@ func getIPs(v6Mask int, allIfs bool) (*[]IPNet, error) { ips := make(map[string]*IPNet) for _, addr := range addrs { - ip, err := IPNetFromAddr(addr) + p, err := IPNetFromAddr(addr) if err != nil { log.WithFields(log.Fields{"Topic": "Helper", "Route": addr, "Error": "invalid IP"}).Warn("invalid IP") continue } // ignore loopback IPs - if ip.IP.IsLoopback() { - log.WithFields(log.Fields{"Topic": "Helper", "Route": ip, "Warn": "not acceptable elastic IP"}). + if p.ip.IP.IsLoopback() { + log.WithFields(log.Fields{"Topic": "Helper", "Route": p, "Warn": "not acceptable elastic IP"}). Trace("ignoring loopback IPs") continue } // ignore link local IPs - if ip.IP.IsLinkLocalUnicast() { - log.WithFields(log.Fields{"Topic": "Helper", "Route": ip, "Warn": "not acceptable elastic IP"}). + if p.ip.IP.IsLinkLocalUnicast() { + log.WithFields(log.Fields{"Topic": "Helper", "Route": p, "Warn": "not acceptable elastic IP"}). Trace("ignoring linklocal IPs") continue } // for ipv4 only a /32 is acceptable - if ip.IP.To4() != nil && ip.Plen() != 32 { - log.WithFields(log.Fields{"Topic": "Helper", "Route": ip, "Warn": "not accepted prefix length"}). + if p.ip.IP.To4() != nil && p.Plen() != 32 { + log.WithFields(log.Fields{"Topic": "Helper", "Route": p, "Warn": "not accepted prefix length"}). Warn("not accepted prefix length") continue } // for ipv6 lets find the greater subnet we're part of, make it a /64 (or if asked a /56) and advertise that - if ip.IP.To4() == nil { - if ip.Plen() != 64 && ip.Plen() != 56 { - log.WithFields(log.Fields{"Topic": "Helper", "Route": ip, "Warn": "fixing prefix length"}). + if p.ip.IP.To4() == nil { + if p.Plen() != 64 && p.Plen() != 56 { + log.WithFields(log.Fields{"Topic": "Helper", "Route": p, "Warn": "fixing prefix length"}). Warnf("fixing prefix lenth length to /%d", v6Mask) - ip.Mask = sendMask + p.ip.Mask = sendMask } - _, ipNew, err := net.ParseCIDR(ip.String()) + _, ipNew, err := net.ParseCIDR(p.String()) if err != nil { - log.WithFields(log.Fields{"Topic": "Helper", "Route": ip, "Error": "invalid IP"}). + log.WithFields(log.Fields{"Topic": "Helper", "Route": p, "Error": "invalid IP"}). Warnf("unable to supernet") continue } - ip = &IPNet{ - IP: ipNew.IP, - Mask: ipNew.Mask, + p = &IPNet{ + ip: net.IPNet{ + IP: ipNew.IP, + Mask: ipNew.Mask, + }, } } - ips[ip.String()] = ip - log.WithFields(log.Fields{"Topic": "Helper", "Route": ip}).Debug("handling prefix") + ips[p.String()] = p + log.WithFields(log.Fields{"Topic": "Helper", "Route": p}).Debug("handling prefix") } var uniqIPs []IPNet diff --git a/main.go b/main.go index 6097305..15bd80e 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "strings" log "github.com/sirupsen/logrus" ) @@ -16,9 +17,25 @@ const ( communitySecondary = "65000:2" ) +type arrayFlags []string + +func (i *arrayFlags) String() string { + return fmt.Sprintf("%v", []string(*i)) +} + +func (i *arrayFlags) Set(value string) error { + *i = append(*i, value) + return nil +} + +var primaryIps arrayFlags +var secondaryIps arrayFlags + func main() { primary := flag.Bool("primary", false, "advertise as primary") secondary := flag.Bool("secondary", false, "advertise as secondary") + flag.Var(&primaryIps, "primary-ip", "Advertise as primary for a specific IP. Mutual exclusive with primary flag.") + flag.Var(&secondaryIps, "secondary-ip", "Advertise as secondary for a specific IP. Mutual exclusive with secondary flag.") loglevel := flag.String("loglevel", "info", "set log level: trace, debug, info or warn") logjson := flag.Bool("logjson", false, "set log format to json") dcid := flag.Int("dcid", 0, "dcid for your DC") @@ -46,9 +63,16 @@ func main() { log.WithFields(log.Fields{"Topic": "Main"}).Fatal("dcid not provided, I need this info") } - if !*primary && !*secondary { + if (len(primaryIps) > 0 || len(secondaryIps) > 0) && (*primary || *secondary) { flag.Usage() - log.WithFields(log.Fields{"Topic": "Main"}).Fatal("use either primary or secondary flag") + log.WithFields(log.Fields{"Topic": "Main"}).Fatal("primary/secondary and primary-ip/secondary-ip are mutually exclusive") + } + + if len(primaryIps) == 0 && len(secondaryIps) == 0 { + if !*primary && !*secondary { + flag.Usage() + log.WithFields(log.Fields{"Topic": "Main"}).Fatal("use either primary or secondary flag") + } } switch *loglevel { @@ -67,28 +91,65 @@ func main() { } var myCommunity string + var communityMap map[string]string + communityMap = make(map[string]string) + + if len(primaryIps) > 0 { + for _, ip := range primaryIps { + if !strings.Contains(ip, "/") { + ip = fmt.Sprintf("%s/32", ip) + } + communityMap[ip] = communityPrimary + } + } - switch { - case *primary: - myCommunity = communityPrimary - case *secondary: - myCommunity = communitySecondary - default: - log.WithFields(log.Fields{"Topic": "Main"}).Fatal("use either primary or secondary flag") + if len(secondaryIps) > 0 { + for _, ip := range secondaryIps { + if !strings.Contains(ip, "/") { + ip = fmt.Sprintf("%s/32", ip) + } + communityMap[ip] = communitySecondary + } + } + + // If no IPs are specified, fallback to using the primary/secondary flags + if len(communityMap) == 0 { + switch { + case *primary: + myCommunity = communityPrimary + case *secondary: + myCommunity = communitySecondary + default: + log.WithFields(log.Fields{"Topic": "Main"}).Fatal("use either primary or secondary flag") + } } - //ips v6Mask := 64 if *send56 { v6Mask = 56 } - ips, err := getIPs(v6Mask, *allIfs) + allIps, err := getIPs(v6Mask, *allIfs) if err != nil { log.WithFields(log.Fields{"Topic": "Main"}).Fatalf("unable to detect IPs: %v", err) } - c, err := NewClient(myCommunity, ips) + var ips []IPNet + + // Filter the list of IPs to only include the ones we want to advertise + if len(communityMap) > 0 { + for _, ipData := range *allIps { + ipString := ipData.String() + if _, ok := communityMap[ipString]; ok { + ipData.community = communityMap[ipString] + ips = append(ips, ipData) + } + } + } else { + ips = *allIps + } + + c, err := NewClient(myCommunity, &ips) if err != nil { log.WithFields(log.Fields{"Topic": "Main"}).Fatal("failed to initiate the client: ", err) } From 2b98e023a087c8ccd9c3e0e54dc3e0c9c2a2d1ab Mon Sep 17 00:00:00 2001 From: Ben Bettridge Date: Thu, 30 Nov 2023 19:36:21 +1300 Subject: [PATCH 2/3] Remove primary and secondary fallback behaviour --- client.go | 20 +++++++++----------- helpers.go | 16 ---------------- main.go | 42 +++++++----------------------------------- 3 files changed, 16 insertions(+), 62 deletions(-) diff --git a/client.go b/client.go index e8c4915..8de12fd 100644 --- a/client.go +++ b/client.go @@ -13,15 +13,14 @@ import ( // Client is the client type Client struct { - c *server.BgpServer - ips *[]IPNet - ipv6Plen int - community string - wg *sync.WaitGroup + c *server.BgpServer + ips *[]IPNet + ipv6Plen int + wg *sync.WaitGroup } // NewClient instantiates a new client connection -func NewClient(c string, ips *[]IPNet) (*Client, error) { +func NewClient(ips *[]IPNet) (*Client, error) { maxSize := 256 << 20 grpcOpts := []grpc.ServerOption{grpc.MaxRecvMsgSize(maxSize), grpc.MaxSendMsgSize(maxSize)} @@ -51,11 +50,10 @@ func NewClient(c string, ips *[]IPNet) (*Client, error) { } return &Client{ - c: cl, - ips: ips, - ipv6Plen: 64, - community: c, - wg: wg, + c: cl, + ips: ips, + ipv6Plen: 64, + wg: wg, }, nil } diff --git a/helpers.go b/helpers.go index 25dcff7..8ba39ca 100644 --- a/helpers.go +++ b/helpers.go @@ -41,22 +41,6 @@ func IPNetFromAddr(a net.Addr) (*IPNet, error) { }, nil } -// IPNetFromString converts a string to an IPNet -// If the supplied string does not have a CIDR notation, it is assumed to be /32 -func IPNetFromString(s string) (*IPNet, error) { - if !strings.Contains(s, "/") { - s = fmt.Sprintf("%s/32", s) - } - _, p, err := net.ParseCIDR(s) - if err != nil { - return nil, err - } - - return &IPNet{ - ip: *p, - }, nil -} - // parse bgp community from string to uint32 func parseCommunity(c string) (uint32, error) { s := strings.SplitN(c, ":", 2) diff --git a/main.go b/main.go index 15bd80e..8b547a0 100644 --- a/main.go +++ b/main.go @@ -3,8 +3,6 @@ package main import ( "flag" "fmt" - "strings" - log "github.com/sirupsen/logrus" ) @@ -32,10 +30,8 @@ var primaryIps arrayFlags var secondaryIps arrayFlags func main() { - primary := flag.Bool("primary", false, "advertise as primary") - secondary := flag.Bool("secondary", false, "advertise as secondary") - flag.Var(&primaryIps, "primary-ip", "Advertise as primary for a specific IP. Mutual exclusive with primary flag.") - flag.Var(&secondaryIps, "secondary-ip", "Advertise as secondary for a specific IP. Mutual exclusive with secondary flag.") + flag.Var(&primaryIps, "primary", "Advertise as primary for a specific IP. Must contain CIDR notation") + flag.Var(&secondaryIps, "secondary", "Advertise as secondary for a specific IP. Must contain CIDR notation") loglevel := flag.String("loglevel", "info", "set log level: trace, debug, info or warn") logjson := flag.Bool("logjson", false, "set log format to json") dcid := flag.Int("dcid", 0, "dcid for your DC") @@ -63,16 +59,9 @@ func main() { log.WithFields(log.Fields{"Topic": "Main"}).Fatal("dcid not provided, I need this info") } - if (len(primaryIps) > 0 || len(secondaryIps) > 0) && (*primary || *secondary) { - flag.Usage() - log.WithFields(log.Fields{"Topic": "Main"}).Fatal("primary/secondary and primary-ip/secondary-ip are mutually exclusive") - } - if len(primaryIps) == 0 && len(secondaryIps) == 0 { - if !*primary && !*secondary { - flag.Usage() - log.WithFields(log.Fields{"Topic": "Main"}).Fatal("use either primary or secondary flag") - } + flag.Usage() + log.WithFields(log.Fields{"Topic": "Main"}).Fatal("primary and/or secondary must be provided") } switch *loglevel { @@ -90,40 +79,21 @@ func main() { log.SetLevel(log.InfoLevel) } - var myCommunity string var communityMap map[string]string communityMap = make(map[string]string) if len(primaryIps) > 0 { for _, ip := range primaryIps { - if !strings.Contains(ip, "/") { - ip = fmt.Sprintf("%s/32", ip) - } communityMap[ip] = communityPrimary } } if len(secondaryIps) > 0 { for _, ip := range secondaryIps { - if !strings.Contains(ip, "/") { - ip = fmt.Sprintf("%s/32", ip) - } communityMap[ip] = communitySecondary } } - // If no IPs are specified, fallback to using the primary/secondary flags - if len(communityMap) == 0 { - switch { - case *primary: - myCommunity = communityPrimary - case *secondary: - myCommunity = communitySecondary - default: - log.WithFields(log.Fields{"Topic": "Main"}).Fatal("use either primary or secondary flag") - } - } - v6Mask := 64 if *send56 { v6Mask = 56 @@ -141,15 +111,17 @@ func main() { for _, ipData := range *allIps { ipString := ipData.String() if _, ok := communityMap[ipString]; ok { + log.WithFields(log.Fields{"Topic": "Main"}).Infof("advertising IP %s with community %s", ipString, communityMap[ipString]) ipData.community = communityMap[ipString] ips = append(ips, ipData) } } } else { + log.WithFields(log.Fields{"Topic": "Main"}).Info("no IPs specified, advertising all IPs") ips = *allIps } - c, err := NewClient(myCommunity, &ips) + c, err := NewClient(&ips) if err != nil { log.WithFields(log.Fields{"Topic": "Main"}).Fatal("failed to initiate the client: ", err) } From b336e385405a2edf8acceefd118adcd73bb26d01 Mon Sep 17 00:00:00 2001 From: Ben Bettridge Date: Thu, 30 Nov 2023 20:03:33 +1300 Subject: [PATCH 3/3] Error checking --- main.go | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 8b547a0..0b0ab75 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" log "github.com/sirupsen/logrus" + "net" ) const ( @@ -59,11 +60,23 @@ func main() { log.WithFields(log.Fields{"Topic": "Main"}).Fatal("dcid not provided, I need this info") } - if len(primaryIps) == 0 && len(secondaryIps) == 0 { + if primaryIps == nil && secondaryIps == nil { flag.Usage() log.WithFields(log.Fields{"Topic": "Main"}).Fatal("primary and/or secondary must be provided") } + for _, ip := range primaryIps { + if _, _, err := net.ParseCIDR(ip); err != nil { + log.WithFields(log.Fields{"Topic": "Main"}).Fatalf("invalid primary ip: %s. Must be in CIDR notation", ip) + } + } + + for _, ip := range secondaryIps { + if _, _, err := net.ParseCIDR(ip); err != nil { + log.WithFields(log.Fields{"Topic": "Main"}).Fatalf("invalid secondary ip: %s. Must be in CIDR notation", ip) + } + } + switch *loglevel { case "trace": log.SetLevel(log.TraceLevel) @@ -114,6 +127,8 @@ func main() { log.WithFields(log.Fields{"Topic": "Main"}).Infof("advertising IP %s with community %s", ipString, communityMap[ipString]) ipData.community = communityMap[ipString] ips = append(ips, ipData) + } else { + log.WithFields(log.Fields{"Topic": "Main", "IP": ipString}).Warnf("not advetising IP %s as it was not specified", ipString) } } } else { @@ -121,6 +136,15 @@ func main() { ips = *allIps } + if len(ips) != len(communityMap) { + log.WithFields(log.Fields{ + "Topic": "Main", + "Detected": ips, + "Requested Primaries": primaryIps, + "Requested Secondaries": secondaryIps, + }).Fatal("Unable to detect all IPs specified. Check the IP addresses assigned to 'lo' or alternatively try the -allifs flag") + } + c, err := NewClient(&ips) if err != nil { log.WithFields(log.Fields{"Topic": "Main"}).Fatal("failed to initiate the client: ", err)