Skip to content

Commit 5709152

Browse files
committed
add package bgpdisco
1 parent e51f3b5 commit 5709152

File tree

11 files changed

+883
-0
lines changed

11 files changed

+883
-0
lines changed

packages/bgpdisco/Makefile

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
#
3+
# Copyright (C) 2024 Simon Polack <[email protected]>
4+
#
5+
6+
include $(TOPDIR)/rules.mk
7+
8+
PKG_NAME:=bgpdisco
9+
PKG_VERSION:=1
10+
PKG_RELEASE:=1
11+
12+
PKG_MAINTAINER:=Simon Polack <[email protected]>
13+
PKG_LICENSE:=GPL-2.0-only
14+
15+
16+
include $(INCLUDE_DIR)/package.mk
17+
18+
Build/Compile=
19+
20+
define Package/bgpdisco/default
21+
SECTION:=net
22+
CATEGORY:=Network
23+
TITLE:=BGP node discovery agent
24+
URL:=https://github.com/freifunk-berlin/falter-packages
25+
PKGARCH:=all
26+
endef
27+
28+
define Package/bgpdisco
29+
$(Package/bgpdisco/default)
30+
EXTRA_DEPENDS:= \
31+
bird2, bird2c, \
32+
ucode, ucode-mod-rtnl, ucode-mod-debug \
33+
ucode-mod-uloop, ucode-mod-fs, ucode-mod-struct
34+
endef
35+
36+
define Package/bgpdisco-plugin-nameservice
37+
$(Package/bgpdisco/default)
38+
TITLE+= - Nameservice Plugin
39+
EXTRA_DEPENDS:= bgpdisco
40+
endef
41+
42+
43+
define Package/bgpdisco/conffiles
44+
/etc/config/bgpdisco
45+
endef
46+
47+
define Package/bgpdisco-plugin-nameservice/conffiles
48+
/etc/config/bgpdisco_nameservice
49+
endef
50+
51+
52+
define Package/bgpdisco/install
53+
$(INSTALL_DIR) $(1)/etc/init.d
54+
$(INSTALL_BIN) ./bgpdisco.init $(1)/etc/init.d/bgpdisco
55+
56+
$(INSTALL_DIR) $(1)/etc/uci-defaults
57+
$(INSTALL_BIN) ./bgpdisco.defaults $(1)/etc/uci-defaults/bgpdisco
58+
59+
$(INSTALL_DIR) $(1)/usr/bin
60+
$(INSTALL_BIN) ./bgpdisco.uc $(1)/usr/bin/bgpdisco
61+
62+
$(INSTALL_DIR) $(1)/usr/share/ucode/bgpdisco
63+
$(INSTALL_BIN) ./birdctl.uc $(1)/usr/share/ucode/bgpdisco/birdctl.uc
64+
$(INSTALL_BIN) ./plugin.uc $(1)/usr/share/ucode/bgpdisco/plugin.uc
65+
$(INSTALL_BIN) ./logger.uc $(1)/usr/share/ucode/bgpdisco/logger.uc
66+
$(INSTALL_BIN) ./mrtdump.uc $(1)/usr/share/ucode/bgpdisco/mrtdump.uc
67+
$(INSTALL_BIN) ./bird_config_template.ut $(1)/usr/share/ucode/bgpdisco/bird_config_template.ut
68+
69+
$(INSTALL_DIR) $(1)/usr/share/ucode/bgpdisco/plugins
70+
endef
71+
72+
define Package/bgpdisco-plugin-nameservice/install
73+
$(INSTALL_DIR) $(1)/etc/uci-defaults
74+
$(INSTALL_BIN) ./bgpdisco_nameservice.defaults $(1)/etc/uci-defaults/bgpdisco_nameservice
75+
76+
$(INSTALL_DIR) $(1)/usr/share/ucode/bgpdisco/plugins
77+
$(INSTALL_BIN) ./nameservice.uc $(1)/usr/share/ucode/bgpdisco/plugins/nameservice.uc
78+
endef
79+
80+
$(eval $(call BuildPackage,bgpdisco))
81+
$(eval $(call BuildPackage,bgpdisco-plugin-nameservice))
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/sh
2+
3+
if [ ! -f /etc/config/bgpdisco ]; then
4+
cat <<EOT > /etc/config/bgpdisco
5+
package 'bgpdisco'
6+
7+
config general
8+
option debug '0'
9+
option refresh_remote_data_interval '10'
10+
option nice '19'
11+
12+
config bird
13+
option control_socket '/var/run/bird.ctl'
14+
option config_target '/dev/shm/bird_bgpdisco.conf'
15+
option config_template '/usr/share/ucode/bgpdisco/bird_config_template.ut'
16+
option mrt_file '/tmp/mrt_bgpdisco.dump'
17+
EOT
18+
fi

