# ==== CONSTANTS ====

define OWNAS = <OWNAS>; # your autonomous system number

# the first non-zero IPv4 you control. Since we're only using
# IPv6. Use the first non-zero 32 bits you control. E.g., if
# you control 255::/16, use 2.85.0.1 (255 is two hex bytes, 
# 0x02, and 0x55 which is 2.85 in a v4 addr).
define OWNIP = <OWNIP>;

# the router's actual IPv6 addr that it can be reached by.
define OWNIPv6 = <OWNIPv6>;

# the subnet you control
define OWNNETv6 = <OWNNET>;

# set of all addrs you control.
define OWNNETSETv6 = [<OWNNET>+];

# ===================

router id OWNIP;

# "everything" is a protocol in bird. The `device` protocol is not really
# a protocol, it just instructs bird to ask the kernel for information
# on devices (interfaces).
protocol device {
  # scan devices every 10 seconds.
  scan time 10;
}

# ==== UTILITY FUNCTIONS ====

function is_self_net() {
  return net ~ OWNNETSETv6;
}

function is_valid_network() {
  return net ~ [
    0200::/7+
  ];
}

# Tell bird how to interact with the kernel (modify routes):
#
# Again, not really a protocol but ¯\_(ツ)_/¯
protocol kernel {
  # scan the routing table every 20 seconds (update bird state).
  scan time 20;
  
  # configure ipv6
  ipv6 {
    # Don't import any routes from the kernel, let bird figure out
    # what routes to add or change. `none` is a "filter" that discards
    # routes that are given to it. The kernel gives bird routes and we
    # want bird to ignore them. kernel -> bird.
    import none;
  
    # We write an inline filter that describes what routes to give the
    # kernel from bird. bird -> kernel.
    export filter {
      # if the source of this route is static configuration, then we
      # don't want to give it to the kernel. (only want dynamic routes).
      # I don't know why this is important? Is that right?
      if source = RTS_STATIC then reject;
      # set the `krt_prefsrc` attribute for this route.
      # see comment above above the kernel block.
      krt_prefsrc = OWNIPv6;
      # accept this route (don't filter it out).
      accept;
    };
  };
}

# Static protocol lets you define static routes that do not change.
protocol static {
  # reject any routes in our subnet (I think?)
  route OWNNETv6 reject;

  ipv6 {
    # tell bird about everything. static -> bird.
    import all;
    # bird can't configure static routes (default)?.
    # bird -> static.
    export none;
  };
}

# Create a template for the `bgp` protocol. This is where the real
# magic happens. We use a template since every peer will essentially
# have the same BGP configuration. The template name is `alapeers`.
#
# Each peer gets its own template and file under `peers/*``.
#
# We're using BGP in "exterior" or `eBGP` mode. This means we're using
# the protocol to define routes between Autonomous Systems, not really
# between individual IP addrs.
#
# Each instance of the `bgp` protocol corresponds with one neighboring
# router, or in our case, a peer.
template bgp alapeers {
  # our `as` number is the OWNAS constant. This is defined in the local
  # defs file that comes later.
  local as OWNAS;
  # compare path lengths to determine which route is the best one.
  path metric 1;
  
  ipv6 {
    # only accept routes from peers that follow this inline filter.
    # bgp -> bird.
    import filter {
      # Accept the route if its a valid network and is not our
      # own network. (fns defined in local defs file).
      if is_valid_network() && !is_self_net() then {
        accept;
      }
      reject;
    };
    # only send routes that follow this inline filter.
    # bird -> bgp (other peers).
    export filter {
      # Only send if it's a valid netowrk and the route comes from
      # static configuration or from BGP.
      if is_valid_network() && source ~ [RTS_STATIC, RTS_BGP] then {
        accept;
      }
      reject;
    };
    # Only allow for 1000 total routes to be imported. Once we hit
    # 1000, block further routes. 
    import limit 1000 action block;
  };
}

# Include all of our peers!
include "peers/*";
