From 23257f7a2bff0c99ddaa14c46b4787d42c9864ca Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 18 May 2022 14:52:02 +0100 Subject: [PATCH 1/3] Improve argument usage and error messages --- main.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/main.go b/main.go index 3d63d36..d941256 100644 --- a/main.go +++ b/main.go @@ -17,12 +17,12 @@ const ( ) func main() { - primary := flag.Bool("primary", false, "advertise as primary") - secondary := flag.Bool("secondary", false, "advertise as secondary") - 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") - send56 := flag.Bool("send56", false, "Advertise ipv6 as /56 subnet (defaults to /64)") + primary := flag.Bool("primary", false, "Advertise as primary") + secondary := flag.Bool("secondary", false, "Advertise as secondary") + 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, "Your Linode data center id") + send56 := flag.Bool("send56", false, "Advertise IPv6 as /56 subnet (defaults to /64)") allIfs := flag.Bool( "allifs", false, @@ -43,12 +43,12 @@ func main() { if *dcid <= 1 { flag.Usage() - log.WithFields(log.Fields{"Topic": "Main"}).Fatal("dcid not provided, I need this info") + log.WithFields(log.Fields{"Topic": "Main"}).Fatal("Required -dcid not provided") } if !*primary && !*secondary { flag.Usage() - log.WithFields(log.Fields{"Topic": "Main"}).Fatal("use either primary or secondary flag") + log.WithFields(log.Fields{"Topic": "Main"}).Fatal("Use either -primary or -secondary flag") } switch *loglevel { @@ -62,7 +62,7 @@ func main() { log.SetLevel(log.WarnLevel) default: log.WithFields(log.Fields{"Topic": "Main"}). - Warn("unknown log level, only trace, debug, info and warn are supported, falling back to loglevel info") + Warn("Unknown log level, only trace, debug, info and warn are supported, falling back to loglevel info") log.SetLevel(log.InfoLevel) } @@ -85,12 +85,12 @@ func main() { ips, err := getIPs(v6Mask, *allIfs) if err != nil { - log.WithFields(log.Fields{"Topic": "Main"}).Fatalf("unable to detect IPs: %v", err) + log.WithFields(log.Fields{"Topic": "Main"}).Fatalf("Unable to detect IPs: %v", err) } c, err := NewClient(myCommunity, ips) if err != nil { - log.WithFields(log.Fields{"Topic": "Main"}).Fatal("failed to initiate the client: ", err) + log.WithFields(log.Fields{"Topic": "Main"}).Fatal("Failed to initiate the BGP client: ", err) } c.wg.Add(1) @@ -98,13 +98,13 @@ func main() { for i := 1; i <= 4; i++ { rs := fmt.Sprintf("2600:3c0f:%d:34::%d", *dcid, i) if err := c.AddRs(rs); err != nil { - log.WithFields(log.Fields{"Topic": "Neighbor", "Neighbor": rs}).Fatal("failed adding neighbor") + log.WithFields(log.Fields{"Topic": "Neighbor", "Neighbor": rs}).Fatal("Failed adding neighbor") } // log.WithFields(log.Fields{"Topic": "Neighbor", "Neighbor": rs}).Info("added neighbor") } if err := c.AddRoutes(); err != nil { - log.WithFields(log.Fields{"Topic": "IPs"}).Fatal("failed adding IP advertisements: ", err) + log.WithFields(log.Fields{"Topic": "IPs"}).Fatal("Failed adding IP advertisements: ", err) } log.WithFields(log.Fields{"Topic": "Main"}).Info("Running....") From ff90c2eb20dba674b10d682054cd6c1788cddf30 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 18 May 2022 14:54:11 +0100 Subject: [PATCH 2/3] Default to primary advertisment if -secondary not specified. --- main.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/main.go b/main.go index d941256..aaf5532 100644 --- a/main.go +++ b/main.go @@ -17,7 +17,7 @@ const ( ) func main() { - primary := flag.Bool("primary", false, "Advertise as primary") + primary := flag.Bool("primary", false, "Advertise as primary (default if -secondary not specified)") secondary := flag.Bool("secondary", false, "Advertise as secondary") loglevel := flag.String("loglevel", "info", "Set log level: trace, debug, info or warn") logjson := flag.Bool("logjson", false, "Set log format to json") @@ -46,11 +46,13 @@ func main() { log.WithFields(log.Fields{"Topic": "Main"}).Fatal("Required -dcid not provided") } - if !*primary && !*secondary { + if *primary && *secondary { flag.Usage() - log.WithFields(log.Fields{"Topic": "Main"}).Fatal("Use either -primary or -secondary flag") + log.WithFields(log.Fields{"Topic": "Main"}).Fatal("Use either -primary or -secondary flag, not both!") } + + switch *loglevel { case "trace": log.SetLevel(log.TraceLevel) @@ -66,15 +68,9 @@ func main() { log.SetLevel(log.InfoLevel) } - var myCommunity string - - switch { - case *primary: - myCommunity = communityPrimary - case *secondary: + myCommunity := communityPrimary; + if *secondary { myCommunity = communitySecondary - default: - log.WithFields(log.Fields{"Topic": "Main"}).Fatal("use either primary or secondary flag") } //ips From 4164ac2fa320e47f7bf575a3210532cb42684b1f Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 18 May 2022 16:08:51 +0100 Subject: [PATCH 3/3] Allow specifying IPs manually on the command line. There are some unique cases where this is necessary such as when using network namespaces to isloate the IP assignment on a given host. For example, one could use a container which uses a bridge IPVLAN interface to ensure the shared IP is available online 'inside' that container. When this is the case, you cannot run lelastic (or any other BGP client) inside the container as it requires it's own IPv6 network assigned via SLAAC to communicate with the peers. As you have to run it one the host, and the IPs in question are not shown on any interface visible on the host, they cannot be auto-detected. The simplest solution here is to allow specifying the IPs on the command line. A future improvement might be to speak to systemd-machined to obtain a list of machine and their relevant IPs to advertise (assuming the container is registered with systemd-machined). --- helpers.go | 106 +++++++++++++++++++++++++++++++++-------------------- main.go | 4 +- 2 files changed, 68 insertions(+), 42 deletions(-) diff --git a/helpers.go b/helpers.go index 7092c33..ec69cd9 100644 --- a/helpers.go +++ b/helpers.go @@ -27,8 +27,8 @@ func (i IPNet) Plen() uint32 { } // IPNetFromAddr reads net.Addr and converts it to IPNet -func IPNetFromAddr(a net.Addr) (*IPNet, error) { - _, p, err := net.ParseCIDR(a.String()) +func IPNetFromString(a string) (*IPNet, error) { + _, p, err := net.ParseCIDR(a) if err != nil { return nil, err } @@ -39,6 +39,10 @@ func IPNetFromAddr(a net.Addr) (*IPNet, error) { }, nil } +func IPNetFromAddr(a net.Addr) (*IPNet, error) { + return IPNetFromString(a.String()) +} + // parse bgp community from string to uint32 func parseCommunity(c string) (uint32, error) { s := strings.SplitN(c, ":", 2) @@ -111,55 +115,26 @@ func getPath(p IPNet, nh string, myCom string) (*api.Path, error) { }, nil } -//get all local IPs elegible to be elastic IP -func getIPs(v6Mask int, allIfs bool) (*[]IPNet, error) { - var addrs []net.Addr - var err error - - if allIfs { - addrs, err = net.InterfaceAddrs() - } else { - lo, err := net.InterfaceByName("lo") - if err != nil { - return nil, err - } - addrs, err = lo.Addrs() - } - if err != nil { - return nil, err - } - - sendMask := net.CIDRMask(v6Mask, 128) - - // var ips []IPNet - ips := make(map[string]*IPNet) - - for _, addr := range addrs { - ip, err := IPNetFromAddr(addr) - if err != nil { - log.WithFields(log.Fields{"Topic": "Helper", "Route": addr, "Error": "invalid IP"}).Warn("invalid IP") - continue - } - +func validateIP(ip *IPNet, v6Mask int, sendMask net.IPMask) (*IPNet) { // ignore loopback IPs if ip.IP.IsLoopback() { log.WithFields(log.Fields{"Topic": "Helper", "Route": ip, "Warn": "not acceptable elastic IP"}). Trace("ignoring loopback IPs") - continue + return nil } // ignore link local IPs if ip.IP.IsLinkLocalUnicast() { log.WithFields(log.Fields{"Topic": "Helper", "Route": ip, "Warn": "not acceptable elastic IP"}). Trace("ignoring linklocal IPs") - continue + return nil } // 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"}). Warn("not accepted prefix length") - continue + return nil } // for ipv6 lets find the greater subnet we're part of, make it a /64 (or if asked a /56) and advertise that @@ -173,16 +148,67 @@ func getIPs(v6Mask int, allIfs bool) (*[]IPNet, error) { if err != nil { log.WithFields(log.Fields{"Topic": "Helper", "Route": ip, "Error": "invalid IP"}). Warnf("unable to supernet") - continue + return nil } - ip = &IPNet{ + return &IPNet{ IP: ipNew.IP, Mask: ipNew.Mask, } } - ips[ip.String()] = ip - log.WithFields(log.Fields{"Topic": "Helper", "Route": ip}).Debug("handling prefix") + return ip +} + +//get all local IPs elegible to be elastic IP +func getIPs(v6Mask int, allIfs bool, manualIPs []string) (*[]IPNet, error) { + var addrs []net.Addr + var err error + ips := make(map[string]*IPNet) + + sendMask := net.CIDRMask(v6Mask, 128) + + // Manually provided IPs + for _, a := range manualIPs { + ip, err := IPNetFromString(a) + if err != nil { + log.WithFields(log.Fields{"Topic": "Main", "Route": a, "Error": "invalid IP"}).Warnf("Invalid IP: %v", err) + continue + } + ip = validateIP(ip, v6Mask, sendMask) + if ip != nil { + ips[ip.String()] = ip + log.WithFields(log.Fields{"Topic": "Helper", "Route": ip}).Debug("handling prefix (manual)") + } + } + + + if allIfs { + addrs, err = net.InterfaceAddrs() + } else { + lo, err := net.InterfaceByName("lo") + if err != nil { + log.WithFields(log.Fields{"Topic": "Helper"}).Warnf("Cannot find 'lo' interface: %v", err) + } else { + addrs, err = lo.Addrs() + } + } + if err != nil { + log.WithFields(log.Fields{"Topic": "Helper"}).Warnf("Cannot load interface addresses: %v", err) + } + + + for _, addr := range addrs { + ip, err := IPNetFromAddr(addr) + if err != nil { + log.WithFields(log.Fields{"Topic": "Helper", "Route": addr, "Error": "invalid IP"}).Warnf("Invalid IP: %v", err) + continue + } + + ip = validateIP(ip, v6Mask, sendMask) + if ip != nil { + ips[ip.String()] = ip + log.WithFields(log.Fields{"Topic": "Helper", "Route": ip}).Debug("handling prefix") + } } var uniqIPs []IPNet @@ -191,7 +217,7 @@ func getIPs(v6Mask int, allIfs bool) (*[]IPNet, error) { } if len(uniqIPs) == 0 { - return nil, fmt.Errorf("didn't find any configured elastic IPs") + return nil, fmt.Errorf("Didn't find any elastic IPs") } return &uniqIPs, nil diff --git a/main.go b/main.go index aaf5532..e23673d 100644 --- a/main.go +++ b/main.go @@ -79,9 +79,9 @@ func main() { v6Mask = 56 } - ips, err := getIPs(v6Mask, *allIfs) + ips, err := getIPs(v6Mask, *allIfs, flag.Args()) if err != nil { - log.WithFields(log.Fields{"Topic": "Main"}).Fatalf("Unable to detect IPs: %v", err) + log.WithFields(log.Fields{"Topic": "Main"}).Fatalf("No IPs: %v", err) } c, err := NewClient(myCommunity, ips)