Skip to content

Commit

Permalink
add package bgpdisco
Browse files Browse the repository at this point in the history
  • Loading branch information
spolack committed Nov 11, 2024
1 parent e51f3b5 commit 5709152
Show file tree
Hide file tree
Showing 11 changed files with 883 additions and 0 deletions.
81 changes: 81 additions & 0 deletions packages/bgpdisco/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Copyright (C) 2024 Simon Polack <spolack+git@mailbox.org>
#

include $(TOPDIR)/rules.mk

PKG_NAME:=bgpdisco
PKG_VERSION:=1
PKG_RELEASE:=1

PKG_MAINTAINER:=Simon Polack <spolack+git@mailbox.org>
PKG_LICENSE:=GPL-2.0-only


include $(INCLUDE_DIR)/package.mk

Build/Compile=

define Package/bgpdisco/default
SECTION:=net
CATEGORY:=Network
TITLE:=BGP node discovery agent
URL:=https://github.com/freifunk-berlin/falter-packages
PKGARCH:=all
endef

define Package/bgpdisco
$(Package/bgpdisco/default)
EXTRA_DEPENDS:= \
bird2, bird2c, \
ucode, ucode-mod-rtnl, ucode-mod-debug \
ucode-mod-uloop, ucode-mod-fs, ucode-mod-struct
endef

define Package/bgpdisco-plugin-nameservice
$(Package/bgpdisco/default)
TITLE+= - Nameservice Plugin
EXTRA_DEPENDS:= bgpdisco
endef


define Package/bgpdisco/conffiles
/etc/config/bgpdisco
endef

define Package/bgpdisco-plugin-nameservice/conffiles
/etc/config/bgpdisco_nameservice
endef


define Package/bgpdisco/install
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./bgpdisco.init $(1)/etc/init.d/bgpdisco

$(INSTALL_DIR) $(1)/etc/uci-defaults
$(INSTALL_BIN) ./bgpdisco.defaults $(1)/etc/uci-defaults/bgpdisco

$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) ./bgpdisco.uc $(1)/usr/bin/bgpdisco

$(INSTALL_DIR) $(1)/usr/share/ucode/bgpdisco
$(INSTALL_BIN) ./birdctl.uc $(1)/usr/share/ucode/bgpdisco/birdctl.uc
$(INSTALL_BIN) ./plugin.uc $(1)/usr/share/ucode/bgpdisco/plugin.uc
$(INSTALL_BIN) ./logger.uc $(1)/usr/share/ucode/bgpdisco/logger.uc
$(INSTALL_BIN) ./mrtdump.uc $(1)/usr/share/ucode/bgpdisco/mrtdump.uc
$(INSTALL_BIN) ./bird_config_template.ut $(1)/usr/share/ucode/bgpdisco/bird_config_template.ut

$(INSTALL_DIR) $(1)/usr/share/ucode/bgpdisco/plugins
endef

define Package/bgpdisco-plugin-nameservice/install
$(INSTALL_DIR) $(1)/etc/uci-defaults
$(INSTALL_BIN) ./bgpdisco_nameservice.defaults $(1)/etc/uci-defaults/bgpdisco_nameservice

$(INSTALL_DIR) $(1)/usr/share/ucode/bgpdisco/plugins
$(INSTALL_BIN) ./nameservice.uc $(1)/usr/share/ucode/bgpdisco/plugins/nameservice.uc
endef

$(eval $(call BuildPackage,bgpdisco))
$(eval $(call BuildPackage,bgpdisco-plugin-nameservice))
18 changes: 18 additions & 0 deletions packages/bgpdisco/bgpdisco.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/sh

if [ ! -f /etc/config/bgpdisco ]; then
cat <<EOT > /etc/config/bgpdisco
package 'bgpdisco'
config general
option debug '0'
option refresh_remote_data_interval '10'
option nice '19'
config bird
option control_socket '/var/run/bird.ctl'
option config_target '/dev/shm/bird_bgpdisco.conf'
option config_template '/usr/share/ucode/bgpdisco/bird_config_template.ut'
option mrt_file '/tmp/mrt_bgpdisco.dump'
EOT
fi
26 changes: 26 additions & 0 deletions packages/bgpdisco/bgpdisco.init
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh /etc/rc.common

