X-Git-Url: https://git.sommitrealweird.co.uk/quagga-debian.git/blobdiff_plain/6d99eb2938e976229cb50d848a1bc491532b54f9..110ea29b8fe7d11de09e19382a9a5808cfb90d32:/nhrpd/nhrp-events.lua diff --git a/nhrpd/nhrp-events.lua b/nhrpd/nhrp-events.lua deleted file mode 100755 index e5e3bbf..0000000 --- a/nhrpd/nhrp-events.lua +++ /dev/null @@ -1,272 +0,0 @@ -#!/usr/bin/lua5.2 - --- Example NHRP events processing script which validates --- NHRP registration GRE address against certificate subjectAltName IP --- and auto-creates BGP pairings and filters based on sbgp extensions. - --- Depends on lua5.2 lua5.2-posix lua5.2-cqueues lua5.2-ossl lua-asn1 - -local posix = require 'posix' -local struct = require 'struct' -local cq = require 'cqueues' -local cqs = require 'cqueues.socket' -local x509 = require 'openssl.x509' -local x509an = require 'openssl.x509.altname' -local rfc3779 = require 'asn1.rfc3779' - -local SOCK = "/var/run/nhrp-events.sock" -posix.unlink(SOCK) - -local loop = cq.new() -local nulfd = posix.open("/dev/null", posix.O_RDWR) -local listener = cqs.listen{path=SOCK} - -posix.chown(SOCK, "quagga", "quagga") -posix.setpid("u", "quagga") -posix.setpid("g", "quagga") -posix.openlog("nhrp-events", "np") - -function string.hex2bin(str) - return str:gsub('..', function(cc) return string.char(tonumber(cc, 16)) end) -end - -local function decode_ext(cert, name, tpe) - local ext = cert:getExtension(name) - if not ext then return end - return tpe.decode(ext:getData()) -end - -local function do_parse_cert(cert, out) - for type, value in pairs(cert:getSubjectAlt()) do - if type == 'IP' then - table.insert(out.GRE, value) - end - end - if #out.GRE == 0 then return end - - local asn = decode_ext(cert, 'sbgp-autonomousSysNum', rfc3779.ASIdentifiers) - if asn and asn.asnum and asn.asnum.asIdsOrRanges then - for _, as in ipairs(asn.asnum.asIdsOrRanges) do - if as.id then - out.AS = tonumber(as.id) - break - end - end - end - - local addrBlocks = decode_ext(cert, 'sbgp-ipAddrBlock', rfc3779.IPAddrBlocks) - for _, ab in ipairs(addrBlocks or {}) do - if ab.ipAddressChoice and ab.ipAddressChoice.addressesOrRanges then - for _, a in ipairs(ab.ipAddressChoice.addressesOrRanges) do - if a.addressPrefix then - table.insert(out.NET, a.addressPrefix) - end - end - end - end - - return true -end - -local function parse_cert(certhex) - local out = { - cn = "(no CN)", - AS = 0, - GRE = {}, - NET = {}, - } - local cert = x509.new(certhex:hex2bin(), 'der') - out.cn = tostring(cert:getSubject()) - -- Recognize hubs by certificate's CN to have OU=Hubs - out.hub = out.cn:match("/OU=Hubs/") and true or nil - do_parse_cert(cert, out) - return out -end - -local function execute(desc, cmd, ...) - local piper, pipew = posix.pipe() - if piper == nil then - return error("Pipe failed") - end - - local pid = posix.fork() - if pid == -1 then - return error("Fork failed") - end - if pid == 0 then - posix.close(piper) - posix.dup2(nulfd, 0) - posix.dup2(pipew, 1) - posix.dup2(nulfd, 2) - posix.execp(cmd, ...) - os.exit(1) - end - posix.close(pipew) - - -- This blocks -- perhaps should handle command executions in separate queue. - local output = {} - while true do - local d = posix.read(piper, 8192) - if d == nil or d == "" then break end - table.insert(output, d) - end - posix.close(piper) - - local _, reason, status = posix.wait(pid) - if status == 0 then - posix.syslog(6, ("Executed '%s' successfully"):format(desc)) - else - posix.syslog(3, ("Failed to execute '%s': %s %d"):format(desc, reason, status)) - end - return status, table.concat(output) -end - -local function configure_bgp(desc, ...) - local args = { - "-d", "bgpd", - "-c", "configure terminal", - } - for _, val in ipairs({...}) do - table.insert(args, "-c") - table.insert(args, val) - end - return execute(desc, "vtysh", table.unpack(args)) -end - -local last_bgp_reset = 0 - -local function bgp_reset(msg, local_cert) - local now = os.time() - if last_bgp_reset + 60 > now then return end - last_bgp_reset = now - - configure_bgp("spoke reset", - "route-map RTT-SET permit 10", "set metric rtt", "exit", - "route-map RTT-ADD permit 10", "set metric +rtt", "exit", - ("router bgp %d"):format(local_cert.AS), - "no neighbor hubs", - "neighbor hubs peer-group", - "neighbor hubs remote-as 65000", - "neighbor hubs ebgp-multihop 1", - "neighbor hubs disable-connected-check", - "neighbor hubs timers 10 30", - "neighbor hubs timers connect 10", - "neighbor hubs next-hop-self all", - "neighbor hubs soft-reconfiguration inbound", - "neighbor hubs route-map RTT-ADD in") -end - -local function bgp_nhs_up(msg, remote_cert, local_cert) - configure_bgp(("nhs-up %s"):format(msg.remote_addr), - ("router bgp %s"):format(local_cert.AS), - ("neighbor %s peer-group hubs"):format(msg.remote_addr)) -end - -local function bgp_nhs_down(msg, remote_cert, local_cert) - configure_bgp(("nhs-down %s"):format(msg.remote_addr), - ("router bgp %s"):format(local_cert.AS), - ("no neighbor %s"):format(msg.remote_addr)) -end - -local function bgp_create_spoke_rules(msg, remote_cert, local_cert) - if not local_cert.hub then return end - - local bgpcfg = {} - for seq, net in ipairs(remote_cert.NET) do - table.insert(bgpcfg, - ("ip prefix-list net-%s-in seq %d permit %s le %d"):format( - msg.remote_addr, seq * 5, net, - remote_cert.hub and 32 or 26)) - end - table.insert(bgpcfg, ("router bgp %s"):format(local_cert.AS)) - if remote_cert.hub then - table.insert(bgpcfg, ("neighbor %s peer-group hubs"):format(msg.remote_addr)) - elseif local_cert.AS == remote_cert.AS then - table.insert(bgpcfg, ("neighbor %s peer-group spoke-ibgp"):format(msg.remote_addr)) - else - table.insert(bgpcfg, ("neighbor %s remote-as %s"):format(msg.remote_addr, remote_cert.AS)) - table.insert(bgpcfg, ("neighbor %s peer-group spoke-ebgp"):format(msg.remote_addr)) - end - table.insert(bgpcfg, ("neighbor %s prefix-list net-%s-in in"):format(msg.remote_addr, msg.remote_addr)) - - local status, output = configure_bgp(("nhc-register %s"):format(msg.remote_addr), table.unpack(bgpcfg)) - if output:find("Cannot") then - posix.syslog(6, "BGP: "..output) - configure_bgp( - ("nhc-recreate %s"):format(msg.remote_addr), - ("router bgp %s"):format(local_cert.AS), - ("no neighbor %s"):format(msg.remote_addr), - table.unpack(bgpcfg)) - end -end - -local function handle_message(msg) - if msg.event ~= "authorize-binding" then return end - - -- Verify protocol address against certificate - local auth = false - local local_cert = parse_cert(msg.local_cert) - local remote_cert = parse_cert(msg.remote_cert) - for _, gre in pairs(remote_cert.GRE) do - if gre == msg.remote_addr then auth = true end - end - if not auth then - posix.syslog(3, ("GRE %s to NBMA %s DENIED (cert '%s', allows: %s)"):format( - msg.remote_addr, msg.remote_nbma, - remote_cert.cn, table.concat(remote_cert.GRE, " "))) - return "deny" - end - posix.syslog(6, ("GRE %s to NBMA %s authenticated for %s"):format( - msg.remote_addr, msg.remote_nbma, remote_cert.cn)) - - -- Automatic BGP binding for hub-spoke connections - if msg.type == "nhs" and msg.old_type ~= "nhs" then - if not local_cert.hub then - if tonumber(msg.num_nhs) == 0 and msg.vc_initiated == "yes" then - bgp_reset(msg, local_cert) - end - bgp_nhs_up(msg, remote_cert, local_cert) - else - bgp_create_spoke_rules(msg, remote_cert, local_cert) - end - elseif msg.type ~= "nhs" and msg.old_type == "nhs" then - bgp_nhs_down(msg, remote_cert, local_cert) - elseif msg.type == "dynamic" and msg.old_type ~= "dynamic" then - bgp_create_spoke_rules(msg, remote_cert, local_cert) - end - - return "accept" -end - -local function handle_connection(conn) - local msg = {} - for l in conn:lines() do - if l == "" then - res = handle_message(msg) - if msg.eventid then - conn:write(("eventid=%s\nresult=%s\n\n"):format(msg.eventid, res or "default")) - end - msg = {} - else - local key, value = l:match('([^=]*)=(.*)') - if key and value then - msg[key] = value - end - end - end - conn:close() -end - -loop:wrap(function() - while true do - local conn = listener:accept() - conn:setmode("b", "bl") - loop:wrap(function() - local ok, msg = pcall(handle_connection, conn) - if not ok then posix.syslog(3, msg) end - conn:close() - end) - end -end) - -print(loop:loop())