Skip to content

Commit

Permalink
add VRRP
Browse files Browse the repository at this point in the history
  • Loading branch information
natesales committed Mar 6, 2021
1 parent 79f3dbd commit 0841b76
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 25 deletions.
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
[![License](https://img.shields.io/github/license/natesales/bcg?style=for-the-badge)](https://choosealicense.com/licenses/gpl-3.0/)
[![Release](https://img.shields.io/github/v/release/natesales/bcg?style=for-the-badge)](https://github.com/natesales/bcg/releases)

The automatic BIRD configuration generator with bogon, IRR, RPKI, and max prefix filtering support.
The automatic router configuration generator for BGP with bogon, IRR, RPKI, and max prefix filtering support.

### Installation

bcg depends on [bird2](https://gitlab.nic.cz/labs/bird/), [GoRTR](https://github.com/cloudflare/gortr), and [bgpq4](https://github.com/bgp/bgpq4). Make sure the `bird` and `gortr` daemons are running and `bgpq4` is in path before running bcg. Releases can be downloaded from Github and from my public code repositories - see https://github.com/natesales/repo for more info. You can also build from source by cloning the repo and running `go build`. It's recommended to run bcg every 12 hours to update IRR prefix lists and PeeringDB prefix limits. Adding `0 */12 * * * /usr/bin/bcg` to your crontab will update the filters at 12 AM and PM. If you're using ZSH you might also be interested in my [birdc completion](https://github.com/natesales/zsh-bird-completions).
bcg depends on [bird2](https://gitlab.nic.cz/labs/bird/), [GoRTR](https://github.com/cloudflare/gortr), [bgpq4](https://github.com/bgp/bgpq4), and optionally [keepalived](https://github.com/acassen/keepalived). Make sure the `bird` and `gortr` daemons are running and `bgpq4` is in path before running bcg. Releases can be downloaded from Github and from my public code repositories - see https://github.com/natesales/repo for more info. You can also build from source by cloning the repo and running `go build`. It's recommended to run bcg every 12 hours to update IRR prefix lists and PeeringDB prefix limits. Adding `0 */12 * * * /usr/bin/bcg` to your crontab will update the filters at 12 AM and PM. If you're using ZSH you might also be interested in my [birdc completion](https://github.com/natesales/zsh-bird-completions).

#### Configuration

Expand Down Expand Up @@ -101,6 +101,10 @@ bcg uses RFC 8092 BGP Large Communities
Peers with type `peer` or `downstream` reject any route with a Tier 1 ASN in path ([Peerlock Lite](https://github.com/job/peerlock)).
#### VRRP
bcg can build [keepalived](https://github.com/acassen/keepalived) configs for VRRP. To enable VRRP, add a `vrrp` config key containing a list of VRRP instances to your bcg config file.
#### Communities
| Large | Meaning |
Expand Down Expand Up @@ -132,7 +136,7 @@ Peers with type `peer` or `downstream` reject any route with a Tier 1 ASN in pat
| filter-default | Should default routes be denied? |
| enable-default | Add static default routes |
#### Peer Configuration Options
#### BGP Peer Configuration Options
| Option | Usage |
| -------------- | --------------------------------------------------------------------------------------------------------- |
Expand All @@ -157,10 +161,19 @@ Peers with type `peer` or `downstream` reject any route with a Tier 1 ASN in pat
| bfd | Enable BFD |
| session-global | String to add to session global config |
| enforce-first-as | Reject routes that don't have the peer ASN as the first ASN in path |
| enforce-peer-nexthop | Reject routes where the next hop doesn't match the neighbor address |
| export-default | Should a default route be sent over the session? (default false) |
| enforce-peer-nexthop | Reject routes where the next hop doesn't match the neighbor address |
| export-default | Should a default route be sent over the session? (default false) |
| no-specifics | Don't send specific routes (default false, make sure to enable export-default or else no routes will be exported) |
| allow-blackholes | Accept community (ASN,1,666) to blackhole /32 and /128 prefixes |
| communities | List of BGP communities to add on export (two comma-separated values per list element; example `0,0`) |
| large-communities | List of BGP large communities to add on export (three comma-separated values per list element; example `0,0,0`) |
| description | Description string (just for human reference) |
#### VRRP instance config options
| Option | Usage |
| ----------- | ------------------------------------------------------------------------------ |
| state | VRRP state (`primary` or `backup`) |
| interface | Interface to run VRRP on |
| vrrid | VRRP Router ID (must be the same for multiple routers in the same VRRP domain |
| priority | VRRP router selection priority |
| vips | List of Virtual IPs |
47 changes: 47 additions & 0 deletions internal/config/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,25 @@ type Peer struct {
PrefixSet6 []string `yaml:"-" toml:"-" json:"-"`
}

// VRRPInstance stores a VRRP instance
type VRRPInstance struct {
State string `yaml:"state" json:"state" toml:"State"`
Interface string `yaml:"interface" json:"interface" toml:"Interface"`
VRRID uint `yaml:"vrrid" json:"vrrid" toml:"VRRID"`
Priority uint8 `yaml:"priority" json:"priority" toml:"Priority"`
VIPs []string `yaml:"vips" json:"vips" toml:"VIPs"`

VIPs4 []string `yaml:"-" json:"-" toml:"-"`
VIPs6 []string `yaml:"-" json:"-" toml:"-"`
}

// Config contains global configuration about this router and BCG instance
type Config struct {
Asn uint `yaml:"asn" toml:"ASN" json:"asn"`
RouterId string `yaml:"router-id" toml:"Router-ID" json:"router-id"`
Prefixes []string `yaml:"prefixes" toml:"Prefixes" json:"prefixes"`
Peers map[string]*Peer `yaml:"peers" toml:"Peers" json:"peers"`
VRRPInstances []*VRRPInstance `yaml:"vrrp" toml:"VRRP" json:"vrrp"`
IrrDb string `yaml:"irrdb" toml:"IRRDB" json:"irrdb"`
RtrServer string `yaml:"rtr-server" toml:"RTR-Server" json:"rtr-server"`
RtrPort int `yaml:"rtr-port" toml:"RTR-Port" json:"rtr-port"`
Expand Down Expand Up @@ -182,5 +195,39 @@ func Load(filename string) (*Config, error) {
setPeerDefaults(name, peer)
}

// Parse VRRP configs
for _, vrrpInstance := range config.VRRPInstances {
// Sort VIPs by address family
for _, vip := range vrrpInstance.VIPs {
ip, _, err := net.ParseCIDR(vip)
if err != nil {
return nil, errorx.Decorate(err, "Invalid VIP")
}

if ip.To4() == nil { // If IPv6
vrrpInstance.VIPs6 = append(vrrpInstance.VIPs6, vip)
} else { // If IPv4
vrrpInstance.VIPs4 = append(vrrpInstance.VIPs4, vip)
}
}

// Validate vrrpInstance
if vrrpInstance.State == "primary" {
vrrpInstance.State = "MASTER"
} else if vrrpInstance.State == "backup" {
vrrpInstance.State = "BACKUP"
} else {
return nil, errors.New("VRRP state must be 'primary' or 'backup', unexpected " + vrrpInstance.State)
}

if vrrpInstance.Interface == "" {
return nil, errors.New("VRRP interface is not defined")
}

if len(vrrpInstance.VIPs) < 1 {
return nil, errors.New("VRRP instance must have at least one VIP defined")
}
}

return &config, nil // nil error
}
7 changes: 7 additions & 0 deletions internal/templating/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ var funcMap = template.FuncMap{
var PeerTemplate *template.Template
var GlobalTemplate *template.Template
var UiTemplate *template.Template
var VRRPTemplate *template.Template

// Load loads the templates from the embedded filesystem
func Load(fs embed.FS) error {
Expand All @@ -95,5 +96,11 @@ func Load(fs embed.FS) error {
return errorx.Decorate(err, "Reading ui template")
}

// Generate VRRP template
VRRPTemplate, err = template.New("").Funcs(funcMap).ParseFS(fs, "templates/vrrp.tmpl")
if err != nil {
return errorx.Decorate(err, "Reading VRRP template")
}

return nil // nil error
}
36 changes: 27 additions & 9 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,16 @@ const (

// Flags
var opts struct {
ConfigFile string `short:"c" long:"config" description:"Configuration file in YAML, TOML, or JSON format" default:"/etc/bcg/config.yml"`
Output string `short:"o" long:"output" description:"Directory to write output files to" default:"/etc/bird/"`
Socket string `short:"s" long:"socket" description:"BIRD control socket" default:"/run/bird/bird.ctl"`
UiFile string `short:"u" long:"ui-file" description:"File to store web UI" default:"/tmp/bcg-ui.html"`
NoUi bool `short:"n" long:"no-ui" description:"Don't generate web UI"`
Verbose bool `short:"v" long:"verbose" description:"Show verbose log messages"`
DryRun bool `short:"d" long:"dry-run" description:"Don't modify BIRD config"`
NoConfigure bool `long:"no-configure" description:"Don't configure BIRD"`
ShowVersion bool `long:"version" description:"Show version and exit"`
ConfigFile string `short:"c" long:"config" description:"Configuration file in YAML, TOML, or JSON format" default:"/etc/bcg/config.yml"`
Output string `short:"o" long:"output" description:"Directory to write output files to" default:"/etc/bird/"`
Socket string `short:"s" long:"socket" description:"BIRD control socket" default:"/run/bird/bird.ctl"`
KeepalivedConfig string `short:"k" long:"keepalived-config" description:"Configuration file for keepalived" default:"/etc/keepalived/keepalived.conf"`
UiFile string `short:"u" long:"ui-file" description:"File to store web UI" default:"/tmp/bcg-ui.html"`
NoUi bool `short:"n" long:"no-ui" description:"Don't generate web UI"`
Verbose bool `short:"v" long:"verbose" description:"Show verbose log messages"`
DryRun bool `short:"d" long:"dry-run" description:"Don't modify BIRD config"`
NoConfigure bool `long:"no-configure" description:"Don't configure BIRD"`
ShowVersion bool `long:"version" description:"Show version and exit"`
}

// Embedded filesystem
Expand Down Expand Up @@ -392,6 +393,23 @@ func main() {
}
}

// Write VRRP config
if !opts.DryRun && len(globalConfig.VRRPInstances) > 0 {
// Create the peer specific file
peerSpecificFile, err := os.Create(path.Join(opts.KeepalivedConfig))
if err != nil {
log.Fatalf("Create peer specific output file: %v", err)
}

// Render the template and write to disk
err = templating.VRRPTemplate.ExecuteTemplate(peerSpecificFile, "vrrp.tmpl", globalConfig.VRRPInstances)
if err != nil {
log.Fatalf("Execute template: %v", err)
}
} else {
log.Infof("Dry run is enabled, not writing VRRP config")
}

if !opts.DryRun {
if !opts.NoUi {
// Create the ui output file
Expand Down
33 changes: 22 additions & 11 deletions templates/vrrp.tmpl
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
vrrp_instance {{ .Name }} {
state {{ .State }}
interface {{ .Interface }}
virtual_router_id {{ .VRRID }}
priority {{ .Priority }}
advert_int 1
virtual_ipaddress {
{{- range $i, $vip := .VIPs4 }}
{{ $vip }}
{{- end }}
}
{{- range $instanceId, $instance := . -}}
vrrp_instance VRRP{{ $instanceId }} {
state {{ .State }}
interface {{ .Interface }}
virtual_router_id {{ .VRRID }}
priority {{ .Priority }}
advert_int 1
{{- if .VIPs4 }}
virtual_ipaddress {
{{- range $i, $vip := .VIPs4 }}
{{ $vip }}
{{- end }}
}
{{- end }}
{{- if .VIPs6 }}
virtual_ipaddress_excluded {
{{- range $i, $vip := .VIPs6 }}
{{ $vip }}
{{- end }}
}
{{- end }}
}
{{- end }}

0 comments on commit 0841b76

Please sign in to comment.