Skip to content

Instantly share code, notes, and snippets.

@thebsdbox
Created August 27, 2020 13:43
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thebsdbox/7a6053f2d5b9a5a8ad66e6fc5ef7bb2d to your computer and use it in GitHub Desktop.
Save thebsdbox/7a6053f2d5b9a5a8ad66e6fc5ef7bb2d to your computer and use it in GitHub Desktop.
This CLI will advertise a packet EIP through BGP
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