USE_PROCD=1
START=99
STOP=01

start_service() {

config_load bgpdisco
local _nice
local _jail

config_get _nice general nice 19
# config_get _jail general 'jail'

procd_open_instance
procd_set_param command /usr/bin/bgpdisco
procd_set_param stdout 0
procd_set_param stderr 0
procd_set_param nice "$_nice"
procd_close_instance
}

service_stopped() {
echo 'bgpdisco stopped!'
}
217 changes: 217 additions & 0 deletions packages/bgpdisco/bgpdisco.uc
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
#!/usr/bin/ucode

import * as rtnl from 'rtnl';
import * as uloop from 'uloop';
import * as fs from 'fs';
import * as uci from 'uci';
import * as mrtdump from 'bgpdisco.mrtdump';
import * as plugin from 'bgpdisco.plugin';
import * as birdctl from 'bgpdisco.birdctl';
import { DBG, INFO, WARN, ERR, enable_debug } from 'bgpdisco.logger';

let tty;
let bird;
let plugins;

let monitor_interfaces = [];

let cache_neighbors;
let cache_data;

let timer_refresh_remote_data;

let cfg = {
// General
general_debug: false,
general_plugin_directory: '/usr/share/ucode/bgpdisco/plugins/',
general_enable_plugins: [],
general_refresh_remote_data_interval: 60,
// Bird
bird_control_socket: '/var/run/bird.ctl',
bird_config_target: '/dev/shm/bird_bgpdisco.conf',
bird_config_template: '/usr/share/ucode/bgpdisco/bird_config_template.ut',
bird_mrt_file: '/tmp/mrt_bgpdisco.dump',
};

function render_bird_config() {
DBG('render_bird_config()');
return render(cfg.bird_config_template, proto({
index: index, hexenc, replace,
neighbors: cache_neighbors, data: plugins.provide_data()
}, {}));
}

function configure_bird() {
DBG('configure_bird()');

let config = render_bird_config();

DBG('write rendered config');
if (!fs.writefile(cfg.bird_config_target, config)) {
ERR('cant write bird config to filesystem');
}

INFO('triggering bird config reload');
bird.cmd('configure');
}

function retrieve_data_from_bird() {
DBG('retrieve_data_from_bird()');

DBG('remove old mrt dump');
fs.unlink(cfg.bird_mrt_file);

DBG('request new mrt dump');
bird.cmd('mrt dump table "*_bgpdisco" to "' + cfg.bird_mrt_file + '"');


DBG('parse mrt dump');
let data = mrtdump.get_routes(cfg.bird_mrt_file);

DBG('processing routes');
let parsed_data = {};
for (let route in data) {
if (index(keys(route.attributes), '250') == -1) {
DBG('skip route due to missing magic attribute: %s', route);
continue;
}
let ip = split(route.prefix, '/')[0];
for (let darr in json(route.attributes['250'])) {
let id = shift(darr);
parsed_data[id] ??= {};
if (index(keys(parsed_data[id]), ip) != -1)
DBG('route is already parsed, do we have stale routes in the network?: %s', route);
parsed_data[id][ip] = darr;
}
}
return parsed_data;
}

function sync_peers() {
DBG('sync_peers()');
let neighbors = bird.get_babel_neighbors();
if (sprintf('%s', neighbors) == sprintf('%s', cache_neighbors)) {
DBG('No change in babel neighbors - no sync required');
return;
}
INFO('Babel neighbors have changed - sync to BGP');
cache_neighbors = neighbors;
configure_bird();
}

function cb_refresh_remote_data() {
DBG('cb_refresh_remote_data()');
let data = retrieve_data_from_bird();
if (sprintf('%s', data) == sprintf('%s', cache_data)) {
DBG('Received data matches cache - no need to trigger handler plugins');
return;
};
INFO('Received data differs from cache - trigger handler plugins');
cache_data = data;
plugins.handle_data(data);
}

