Created
August 27, 2020 13:43
-
-
Save thebsdbox/7a6053f2d5b9a5a8ad66e6fc5ef7bb2d to your computer and use it in GitHub Desktop.
This CLI will advertise a packet EIP through BGP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"flag" | |
"fmt" | |
"net" | |
"os" | |
"strconv" | |
"strings" | |
"time" | |
"github.com/golang/protobuf/ptypes" | |
"github.com/golang/protobuf/ptypes/any" | |
api "github.com/osrg/gobgp/api" | |
gobgp "github.com/osrg/gobgp/pkg/server" | |
"github.com/packethost/packngo" | |
log "github.com/sirupsen/logrus" | |
) | |
func main() { | |
log.SetLevel(log.DebugLevel) | |
token := flag.String("token", "xxx", "The token for the Packet API") | |
project := flag.String("project", "", "The name of a project in Packet") | |
host := flag.String("eip", "", "") | |
flag.Parse() | |
client := packngo.NewClientWithAuth("", *token, nil) | |
proj := findProject(*project, client) | |
if proj == nil { | |
log.Fatalf("Unable to find Project [%s]", *project) | |
} | |
thisDevice := findSelf(client, proj.ID) | |
if thisDevice == nil { | |
log.Fatalf("Unable to find correct device") | |
} | |
fmt.Printf("Querying BGP settings for [%s]", thisDevice.Hostname) | |
neighbours, _, _ := client.Devices.ListBGPNeighbors(thisDevice.ID, &packngo.ListOptions{}) | |
if len(neighbours) > 1 { | |
log.Fatalf("There are [%s] neighbours, only designed to manage one", len(neighbours)) | |
} | |
bgpCfg := &Config{ | |
RouterID: neighbours[0].CustomerIP, | |
AS: uint32(neighbours[0].CustomerAs), | |
} | |
p := &Peer{ | |
Address: neighbours[0].PeerIps[0], | |
AS: uint32(neighbours[0].PeerAs), | |
} | |
bgpCfg.Peers = append(bgpCfg.Peers, *p) | |
s, err := NewBgp(bgpCfg) | |
if err != nil { | |
log.Fatal(err) | |
} | |
err = s.AddHost(*host) | |
if err != nil { | |
log.Fatal(err) | |
} | |
fmt.Println("Enjoy your EIP for 3 whole minutes!") | |
// do something useful here instead of exiting | |
time.Sleep(time.Minute * 3) | |
} | |
func findProject(project string, c *packngo.Client) *packngo.Project { | |
l := &packngo.ListOptions{Includes: []string{project}} | |
ps, _, err := c.Projects.List(l) | |
if err != nil { | |
log.Error(err) | |
} | |
for _, p := range ps { | |
// Find our project | |
if p.Name == project { | |
return &p | |
} | |
} | |
return nil | |
} | |
func findSelf(c *packngo.Client, projectID string) *packngo.Device { | |
// Go through devices | |
dev, _, _ := c.Devices.List(projectID, &packngo.ListOptions{}) | |
for _, d := range dev { | |
me, _ := os.Hostname() | |
if me == d.Hostname { | |
return &d | |
} | |
} | |
return nil | |
} | |
type Peer struct { | |
Address string | |
AS uint32 | |
} | |
type Config struct { | |
AS uint32 | |
RouterID string | |
NextHop string | |
SourceIP string | |
SourceIF string | |
Peers []Peer | |
IPv6 bool | |
} | |
type Server struct { | |
s *gobgp.BgpServer | |
c *Config | |
} | |
func NewBgp(c *Config) (b *Server, err error) { | |
if c.AS == 0 { | |
return nil, fmt.Errorf("You need to provide AS") | |
} | |
if c.SourceIP != "" && c.SourceIF != "" { | |
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") | |
} | |
b = &Server{ | |
s: gobgp.NewBgpServer(), | |
c: c, | |
} | |
go b.s.Serve() | |
if err = b.s.StartBgp(context.Background(), &api.StartBgpRequest{ | |
Global: &api.Global{ | |
As: c.AS, | |
RouterId: c.RouterID, | |
ListenPort: -1, | |
}, | |
}); err != nil { | |
return | |
} | |
if err = b.s.MonitorPeer(context.Background(), &api.MonitorPeerRequest{}, func(p *api.Peer) { log.Println(p) }); err != nil { | |
return | |
} | |
for _, p := range c.Peers { | |
if err = b.AddPeer(p); err != nil { | |
return | |
} | |
} | |
return | |
} | |
func (b *Server) AddPeer(peer Peer) (err error) { | |
port := 179 | |
if t := strings.SplitN(peer.Address, ":", 2); len(t) == 2 { | |
peer.Address = t[0] | |
if port, err = strconv.Atoi(t[1]); err != nil { | |
return fmt.Errorf("Unable to parse port '%s' as int: %s", t[1], err) | |
} | |
} | |
p := &api.Peer{ | |
Conf: &api.PeerConf{ | |
NeighborAddress: peer.Address, | |
PeerAs: peer.AS, | |
}, | |
Timers: &api.Timers{ | |
Config: &api.TimersConfig{ | |
ConnectRetry: 10, | |
}, | |
}, | |
Transport: &api.Transport{ | |
MtuDiscovery: true, | |
RemoteAddress: peer.Address, | |
RemotePort: uint32(port), | |
}, | |
} | |
if b.c.SourceIP != "" { | |
p.Transport.LocalAddress = b.c.SourceIP | |
} | |
if b.c.SourceIF != "" { | |
p.Transport.BindInterface = b.c.SourceIF | |
} | |
return b.s.AddPeer(context.Background(), &api.AddPeerRequest{ | |
Peer: p, | |
}) | |
} | |
func (b *Server) getPath(ip net.IP) *api.Path { | |
var pfxLen uint32 = 32 | |
if ip.To4() == nil { | |
if !b.c.IPv6 { | |
return nil | |
} | |
pfxLen = 128 | |
} | |
nlri, _ := ptypes.MarshalAny(&api.IPAddressPrefix{ | |
Prefix: ip.String(), | |
PrefixLen: pfxLen, | |
}) | |
a1, _ := ptypes.MarshalAny(&api.OriginAttribute{ | |
Origin: 0, | |
}) | |
var nh string | |
if b.c.NextHop != "" { | |
nh = b.c.NextHop | |
} else if b.c.SourceIP != "" { | |
nh = b.c.SourceIP | |
} else { | |
nh = b.c.RouterID | |
} | |
a2, _ := ptypes.MarshalAny(&api.NextHopAttribute{ | |
NextHop: nh, | |
}) | |
return &api.Path{ | |
Family: &api.Family{ | |
Afi: api.Family_AFI_IP, | |
Safi: api.Family_SAFI_UNICAST, | |
}, | |
Nlri: nlri, | |
Pattrs: []*any.Any{a1, a2}, | |
} | |
} | |
func (b *Server) AddHost(addr string) (err error) { | |
ip, _, err := net.ParseCIDR(addr) | |
if err != nil { | |
return err | |
} | |
p := b.getPath(ip) | |
if p == nil { | |
return | |
} | |
_, err = b.s.AddPath(context.Background(), &api.AddPathRequest{ | |
Path: p, | |
}) | |
return | |
} | |
func (b *Server) DelHost(addr string) (err error) { | |
ip, _, err := net.ParseCIDR(addr) | |
if err != nil { | |
return err | |
} | |
p := b.getPath(ip) | |
if p == nil { | |
return | |
} | |
return b.s.DeletePath(context.Background(), &api.DeletePathRequest{ | |
Path: p, | |
}) | |
} | |
func (b *Server) Close() error { | |
ctx, cf := context.WithTimeout(context.Background(), 5*time.Second) | |
defer cf() | |
return b.s.StopBgp(ctx, &api.StopBgpRequest{}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment