Lua for Network Engineers
Lua scripting for network tools, configuration, and embedded systems.
Why Lua?
-
Embedded everywhere - 200KB footprint, runs on anything
-
Network tool scripting - Wireshark, Nmap, Snort
-
Config language - Neovim, Nginx/OpenResty, Redis
-
Cisco IOS-XE - On-box EEM/scripting
-
Fast - LuaJIT rivals C performance
Neovim Configuration
Your future ~/.config/nvim/init.lua:
-- Set leader key
vim.g.mapleader = " "
-- Line numbers
vim.opt.number = true
vim.opt.relativenumber = true
-- Tabs
vim.opt.tabstop = 4
vim.opt.shiftwidth = 4
vim.opt.expandtab = true
-- Keybinding
vim.keymap.set("n", "<leader>w", ":w<CR>", { desc = "Save file" })
Wireshark Dissector
Custom protocol parser:
-- Register custom protocol
local myproto = Proto("myproto", "My Custom Protocol")
-- Define fields
myproto.fields.command = ProtoField.uint8("myproto.cmd", "Command", base.HEX)
myproto.fields.length = ProtoField.uint16("myproto.len", "Length", base.DEC)
-- Dissector function
function myproto.dissector(buffer, pinfo, tree)
pinfo.cols.protocol = "MYPROTO"
local subtree = tree:add(myproto, buffer(), "My Protocol Data")
subtree:add(myproto.fields.command, buffer(0,1))
subtree:add(myproto.fields.length, buffer(1,2))
end
-- Register on TCP port
local tcp_table = DissectorTable.get("tcp.port")
tcp_table:add(9999, myproto)
Nmap NSE Script
Custom vulnerability check:
description = [[
Checks for default IPMI credentials
]]
author = "EvanusModestus"
categories = {"auth", "default"}
local ipmi = require "ipmi"
local shortport = require "shortport"
portrule = shortport.port_or_service(623, "asf-rmcp", "udp")
action = function(host, port)
local default_users = {"ADMIN", "admin", "root"}
local default_pass = {"ADMIN", "admin", "password"}
for _, user in ipairs(default_users) do
for _, pass in ipairs(default_pass) do
local status, err = ipmi.login(host.ip, user, pass)
if status then
return string.format("DEFAULT CREDS: %s/%s", user, pass)
end
end
end
return "No default credentials found"
end
Cisco IOS-XE EEM Script
On-box automation:
-- Triggered when interface goes down
-- File: flash:eem_interface_down.lua
function main()
local cli = require("cli")
local syslog = require("syslog")
-- Get interface from event
local intf = eem.event_data("interface")
-- Log event
syslog.info("Interface " .. intf .. " went down - running recovery")
-- Attempt recovery
cli.exec("configure terminal")
cli.exec("interface " .. intf)
cli.exec("shutdown")
cli.exec("no shutdown")
cli.exec("end")
syslog.info("Recovery attempt complete for " .. intf)
end
main()
Nginx/OpenResty Request Handling
Dynamic routing:
-- /etc/nginx/lua/auth.lua
local jwt = require "resty.jwt"
local function authenticate()
local token = ngx.var.http_authorization
if not token then
ngx.status = 401
ngx.say("Missing token")
return ngx.exit(401)
end
local verified = jwt:verify("secret", token:gsub("Bearer ", ""))
if not verified.verified then
ngx.status = 403
ngx.say("Invalid token")
return ngx.exit(403)
end
-- Set user for downstream
ngx.var.authenticated_user = verified.payload.sub
end
return { authenticate = authenticate }
Variables and Types
-- Variables (global by default, use 'local')
local name = "kvm-02"
local ip = "10.50.1.111"
local port = 22
local enabled = true
local nothing = nil
-- Tables (arrays AND dictionaries)
local hosts = {"kvm-01", "kvm-02", "kvm-03"}
local config = {
hostname = "kvm-02",
ip = "10.50.1.111",
ports = {22, 443, 5900}
}
-- Access
print(hosts[1]) -- "kvm-01" (1-indexed!)
print(config.hostname) -- "kvm-02"
print(config["ip"]) -- "10.50.1.111"
Control Flow
-- If/else
if status == "up" then
print("Online")
elseif status == "degraded" then
print("Warning")
else
print("Down")
end
-- For loops
for i = 1, 10 do
print(i)
end
-- Iterate table
for key, value in pairs(config) do
print(key .. " = " .. tostring(value))
end
-- Iterate array
for index, host in ipairs(hosts) do
print(index .. ": " .. host)
end
Functions
-- Basic function
local function greet(name)
return "Hello, " .. name
end
-- Multiple returns
local function get_host_info(hostname)
return "10.50.1.111", 22, "Rocky Linux"
end
local ip, port, os = get_host_info("kvm-02")
-- Anonymous function
local process = function(x) return x * 2 end
-- Closures
local function counter()
local count = 0
return function()
count = count + 1
return count
end
end
local c = counter()
print(c()) -- 1
print(c()) -- 2
String Operations
local host = "kvm-02.inside.domusdigitalis.dev"
-- Concatenation
local msg = "Host: " .. host
-- Length
print(#host) -- 33
-- Pattern matching (like regex-lite)
local name = host:match("^([^.]+)") -- "kvm-02"
local domain = host:match("%.(.+)$") -- "inside.domusdigitalis.dev"
-- Split (manual, no built-in)
local function split(str, sep)
local result = {}
for part in str:gmatch("([^" .. sep .. "]+)") do
table.insert(result, part)
end
return result
end
local parts = split(host, ".") -- {"kvm-02", "inside", "domusdigitalis", "dev"}
Lua vs Python vs Bash
| Task | Bash | Python | Lua |
|---|---|---|---|
Embedded scripting |
No |
Heavy |
Yes (200KB) |
Network tool integration |
Limited |
Good |
Native (Wireshark, Nmap) |
Config language |
No |
No |
Yes (Neovim, Nginx) |
Performance |
Slow |
Medium |
Fast (LuaJIT) |
Learning curve |
Medium |
Easy |
Easy |
On-device (Cisco) |
No |
Limited |
Yes |
Learning Path
-
Now: Master bash/awk/sed (current focus)
-
Next: Neovim + Lua config
-
Then: Wireshark Lua dissectors
-
Later: Nmap NSE scripting
-
Advanced: OpenResty/Nginx Lua