function trigger_refresh_remote_data() {
cb_refresh_remote_data();
timer_refresh_remote_data.set(cfg.general_refresh_remote_data_interval*1000);
}

function cb_nl_newneigh(ev) {
DBG('cb_nl_newneigh(msg.dev=%s)', ev.msg.dev);
// Not necessary, coz we filter on listener registration
// if (ev.cmd != rtnl.const.RTM_NEWNEIGH):
// log('RTM_ADDNEIGH');
// }

// Ignore other Families
if (ev.msg.family != rtnl.const.AF_INET6) {
return;
}

// Ignore other interfaces
if (length(cfg.neighbor_sync_monitor_interfaces) > 0) {
if (!(ev.msg.dev in cfg.neighbor_sync_monitor_interfaces))
return;
}

// Ignore other state changes than reachable
if (ev.msg.state != rtnl.const.NUD_REACHABLE) {
return;
}

// Ignore other IPs than link local
if (substr(ev.msg.dst, 0, 4) != 'fe80') {
return;
}

DBG('Learned new neighbor - triggering peer syncronization. IP: %s, Dev:', ev.msg.dst, ev.msg.dev);
sync_peers();
}

function uci_config() {
function string(val) {
return val;
}
function bool(val) {
return int(val) == 1;
}

function OPT(section, option, type) {
let name = section['.type'];
let cfg_val = section[option];
if (option in section) {
let val = call(type, null, null, cfg_val);
DBG("Config: Reading option %s - %s with value %s -> %s", option, name, cfg_val, val);
cfg[name + '_' + option] = call(type, null, null, section[option]);
}
}
function handle_section(s) {
let t = s['.type'];
DBG('Config: handling section %s', t);
switch (t) {
case 'general':
OPT(s, 'debug', bool);
OPT(s, 'refresh_remote_data_interval', int);
break;
case 'bird':
OPT(s, 'control_socket', string);
OPT(s, 'config_target', string);
OPT(s, 'config_template', string);
OPT(s, 'mrt_file', string);
break;
default:
ERR('Ignoring unknown section "%s" while parsing configuration', t);
}
}
let ctx = uci.cursor();
ctx.foreach('bgpdisco', null, handle_section);
}

INFO('Start');

uci_config();

if (cfg.general_debug)
enable_debug();

bird = birdctl.init(cfg.bird_control_socket);

plugins = plugin.init(cfg.general_plugin_directory);

// setup refresh data timer with initial timer of 5s, to let the peers get syncronized before
timer_refresh_remote_data = uloop.timer(5000, trigger_refresh_remote_data);

monitor_interfaces = bird.get_babel_interfaces();
INFO('Monitoring following interfaces: %s', join(monitor_interfaces, ', '));

if (length(monitor_interfaces) == 0)
WARN('Warning, couldnt retrieve babel interfacs from bird. Listening on all interfaces');

INFO('Enabling monitoring for new neighbors');
rtnl.listener(cb_nl_newneigh, [rtnl.const.RTM_NEWNEIGH], [rtnl.const.RTNLGRP_NEIGH], {});

// sync peers right away
sync_peers();

// get the party started :)
uloop.run();
17 changes: 17 additions & 0 deletions packages/bgpdisco/bgpdisco_nameservice.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/sh

if [ ! -f /etc/config/bgpdisco_nameservice ]; then
cat <<EOT > /etc/config/bgpdisco_nameservice
package 'bgpdisco-plugin-nameservice'
config general
option domain 'ff'
option hosts_file '/var/hosts/ffnameservice'
option cmd_on_update 'killall -SIGHUP dnsmasq'
#config static-entry
# option host 'sama-xyz'
# list ip '1.2.3.4'
# list ip '2.3.4.5'
EOT
fi
Loading

0 comments on commit 5709152

Please sign in to comment.