Setzen der IPv6-Adresse in der FreeRADIUS-Datenbank bei Nutzung von Mikrotik (RouterOS)

01.05.2018 Netzwerk
 

Einleitung

Bereits mit meiner Bachelorarbeit habe ich IPv6 für einen ISP eingeführt. Kurz darauf wurde es jedoch wieder abgeschaltet, da der DHCPv6-Server der Mikrotik-Geräte abstürzte. Nach mehreren Jahren habe ich mich entschlossen, einen neuen Versuch mit IPv6 auf Mikrotik-Geräten zu wagen.

Hinweis: Die Bilder und Daten stammen alle von meinen persönlichen Testgeräten. Es werden hier keine firmenrelevanten Daten gezeigt. Dieser Artikel geht außerdem davon aus, dass bereits ein funktionierendes System vorhanden ist und behandelt nur die Problematik des fehlenden IPv6-Präfixes in der FreeRADIUS-Datenbank.

IPv6 auf dem Router aktivieren

Auf den Routern muss zuerst das IPv6-Package aktiviert werden:
RouterOS Packages Preview
Es muss ein IPv6-Pool angelegt werden: RouterOS IPv6-Pools Preview
Dieser IPv6-Pool muss im PPPoE-Profil aktiviert werden: RouterOS PPPoE Profile IPv6 Preview
Verbinden sich nun Clients via PPPoE, dann sieht man das Präfix und den Benutzernamen unter IPv6 > DHCP Server > Bindings.

Das Problem

Sehen wir uns nun die MySQL-Datenbank von FreeRADIUS an, so sehen wir, dass das Feld framedipv6address leer ist. Nach einigen Tests hat sich herausgestellt, dass das Problem nicht bei FreeRADIUS sondern RouterOS liegt. Das entsprechende Attribut wird nicht übermittelt. Allerdings muss man in jedem Fall die IPv6-Adresse speichern.

Die Lösung: Eventbasierter Mircoservice für die Mikrotik-API

Da sich vor allem Microservices sehr bequem mit PM2 verwalten lassen, entschloss ich mich, einen NodeJS-Microservice zu schreiben. Aus der Dokumentation von Mikrotik geht hervor, dass mit dem listen-Kommando Änderungen von Router-Seite an den API-Client gepusht werden können.

Nachfolgend zeige ich anhand eines kleinen Beispiels, wie so ein simpler Microservice aussehen könnte.

Laden der Abhängigkeiten und initialisieren von Mikronode:
const MikroNode = require('mikronode');
const device = new MikroNode('10.0.0.5');

Verbinden der MySQL-Datenbank (FreeRADIUS):
var mysql = require('mysql'),
config = require('./config/config.json');

var pool = mysql.createPool({
	connectionLimit: 10,
	host: config.mysql.host,
	port: config.mysql.port,
	user: config.mysql.user,
	password: config.mysql.pass,
	database: config.mysql.db
});

Nun muss sich der Service mit dem Router verbinden:
device.connect().then(function([login]){
	return login('user', 'password');
}).then(function(con){
	// open channel to listen for incoming data
	var chan = con.openChannel('addresses', false);

	// listen to ipv6 dhcp binding changes
	chan.write('/ipv6/dhcp-server/binding/listen');
	chan.data.subscribe(function(data){
		var dataParsed = MikroNode.resultsToObj(data);
		updateRadius(dataParsed);
	}, function(error){
		console.log("Error during listenChannel subscription", error) // This shouldn't be called.
	});
});

Jetzt wird lediglich die Funktion updateRadius benötigt, um den zugehörigen Datensatz im FreeRADIUS-Server zu ersetzen:
function updateRadius(dataParsed, index){
	// only continue if the binding belongs to a pppoe session
	if(typeof dataParsed.server !== 'undefined' && dataParsed.server.substr(0, 7) === '<pppoe-'){
		server = dataParsed.server;
	}else if(typeof dataParsed.interface !== 'undefined' && dataParsed.interface.substr(0, 7) === '<pppoe-'){
		server = dataParsed.interface;
	}else{
		return;
	}

	// extract username from request
	var username = server.substr(7);
	username = username.substr(0, username.length - 1);

	// wait a bit for the session to be written before reading and updating it
	setTimeout(function(){
		pool.query('SELECT `radacctid` FROM `radacct` WHERE `username` = ? AND `acctstoptime` IS NULL ORDER BY `acctstarttime` DESC', [username], function(errSelect, results){
			if(errSelect){
				throw errSelect;
			}
			if(results.length < 1){
				return false;
			}

			pool.query('UPDATE `radacct` SET `framedipv6address` = ? WHERE `radacctid` = ?', [dataParsed.address, results[0].radacctid], function(errUpdate){
				if(errUpdate){
					throw errUpdate;
				}
				console.log('radacctid ', results[0].radacctid, 'got ipv6 prefix', dataParsed.address, 'username', username, index);
			});
		});
	}, 2000);
}

Für jeden Access Concentrator auf dem IPv6 aktiv ist, muss eine Verbindung aufgebaut und gelauscht werden. Auf meinem Testsystem habe ich den Microservice noch um verbessertes Logging und das Auslesen der AC aus einer Datenbank erweitert. Der Dienst läuft so schon seit ein paar Wochen und funktioniert einwandfrei.