packages/bgpdisco/bgpdisco.init

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/bin/sh /etc/rc.common
2+
3+
USE_PROCD=1
4+
START=99
5+
STOP=01
6+
7+
start_service() {
8+
9+
config_load bgpdisco
10+
local _nice
11+
local _jail
12+
13+
config_get _nice general nice 19
14+
# config_get _jail general 'jail'
15+
16+
procd_open_instance
17+
procd_set_param command /usr/bin/bgpdisco
18+
procd_set_param stdout 0
19+
procd_set_param stderr 0
20+
procd_set_param nice "$_nice"
21+
procd_close_instance
22+
}
23+
24+
service_stopped() {
25+
echo 'bgpdisco stopped!'
26+
}

packages/bgpdisco/bgpdisco.uc

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
#!/usr/bin/ucode
2+
3+
import * as rtnl from 'rtnl';
4+
import * as uloop from 'uloop';
5+
import * as fs from 'fs';
6+
import * as uci from 'uci';
7+
import * as mrtdump from 'bgpdisco.mrtdump';
8+
import * as plugin from 'bgpdisco.plugin';
9+
import * as birdctl from 'bgpdisco.birdctl';
10+
import { DBG, INFO, WARN, ERR, enable_debug } from 'bgpdisco.logger';
11+
12+
let tty;
13+
let bird;
14+
let plugins;
15+
16+
let monitor_interfaces = [];
17+
18+
let cache_neighbors;
19+
let cache_data;
20+
21+
let timer_refresh_remote_data;
22+
23+
let cfg = {
24+
// General
25+
general_debug: false,
26+
general_plugin_directory: '/usr/share/ucode/bgpdisco/plugins/',
27+
general_enable_plugins: [],
28+
general_refresh_remote_data_interval: 60,
29+
// Bird
30+
bird_control_socket: '/var/run/bird.ctl',
31+
bird_config_target: '/dev/shm/bird_bgpdisco.conf',
32+
bird_config_template: '/usr/share/ucode/bgpdisco/bird_config_template.ut',
33+
bird_mrt_file: '/tmp/mrt_bgpdisco.dump',
34+
};
35+
36+
function render_bird_config() {
37+
DBG('render_bird_config()');
38+
return render(cfg.bird_config_template, proto({
39+
index: index, hexenc, replace,
40+
neighbors: cache_neighbors, data: plugins.provide_data()
41+
}, {}));
42+
}
43+
44+
function configure_bird() {
45+
DBG('configure_bird()');
46+
47+
let config = render_bird_config();
48+
49+
DBG('write rendered config');
50+
if (!fs.writefile(cfg.bird_config_target, config)) {
51+
ERR('cant write bird config to filesystem');
52+
}
53+
54+
INFO('triggering bird config reload');
55+
bird.cmd('configure');
56+
}
57+
58+
function retrieve_data_from_bird() {
59+
DBG('retrieve_data_from_bird()');
60+
61+
DBG('remove old mrt dump');
62+
fs.unlink(cfg.bird_mrt_file);
63+
64+
DBG('request new mrt dump');
65+
bird.cmd('mrt dump table "*_bgpdisco" to "' + cfg.bird_mrt_file + '"');
66+
67+
68+
DBG('parse mrt dump');
69+
let data = mrtdump.get_routes(cfg.bird_mrt_file);
70+
71+
DBG('processing routes');
72+
let parsed_data = {};
73+
for (let route in data) {
74+
if (index(keys(route.attributes), '250') == -1) {
75+
DBG('skip route due to missing magic attribute: %s', route);
76+
continue;
77+
}
78+
let ip = split(route.prefix, '/')[0];
79+
for (let darr in json(route.attributes['250'])) {
80+
let id = shift(darr);
81+
parsed_data[id] ??= {};
82+
if (index(keys(parsed_data[id]), ip) != -1)
83+
DBG('route is already parsed, do we have stale routes in the network?: %s', route);
84+
parsed_data[id][ip] = darr;
85+
}
86+
}
87+
return parsed_data;
88+
}
89+
90+
function sync_peers() {
91+
DBG('sync_peers()');
92+
let neighbors = bird.get_babel_neighbors();
93+
if (sprintf('%s', neighbors) == sprintf('%s', cache_neighbors)) {
94+
DBG('No change in babel neighbors - no sync required');
95+
return;
96+
}
97+
INFO('Babel neighbors have changed - sync to BGP');
98+
cache_neighbors = neighbors;
99+
configure_bird();
100+
}
101+
102+
function cb_refresh_remote_data() {
103+
DBG('cb_refresh_remote_data()');
104+
let data = retrieve_data_from_bird();
105+
if (sprintf('%s', data) == sprintf('%s', cache_data)) {
106+
DBG('Received data matches cache - no need to trigger handler plugins');
107+
return;
108+
};
109+
INFO('Received data differs from cache - trigger handler plugins');
110+
cache_data = data;
111+
plugins.handle_data(data);
112+
}
113+
114+
function trigger_refresh_remote_data() {
115+
cb_refresh_remote_data();
116+
timer_refresh_remote_data.set(cfg.general_refresh_remote_data_interval*1000);
117+
}
118+
119+
function cb_nl_newneigh(ev) {
120+
DBG('cb_nl_newneigh(msg.dev=%s)', ev.msg.dev);
121+
// Not necessary, coz we filter on listener registration
122+
// if (ev.cmd != rtnl.const.RTM_NEWNEIGH):
123+
// log('RTM_ADDNEIGH');
124+
// }
125+
126+
// Ignore other Families
127+
if (ev.msg.family != rtnl.const.AF_INET6) {
128+
return;
129+
}
130+
131+
// Ignore other interfaces
132+
if (length(cfg.neighbor_sync_monitor_interfaces) > 0) {
133+
if (!(ev.msg.dev in cfg.neighbor_sync_monitor_interfaces))
134+
return;
135+
}
136+
137+
// Ignore other state changes than reachable
138+
if (ev.msg.state != rtnl.const.NUD_REACHABLE) {
139+
return;
140+
}
141+
142+
// Ignore other IPs than link local
143+
if (substr(ev.msg.dst, 0, 4) != 'fe80') {
144+
return;
145+
}
146+
147+
DBG('Learned new neighbor - triggering peer syncronization. IP: %s, Dev:', ev.msg.dst, ev.msg.dev);
148+
sync_peers();
149+
}
150+
151+
function uci_config() {
152+
function string(val) {
153+
return val;
154+
}
155+
function bool(val) {
156+
return int(val) == 1;
157+
}
158+
159+
function OPT(section, option, type) {
160+
let name = section['.type'];
161+
let cfg_val = section[option];
162+
if (option in section) {
163+
let val = call(type, null, null, cfg_val);
164+
DBG("Config: Reading option %s - %s with value %s -> %s", option, name, cfg_val, val);
165+
cfg[name + '_' + option] = call(type, null, null, section[option]);
166+
}
167+
}
168+
function handle_section(s) {
169+
let t = s['.type'];
170+
DBG('Config: handling section %s', t);
171+
switch (t) {
172+
case 'general':
173+
OPT(s, 'debug', bool);
174+
OPT(s, 'refresh_remote_data_interval', int);
175+
break;
176+
case 'bird':
177+
OPT(s, 'control_socket', string);
178+
OPT(s, 'config_target', string);
179+
OPT(s, 'config_template', string);
180+
OPT(s, 'mrt_file', string);
181+
break;
182+
default:
183+
ERR('Ignoring unknown section "%s" while parsing configuration', t);
184+
}
185+
}
186+
let ctx = uci.cursor();
187+
ctx.foreach('bgpdisco', null, handle_section);
188+
}
189+
190+
INFO('Start');
191+
192+
uci_config();
193+
194+
if (cfg.general_debug)
195+
enable_debug();
196+
197+
bird = birdctl.init(cfg.bird_control_socket);
198+
199+
plugins = plugin.init(cfg.general_plugin_directory);
200+
201+
// setup refresh data timer with initial timer of 5s, to let the peers get syncronized before
202+
timer_refresh_remote_data = uloop.timer(5000, trigger_refresh_remote_data);
203+
204+
monitor_interfaces = bird.get_babel_interfaces();
205+
INFO('Monitoring following interfaces: %s', join(monitor_interfaces, ', '));
206+
207+
if (length(monitor_interfaces) == 0)
208+
WARN('Warning, couldnt retrieve babel interfacs from bird. Listening on all interfaces');
209+
210+
INFO('Enabling monitoring for new neighbors');
211+
rtnl.listener(cb_nl_newneigh, [rtnl.const.RTM_NEWNEIGH], [rtnl.const.RTNLGRP_NEIGH], {});
212+
213+
// sync peers right away
214+
sync_peers();
215+
216+
// get the party started :)
217+
uloop.run();
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/sh
2+
3+
if [ ! -f /etc/config/bgpdisco_nameservice ]; then
4+
cat <<EOT > /etc/config/bgpdisco_nameservice
5+
package 'bgpdisco-plugin-nameservice'
6+
7+
config general
8+
option domain 'ff'
9+
option hosts_file '/var/hosts/ffnameservice'
10+
option cmd_on_update 'killall -SIGHUP dnsmasq'
11+
12+
#config static-entry
13+
# option host 'sama-xyz'
14+
# list ip '1.2.3.4'
15+
# list ip '2.3.4.5'
16+
EOT
17+
fi

0 commit comments

Comments
 (0)