If you've spent any time configuring user authentication on... Full Story
By Manny Fernandez
October 19, 2026
Lua Primer for Security Engineers: The Language Behind Nmap, Wireshark and Snort 3
Executive Summary
Objective: Get you from zero to writing useful Lua in one sitting. This Lua primer covers the whole core language (types, strings and patterns, tables, functions, errors, modules, metatables), then puts it to work parsing a FortiGate traffic log and writing a first Nmap NSE script.
Target audience: Network and security engineers who script in Bash or Python and keep running into Lua inside their tools: Nmap scripts, Wireshark dissectors, Snort 3 configs, Redis, HAProxy, OpenResty and Neovim.
Why bother: Lua is small enough to learn in an afternoon. The whole reference manual is shorter than most vendor release notes, and once you know it you can read and modify the scripts that already drive half your security toolbox.
Why Lua Keeps Showing Up in Security Tools
Lua is an embeddable scripting language. The interpreter is a small C library that a host application links in, so the host stays fast in C while users extend it in Lua. That is exactly the model security tools want: a compiled engine plus user-written logic.
| Tool | What Lua does there |
|---|---|
| Nmap (NSE) | Every --script is a Lua file: service detection, vuln checks, brute forcing |
| Wireshark / tshark | Custom protocol dissectors and post-dissectors without recompiling |
| Snort 3 | The main config file snort.lua is a Lua program |
| Suricata | Lua scripts for custom detection logic and output |
| Redis | EVAL runs Lua server-side, atomically |
| HAProxy, NGINX/OpenResty | Request inspection, custom auth, header rewriting at the proxy |
| Neovim | Configuration and plugins |
It also cuts the other way: the Flame espionage malware famously used Lua for its modules. If you do reverse engineering or threat research, being able to read Lua is a real skill.
Prerequisites and Architecture
Assumed knowledge: Comfortable in a terminal. Some scripting experience (Bash, Python, PowerShell) helps but is not required.
Lab requirements: Any Linux, macOS or WSL box. No network access needed except for the optional Nmap step, which uses an internal lab host in 10.0.0.0/16.
| Component | Version used | Notes |
|---|---|---|
| Lua (PUC-Rio reference interpreter) | 5.4.x | What Ubuntu, Debian and Homebrew ship; what Nmap embeds |
| Lua 5.5 | 5.5.x | Current upstream release; everything in this post runs on it |
| LuaJIT | 2.1 | Lua 5.1 dialect plus extensions; used by OpenResty and many game engines |
| luac | matches Lua | Bytecode compiler; we use luac -p as a syntax checker |
| Nmap | 7.9x | Optional, for the NSE step |
Version notes that will bite you:
- Lua 5.3 added real integers and the bitwise operators (
&,|,~,<<,>>) plus floor division//. LuaJIT and Lua 5.1 code does not have these. unpack()moved totable.unpack()in 5.2.- Lua 5.5 adds explicit
globaldeclarations and makesforloop variables read only. Do not assign to a loop counter inside the loop and your code stays portable.

The diagram is the mental model for the rest of this post: the host (Nmap, Wireshark, Redis) owns the process, creates a Lua state, injects its own libraries, and calls into your script. Your script never touches the network or disk directly unless the host or the standard library hands it a function that does.
Step-by-Step Implementation
Every example below is a standalone file. Create a working folder, save each snippet with the file name in its first comment, and run it with lua.
Step 1: Install Lua and Run Your First Script
Goal: A working interpreter and a REPL.
Action: Install from your package manager.
# Ubuntu / Debian
sudo apt update && sudo apt install -y lua5.4
# macOS (Homebrew)
brew install lua
# Confirm
lua5.4 -v # Ubuntu binary name
lua -v # Homebrew and most other installs
On Ubuntu the binary is lua5.4. The examples below say lua; substitute lua5.4 or create an alias with alias lua=lua5.4.
-- 01_hello.lua
print("Hello from Lua " .. _VERSION)
lua 01_hello.lua
Verify:
Hello from Lua Lua 5.4
Run lua with no arguments for an interactive REPL. Exit with Control and D.
Step 2: Variables, Types and the local Keyword
Goal: Know the eight types and why local matters.
Action: Lua has eight types: nil, boolean, number, string, table, function, userdata and thread. You will use the first six daily. Variables are global unless you say local, so always say local.
-- 02_types.lua
local hostname = "fgt-edge-01" -- string
local port = 443 -- integer
local loss = 0.25 -- float
local is_up = true -- boolean
local owner -- nil (declared, no value)
print(type(hostname), type(port), type(loss), type(is_up), type(owner))
print(math.type(port), math.type(loss))
print(7 / 2, 7 // 2, 7 % 2, 2 ^ 10)
Verify:
string number number boolean nil
integer float
3.5 3 1 1024.0
Note that / always returns a float, // is floor division, and ^ (exponent) always returns a float. Comments start with --, and block comments use --[[ ... ]].
Step 3: Strings and Lua Patterns
Goal: Slice, format and parse text, which is most of what security scripting is.
Action: Strings are immutable. Concatenate with .., get length with #, and call string library functions with method syntax (s:upper()). For matching, Lua uses its own patterns, not regex. They are smaller and faster, with % as the escape character instead of backslash.
| Pattern | Matches |
|---|---|
%d / %D |
digit / non-digit |
%a / %w |
letter / alphanumeric |
%s / %S |
whitespace / non-whitespace |
%. |
a literal dot (. alone means any character) |
+ * - |
1 or more, 0 or more (greedy), 0 or more (lazy) |
( ) |
capture |
^ $ |
anchor to start / end |
-- 03_strings.lua
local line = "srcip=10.0.10.50 dstip=198.18.0.10 dstport=443 action=deny"
print(#line) -- length
print(line:upper():sub(1, 15)) -- method call syntax
print(string.format("%-8s %5d", "https", 443)) -- printf style
-- Lua patterns: %d digit, %a letter, %s space, %w alnum, %. literal dot
local src = line:match("srcip=(%d+%.%d+%.%d+%.%d+)")
local port = tonumber(line:match("dstport=(%d+)"))
print(src, port, math.type(port))
-- iterate every key=value pair
for k, v in line:gmatch("(%w+)=(%S+)") do
print(k, v)
end
print(("deny"):rep(3, ","))
print(line:gsub("10%.0%.10%.50", "[REDACTED]"))
Verify:
58
SRCIP=10.0.10.5
https 443
10.0.10.50 443 integer
srcip 10.0.10.50
dstip 198.18.0.10
dstport 443
action deny
deny,deny,deny
srcip=[REDACTED] dstip=198.18.0.10 dstport=443 action=deny 1
The trailing 1 on the last line is not a typo. gsub returns two values: the new string and the number of replacements. That is your first look at multiple return values (Step 6).
Step 4: Control Flow and Truthiness
Goal: Branches and loops, plus the one rule that trips up every Python and JavaScript person.
Action: Blocks end with end. Only nil and false are false. The number 0 and the empty string are true.
-- 04_control.lua
local rtt = 42
if rtt < 20 then
print("fast")
elseif rtt < 100 then
print("ok")
else
print("slow")
end
-- only nil and false are falsy: 0 and "" are TRUE
if 0 then print("0 is true in Lua") end
if "" then print("empty string is true too") end
for i = 1, 5 do io.write(i, " ") end print()
for i = 10, 1, -3 do io.write(i, " ") end print()
local tries = 0
while tries < 3 do tries = tries + 1 end
print("tries:", tries)
repeat tries = tries - 1 until tries == 0
print("after repeat:", tries)
-- no 'continue' keyword: use goto
for i = 1, 6 do
if i % 2 == 0 then goto continue end
io.write(i, " ")
::continue::
end
print()
local mode = (rtt > 50) and "degraded" or "healthy" -- ternary idiom
print(mode)
Verify:
ok
0 is true in Lua
empty string is true too
1 2 3 4 5
10 7 4 1
tries: 3
after repeat: 0
1 3 5
healthy
Comparison operators are ==, ~= (not equal, not !=), <, >, <=, >=. Logical operators are the words and, or, not.
Step 5: Tables, the Only Data Structure
Goal: Build arrays, dictionaries and records with one type.
Action: A table is an associative array. Use integer keys starting at 1 and it behaves like a list. Use string keys and it is a dictionary. Nest them and you have JSON-shaped records. Iterate lists with ipairs (ordered) and dictionaries with pairs (no guaranteed order).
-- 05_tables.lua
-- array part (1-indexed!)
local ports = {22, 80, 443}
table.insert(ports, 8443) -- append
print(ports[1], #ports) -- 22 4
for i, p in ipairs(ports) do -- ordered, stops at first nil
io.write(i, "=", p, " ")
end
print()
-- hash part (dictionary)
local fw = {
name = "fgt-edge-01",
mgmt = "10.0.0.1",
wan = "198.18.0.2",
}
fw.model = "FortiGate 120G" -- add a field
fw["serial"] = "FG120GTK00000000" -- same thing, bracket syntax
fw.serial = nil -- delete a field
local keys = {}
for k in pairs(fw) do keys[#keys + 1] = k end -- pairs order is NOT guaranteed
table.sort(keys)
print(table.concat(keys, ", "))
-- nested tables model real data
local inventory = {
{ host = "sw-core-01", ip = "10.0.0.2", up = true },
{ host = "ap-lobby", ip = "10.0.20.11", up = false },
}
for _, dev in ipairs(inventory) do
print(string.format("%-12s %-12s %s", dev.host, dev.ip, dev.up and "UP" or "DOWN"))
end
-- tables are references, not copies
local a = {1, 2}
local b = a
b[1] = 99
print(a[1])
Verify:
22 4
1=22 2=80 3=443 4=8443
mgmt, model, name, wan
sw-core-01 10.0.0.2 UP
ap-lobby 10.0.20.11 DOWN
99
Setting a key to nil deletes it. t.name is just sugar for t["name"]. The idiom t[#t + 1] = v appends without a function call.
Step 6: Functions, Multiple Returns and Closures
Goal: Write reusable functions the Lua way.
Action: Functions are values: store them in variables and tables, pass them around, return them. They can return several values, take variable arguments with ..., and capture local variables (closures).
-- 06_functions.lua
-- multiple return values
local function split_hostport(s)
local host, port = s:match("^(.-):(%d+)$")
return host, tonumber(port)
end
local h, p = split_hostport("10.0.0.1:8443")
print(h, p)
-- varargs
local function log(level, fmt, ...)
print(string.format("[%s] " .. fmt, level, ...))
end
log("WARN", "%d failed logins from %s", 5, "198.18.0.77")
-- functions are values
local checks = {
ssh = function(port) return port == 22 end,
https = function(port) return port == 443 end,
}
print(checks.https(443), checks.ssh(443))
-- closures capture local state
local function counter()
local n = 0
return function() n = n + 1; return n end
end
local next_id = counter()
next_id(); next_id()
print("next id:", next_id())
-- default argument idiom
local function connect(host, port)
port = port or 22
return host .. ":" .. port
end
print(connect("10.0.0.1"), connect("10.0.0.1", 2222))
Verify:
10.0.0.1 8443
[WARN] 5 failed logins from 198.18.0.77
true false
next id: 3
10.0.0.1:22 10.0.0.1:2222
Step 7: Error Handling with pcall and assert
Goal: Fail cleanly instead of crashing the host tool.
Action: Lua has no try/catch keywords. Raise with error(), catch with pcall() (protected call), which returns true plus results or false plus the error. Many library functions use the other convention: return nil, "error message" and let you decide.
-- 07_errors.lua
local function parse_port(s)
local n = tonumber(s)
if not n or n < 1 or n > 65535 or math.type(n) ~= "integer" then
error("invalid port: " .. tostring(s), 2)
end
return n
end
-- pcall = protected call, Lua's try/catch
local ok, result = pcall(parse_port, "443")
print(ok, result)
ok, result = pcall(parse_port, "99999")
print(ok, result)
-- assert returns its arguments when the first is truthy
local f, err = io.open("does-not-exist.txt", "r")
print(f, err)
local ok2, msg = pcall(assert, f, err)
print(ok2, msg)
Verify:
true 443
false invalid port: 99999
nil does-not-exist.txt: No such file or directory
false does-not-exist.txt: No such file or directory
The common pattern in real scripts is local fh = assert(io.open(path)): it returns the file handle on success and raises the open error on failure.
Step 8: Modules and require
Goal: Split code into files the way every Nmap library does.
Action: A module is a file that returns a table of functions. require finds it on package.path, runs it once and caches the result. This example also shows the Lua 5.3+ bitwise operators doing a subnet check.
-- lib/netutil.lua : a tiny module
local M = {}
function M.ip_to_int(ip)
local a, b, c, d = ip:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$")
if not a then return nil, "bad ip: " .. ip end
return ((tonumber(a) * 256 + tonumber(b)) * 256 + tonumber(c)) * 256 + tonumber(d)
end
function M.in_subnet(ip, net, bits)
local mask = (0xFFFFFFFF << (32 - bits)) & 0xFFFFFFFF
return (M.ip_to_int(ip) & mask) == (M.ip_to_int(net) & mask)
end
return M
-- 08_modules.lua
package.path = "./lib/?.lua;" .. package.path
local net = require("netutil")
print(net.ip_to_int("10.0.0.1"))
print(net.in_subnet("10.0.20.11", "10.0.0.0", 16))
print(net.in_subnet("198.18.0.77", "10.0.0.0", 16))
print(net.ip_to_int("10.0.0"))
Verify:
167772161
true
false
nil bad ip: 10.0.0
Step 9: Metatables and Lightweight Objects
Goal: Read the object-style code you will find in Wireshark and NSE libraries.
Action: A metatable changes how a table behaves. Setting __index to a class table gives you method lookup (that is the whole OOP system), and __tostring controls how print shows it. The colon in function Rule:hit() adds a hidden self parameter, and r:hit() passes it.
-- 09_metatables.lua
local Rule = {}
Rule.__index = Rule
function Rule.new(id, src, dst, action)
local self = setmetatable({}, Rule)
self.id, self.src, self.dst, self.action = id, src, dst, action or "deny"
self.hits = 0
return self
end
function Rule:hit() -- colon = implicit self
self.hits = self.hits + 1
return self
end
Rule.__tostring = function(r)
return string.format("rule %d %s -> %s %s (hits=%d)", r.id, r.src, r.dst, r.action, r.hits)
end
local r = Rule.new(10, "10.0.10.0/24", "198.18.0.10", "accept")
r:hit():hit()
print(r)
print(Rule.new(99, "any", "any"))
Verify:
rule 10 10.0.10.0/24 -> 198.18.0.10 accept (hits=2)
rule 99 any -> any deny (hits=0)
Step 10: Put It Together, a FortiGate Log Parser
Goal: A real tool: read a FortiGate traffic log, count denied sessions per source IP, print a sorted report.
Action: Save a sample log, then the script. It uses everything above: io.open, patterns with captures, tables as counters, table.sort with a custom comparator, and string.format.
date=2026-10-19 time=06:01:11 type="traffic" subtype="forward" srcip=10.0.10.50 dstip=198.18.0.10 dstport=443 action="accept"
date=2026-10-19 time=06:01:12 type="traffic" subtype="forward" srcip=10.0.10.77 dstip=198.18.0.20 dstport=22 action="deny"
date=2026-10-19 time=06:01:13 type="traffic" subtype="forward" srcip=10.0.10.77 dstip=198.18.0.20 dstport=22 action="deny"
date=2026-10-19 time=06:01:14 type="traffic" subtype="forward" srcip=10.0.20.11 dstip=198.18.0.10 dstport=443 action="accept"
date=2026-10-19 time=06:01:15 type="traffic" subtype="forward" srcip=10.0.10.77 dstip=198.18.0.21 dstport=3389 action="deny"
date=2026-10-19 time=06:01:16 type="traffic" subtype="forward" srcip=10.0.30.5 dstip=198.18.0.20 dstport=22 action="deny"
Save that as fgt-sample.log, then:
#!/usr/bin/env lua
-- logcount.lua : count denied sessions per source IP in a FortiGate traffic log
local path = arg[1] or "fgt-sample.log"
local function parse(line)
local rec = {}
-- handles key=value and key="quoted value"
for k, v in line:gmatch('(%w+)=("?[^"%s]*"?)') do
rec[k] = v:gsub('^"(.*)"$', "%1")
end
return rec
end
local fh, err = io.open(path, "r")
if not fh then
io.stderr:write("error: ", err, "\n")
os.exit(1)
end
local denies, total = {}, 0
for line in fh:lines() do
local r = parse(line)
total = total + 1
if r.action == "deny" then
denies[r.srcip] = (denies[r.srcip] or 0) + 1
end
end
fh:close()
-- sort sources by deny count, highest first
local sorted = {}
for ip, n in pairs(denies) do sorted[#sorted + 1] = { ip = ip, n = n } end
table.sort(sorted, function(a, b) return a.n > b.n or (a.n == b.n and a.ip < b.ip) end)
print(string.format("%d lines parsed, %d sources with denies", total, #sorted))
print(string.format("%-15s %s", "SRCIP", "DENIES"))
for _, row in ipairs(sorted) do
print(string.format("%-15s %d", row.ip, row.n))
end
chmod +x logcount.lua
./logcount.lua fgt-sample.log
Verify:
6 lines parsed, 2 sources with denies
SRCIP DENIES
10.0.10.77 3
10.0.30.5 1
The arg table holds command line arguments (arg[1] is the first). The parser keeps this primer readable by skipping quoted values that contain spaces, such as some msg= fields. For production logs, extend the pattern or parse character by character.
Step 11: Your First Nmap NSE Script
Goal: See how the same language plugs into a host tool.
Action: An NSE script is a Lua file with a few required globals. portrule decides whether the script runs against a port; action does the work and returns what Nmap prints. Everything Nmap-specific (shortport, http) comes from libraries the host injects, which is the embedding model from the diagram.
-- http-server-lite.nse
local http = require "http"
local shortport = require "shortport"
description = [[Reports the HTTP Server response header.]]
author = "InfoSecMonkey"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
portrule = shortport.http
action = function(host, port)
local resp = http.get(host, port, "/")
if resp and resp.header and resp.header["server"] then
return "Server: " .. resp.header["server"]
end
end
nmap -p 80,443 --script ./http-server-lite.nse 10.0.10.20
Verify: Each open web port in the output carries a script line under it:
PORT STATE SERVICE
80/tcp open http
|_http-server-lite: Server: nginx
Only scan hosts you own or are authorized to test. NSE scripts are full programs running with your privileges.
Verification and Validation
Run these checks against your working folder.
# 1. Interpreter and version
lua -v
# 2. Syntax check every file without running it
luac -p *.lua lib/*.lua && echo "syntax OK"
# 3. Run the capstone
./logcount.lua fgt-sample.log
Expected success output:
Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio
syntax OK
6 lines parsed, 2 sources with denies
SRCIP DENIES
10.0.10.77 3
10.0.30.5 1
A syntax error shows the file, line and what the parser expected, which is usually a missing end or parenthesis:
luac: bad.lua:2: ')' expected (to close '(' at line 1) near <eof>
Troubleshooting and Gotchas
Gotcha 1: Accidental Globals and “attempt to index a nil value”
Symptom: A counter stays at 0, or you get attempt to index a nil value (local 'x') / (global 'x').
Cause: Any name you assign without local becomes a global, so a typo silently creates a new variable. Reading a misspelled name returns nil instead of raising an error.
local function count_hosts(list)
local total = 0
for _ in ipairs(list) do totl = (totl or 0) + 1 end -- typo creates a GLOBAL
return total
end
print(count_hosts({"a", "b"}), totl) --> 0 2
Fix: Declare everything local. Lint with luacheck (install via luarocks install luacheck), which flags undefined and unused globals. The traceback tells you whether the nil is a local, global, or field, so read it before guessing.
Gotcha 2: Arrays Start at 1, and # Lies When There Are Holes
Symptom: Off-by-one errors, or loops that stop early.
Cause: Lua lists start at index 1, and ipairs stops at the first nil. The # length operator is only reliable when the list has no holes.
local t = {1, 2, nil, 4}
print(#t) --> 4 (or 2: undefined with holes)
-- ipairs(t) stops after 2 elements
Fix: Never store nil inside a list. Use a sentinel such as false, or track the count yourself in a field like t.n. When converting from 0-based data (C arrays, packet offsets, Python), add 1 at the boundary.
Gotcha 3: Patterns Are Not Regex
Symptom: find(".") matches at position 1 on every string, \d does nothing, alternation a|b fails.
Cause: In Lua patterns . means any character, the escape is % not backslash, and there is no | alternation or {n} repetition.
print(("10.0.0.1"):find(".")) --> 1 1 (wildcard)
print(("10.0.0.1"):find("%.")) --> 3 3 (escaped dot)
print(("10.0.0.1"):find(".", 1, true)) --> 3 3 (plain text search)
Fix: Escape magic characters ( ) . % + - * ? [ ] ^ $ with %. Pass true as the fourth argument to string.find for literal searches. For alternation, run two matches. When you truly need regex, the lrexlib or LPeg libraries are available through LuaRocks.
Lua Primer Quick Reference
| Task | Lua |
|---|---|
| Local variable | local x = 10 |
| Not equal | a ~= b |
| Concatenate | "a" .. "b" |
| Length | #s, #t |
| Integer division | 7 // 2 |
| Comment / block comment | -- and --[[ ]] |
| Append to list | t[#t + 1] = v or table.insert(t, v) |
| Iterate list / dict | ipairs(t) / pairs(t) |
| Join list | table.concat(t, ",") |
| String to number | tonumber("443") |
| Format | string.format("%s:%d", h, p) |
| Match / all matches | s:match(pat), s:gmatch(pat) |
| Replace | s:gsub(pat, repl) |
| Catch errors | local ok, err = pcall(f, ...) |
| Import module | local m = require("name") |
| Script arguments | arg[1], arg[2] |
| Syntax check | luac -p file.lua |
Where to Go Next
- Read the official Lua 5.4 Reference Manual at lua.org. It is short and precise.
- Browse the scripts in Nmap’s
scripts/directory and libraries innselib/. You can now read all of them. - Try a Wireshark Lua dissector for a custom protocol in your lab.
- Install LuaRocks, the package manager, for libraries like
luasocket,lpegandluacheck.
InfoSecMonkey | No fluff. Just the config that works.
Recent posts
-
-
DNS is one of those technologies that quietly underpins... Full Story
-
BGP issues on FortiGate firewalls usually trace back to... Full Story
-
Every time your laptop talks to your router, a... Full Story
-
If you've spent any time configuring NAT on a... Full Story
-
If you have spent any time configuring firewall policies... Full Story
-
High availability on FortiGate is one of those features... Full Story
-
If you've configured SD-WAN on a FortiGate, you've almost... Full Story
-
FortiLink is the management protocol that turns a FortiSwitch... Full Story
-
FortiSwitches are pretty rock solid from Mean Time Between... Full Story
-
This is a quicky tip. Have you ever gone... Full Story
-
DNS is one of those quiet pieces of internet... Full Story
-
This article is an updated version of the previous... Full Story
-
You will add ns2 as a secondary (slave) BIND9... Full Story
-
In the process of deploying my lab, I needed... Full Story
-
RFC 8805, used to be known as Self-Correcting IP... Full Story
-
Years back, I wrote an article about certificate pinning. ... Full Story
-
FortiGates have the ability to send alerts to Microsoft... Full Story
-
In this post, I am going to walk through... Full Story
-
Troubleshooting VoIP on a FortiGate can feel like trying... Full Story
-
Prior to FortiOS 7.0, there were three commands to... Full Story
-
In this post, I am going to go over... Full Story
-
What we are going to do: We are going... Full Story
-
Choosing between FGCP (FortiGate Clustering Protocol) and FGSP (FortiGate... Full Story
-
Creating a VLAN on macOS (The "Pro" Move) A... Full Story
-
This blog post explores the logic behind how macOS... Full Story
-
Pretty Fly for a Wi-Fi Tell My Wi-Fi Love... Full Story
-
Part of my daily gig is creating BoMs (Bill-of-Materials)... Full Story
-
ICMP introduces several security risks, but careful filtering, rate... Full Story
-
The command diag debug application dhcps -1 enables full... Full Story
-
In the world of FortiOS, execute tac report is... Full Story
-
LLDP; What is it The Link Layer Discovery Protocol... Full Story
-
What it actually does When you run diagnose fdsm... Full Story
-
Monkey Bites are bite-sized, high-impact security insights designed for... Full Story
-
I have run macOS in macOS with Parallels but... Full Story
-
Don't be confused with my other FortiNAC posts where... Full Story
-
This is the third session in a multi-part article... Full Story
-
Today I was configuring key-based authentication on a FortiGate... Full Story
-
Netcat, often called the "Swiss Army knife" of networking,... Full Story
-
At its core, IEEE 802.1X is a network layer... Full Story
-
In case you did not see the previous FortiNAC... Full Story
-
This is our 5th session where we are going... Full Story
-
Now that we have Wireshark installed and somewhat configured,... Full Story
-
The Philosophy of Packet Analysis Troubleshooting isn't about looking... Full Story
-
Executive Summary Objective: Get you from zero to writing... Full Story
-
NAND cells, the flash translation layer, SLC caching, TRIM,... Full Story
-
Nmap Scripting Engine (NSE): From One-Liners to Your First... Full Story